import request from 'request';
import { JSDOM } from 'jsdom';
import express from 'express';
import Busboy from 'busboy';
import querystring from 'querystring';
import http_errors from 'http-errors';
import jstz from 'jstz';
import { parseString as parse_xml_string } from 'xml2js';
import { OWN_CLOUD_MANAGER_CONFIG } from '../../environments/own-cloud-manager-config';
import { AuthResult } from '../../erpnext-api/own-cloud-api/models';
const DOWNLOAD_URL_RESOLVER: {
[format: string]: (city: string, scan_id: string) => string;
} = {
'zip': (city: string, scan_id: string): string =>
`${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/index.php/apps/files/ajax/download.php?${querystring.stringify({
dir: `/`,
files: `${scan_id}`,
})}`,
'ztl': (city: string, scan_id: string): string =>
`${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/index.php/apps/files/ajax/download.php?${querystring.stringify({
dir: `/`,
files: `${scan_id}/${scan_id}.QA.ztl`,
})}`,
'zpr': (city: string, scan_id: string): string =>
`${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/index.php/apps/files/ajax/download.php?${querystring.stringify({
dir: `/`,
files: `${scan_id}/${scan_id}.QA.zpr`,
})}`,
};
const UPLOAD_URL_RESOLVER: {
[format: string]: (city: string, scan_id: string) => string;
} = {
'ztl': (city: string, scan_id: string): string =>
`${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav/${scan_id}/${scan_id}.QA.ztl`,
'zpr': (city: string, scan_id: string): string =>
`${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav/${scan_id}/${scan_id}.QA.zpr`,
};
function auth(username: string, password: string): Promise<AuthResult> {
return new Promise<AuthResult>((resolve: (result: AuthResult) => void, reject: (error: any) => void): void => {
const jar: request.CookieJar = request.jar();
request({
method: 'GET',
url: `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/index.php/login`,
jar: jar,
}, (error, response, body): void => {
if (error) {
reject(error);
return;
}
const dom: JSDOM = new JSDOM(body);
const document: Document = dom.window.document;
const request_token: string = (document.querySelector('input[name="requesttoken"]') as HTMLInputElement).value;
request({
method: 'POST',
url: `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/index.php/login`,
jar: jar,
form: {
'user': username,
'password': password,
'timezone-offset': -new Date().getTimezoneOffset() / 60,
'timezone': jstz.determine().name(),
'requesttoken': request_token,
},
}, (error: any, response: request.Response, body: any): void => {
if (error) {
reject(error);
return;
}
resolve({request_token, jar});
});
});
});
}
function get_size(jar: request.CookieJar, path: string): Promise<number> {
return new Promise((resolve, reject): void => {
request({
method: 'PROPFIND',
url: `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav/${path}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `
<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:size />
</d:prop>
</d:propfind>
`.trim(),
jar: jar,
}, (error: any, response: request.Response, body: any): void => {
let size: number = -1;
if (response.statusCode === 207) {
parse_xml_string(body, (err: any, result: any): void => {
if (err) {
resolve(size);
return;
}
try {
size = +result['d:multistatus']['d:response']
.find((element: any): boolean => {
return element['d:href'][0] === `/remote.php/webdav/${encodeURI(path)}` || element['d:href'][0] === `/remote.php/webdav/${encodeURI(path)}/`;
})['d:propstat'][0]['d:prop'][0]['oc:size'][0];
} finally {
resolve(size);
}
});
return;
}
resolve(size);
});
});
}
export default class TransferClient {
constructor(private _username: string,
private _password: string) {
}
public async get_size(path: string) {
const {jar} = await auth(this._username, this._password);
return get_size(jar, path);
}
public async download(params: {
city: string;
scan_id: string;
format: string;
}, req: express.Request, res: express.Response, next: express.NextFunction) {
if (!DOWNLOAD_URL_RESOLVER[params.format]) {
throw new http_errors.NotFound();
}
const {jar} = await auth(this._username, this._password);
const url: string = DOWNLOAD_URL_RESOLVER[params.format](params.city, params.scan_id);
const download_request: request.Request = request({
method: 'GET',
url: url,
jar: jar,
}).on('response', (resp: request.Response): void => {
if (resp.statusCode !== 200) {
download_request.abort();
res
.status(resp.statusCode)
.end();
}
}).on('data', (chunk: Buffer): void => {
res.write(chunk);
}).on('complete', async (resp: request.Response, body?: string | Buffer) => {
res.end();
}).on('error', (error: any): void => {
next(error);
});
req.on('close', (): void => {
download_request.abort();
});
req.on('error', (): void => {
download_request.abort();
});
}
public async upload_zbrush_model(params: {
city: string;
scan_id: string;
format: string;
}, req: express.Request, res: express.Response, next: express.NextFunction) {
if (!UPLOAD_URL_RESOLVER[params.format]) {
throw new http_errors.NotFound();
}
const {jar} = await auth(this._username, this._password);
const url: string = UPLOAD_URL_RESOLVER[params.format](params.city, params.scan_id);
await this._delete(url, jar);
const busboy = new Busboy({
headers: req.headers,
});
busboy.on('file', (fieldname: string, file: NodeJS.ReadableStream, filename: string, encoding: string, mimetype: string): void => {
const upload_request: request.Request = request({
method: 'PUT',
url: url,
jar: jar,
body: file,
}).on('complete', async (resp: request.Response, body?: string | Buffer) => {
if (resp.statusCode === 200) {
res.end();
} else {
res
.status(resp.statusCode)
.end();
}
}).on('error', (error: any): void => {
next(error);
});
});
req.pipe(busboy);
}
public async upload_fbx_model(scan_id: string, req: express.Request, res: express.Response, next: express.NextFunction) {
const {jar} = await auth(this._username, this._password);
const base_url: string = `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav/3DG/Unity/fbx`;
const fbx_url: string = `${base_url}/${scan_id}.fbx`;
const jpg_url: string = `${base_url}/${scan_id}.jpg`;
const busboy = new Busboy({
headers: req.headers,
});
let is_fbx_uploaded: boolean = false;
let is_jpg_uploaded: boolean = false;
let is_error_occurred: boolean = false;
const rollback: () => Promise<void> =
async () => {
if (is_fbx_uploaded) {
await this._delete(fbx_url, jar);
}
if (is_jpg_uploaded) {
await this._delete(jpg_url, jar);
}
};
const on_complete: (resp: request.Response) => Promise<void> =
async (resp: request.Response) => {
if (resp.statusCode === 201) {
if (is_fbx_uploaded && is_jpg_uploaded) {
res
.status(201)
.end();
}
} else {
await rollback();
res
.status(resp.statusCode)
.end();
}
};
async function on_error(error: any) {
if (!is_error_occurred) {
await rollback();
is_error_occurred = true;
next(error);
}
}
busboy.on('file', async (fieldname: string, file: NodeJS.ReadableStream, filename: string, encoding: string, mimetype: string) => {
switch (fieldname) {
case 'fbx': {
await this._delete(fbx_url, jar);
request({
method: 'PUT',
url: fbx_url,
jar: jar,
body: file,
}).on('complete', async (resp: request.Response, body?: string | Buffer) => {
if (resp.statusCode === 201) {
is_fbx_uploaded = true;
}
await on_complete(resp);
}).on('error', async (error: any) => {
await on_error(error);
});
break;
}
case 'jpg': {
await this._delete(jpg_url, jar);
request({
method: 'PUT',
url: jpg_url,
jar: jar,
body: file,
}).on('complete', async (resp: request.Response, body?: string | Buffer) => {
if (resp.statusCode === 201) {
is_jpg_uploaded = true;
}
await on_complete(resp);
}).on('error', async (error: any) => {
await on_error(error);
});
break;
}
}
});
req.pipe(busboy);
}
public async upload_file(path: string, file: NodeJS.ReadableStream): Promise<void> {
const {jar} = await auth(this._username, this._password);
const url: string = `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav${path}`;
await this._delete(url, jar);
return new Promise((resolve, reject): void => {
const upload_request: request.Request = request({
method: 'PUT',
url: url,
jar: jar,
body: file,
}).on('complete', async (resp: request.Response, body?: string | Buffer) => {
if (resp.statusCode === 201) {
resolve();
} else {
reject();
}
}).on('error', (error: any): void => {
reject(error);
});
});
}
public async create_folder(path: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const {jar} = await auth(this._username, this._password);
request({
method: 'MKCOL',
url: `${OWN_CLOUD_MANAGER_CONFIG.ownCloud.host}/remote.php/webdav${path}`,
jar: jar,
}, (error: any, response: request.Response, body: any): void => {
if (response.statusCode === 201) {
resolve();
return;
}
reject();
});
});
}
private async _delete(url: string, jar: request.CookieJar) {
return new Promise((resolve): void => {
request({
method: 'DELETE',
url: url,
jar: jar,
}, (): void => {
resolve();
});
});
}
}