diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/ApiManagers/UploadManager.ts | 44 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 342 | ||||
-rw-r--r-- | src/server/public/files/.gitignore | 2 |
3 files changed, 194 insertions, 194 deletions
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index aca63a918..2a9faacd8 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -3,7 +3,7 @@ import { Method, _success } from "../RouteManager"; import * as formidable from 'formidable'; import v4 = require('uuid/v4'); var AdmZip = require('adm-zip'); -import * as path from 'path'; +import { extname, basename, dirname } from 'path'; import { createReadStream, createWriteStream, unlink, readFileSync } from "fs"; import { publicDirectory, filesDirectory } from ".."; import { Database } from "../database"; @@ -81,17 +81,17 @@ export default class UploadManager extends ApiManager { const zip = new AdmZip(path_2); zip.getEntries().forEach((entry: any) => { if (!entry.entryName.startsWith("files/")) return; - let dirname = path.dirname(entry.entryName) + "/"; - let extname = path.extname(entry.entryName); - let basename = path.basename(entry.entryName).split(".")[0]; + let directory = dirname(entry.entryName) + "/"; + let extension = extname(entry.entryName); + let base = basename(entry.entryName).split(".")[0]; try { zip.extractEntryTo(entry.entryName, publicDirectory, true, false); - dirname = "/" + dirname; + directory = "/" + directory; - createReadStream(publicDirectory + dirname + basename + extname).pipe(createWriteStream(publicDirectory + dirname + basename + "_o" + extname)); - createReadStream(publicDirectory + dirname + basename + extname).pipe(createWriteStream(publicDirectory + dirname + basename + "_s" + extname)); - createReadStream(publicDirectory + dirname + basename + extname).pipe(createWriteStream(publicDirectory + dirname + basename + "_m" + extname)); - createReadStream(publicDirectory + dirname + basename + extname).pipe(createWriteStream(publicDirectory + dirname + basename + "_l" + extname)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_o" + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_s" + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_m" + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_l" + extension)); } catch (e) { console.log(e); } @@ -133,29 +133,9 @@ export default class UploadManager extends ApiManager { form.keepExtensions = true; return new Promise<void>(resolve => { form.parse(req, async (_err, _fields, files) => { - let results: DashUploadUtils.ImageFileResponse[] = []; + let results: any[] = []; for (const key in files) { - const { type, path: location, name } = files[key]; - const filename = path.basename(location); - let uploadInformation: Opt<DashUploadUtils.UploadInformation>; - if (filename.endsWith(".pdf")) { - let dataBuffer = readFileSync(filesDirectory + filename); - const result: ParsedPDF = await pdf(dataBuffer); - await new Promise<void>((resolve, reject) => { - const path = filesDirectory + DashUploadUtils.Partitions.pdf_text + "/" + filename.substring(0, filename.length - ".pdf".length) + ".txt"; - createWriteStream(path).write(result.text, error => { - if (!error) { - resolve(); - } else { - reject(error); - } - }); - }); - } else { - uploadInformation = await DashUploadUtils.UploadImage(filesDirectory + filename, filename); - } - const exif = uploadInformation ? uploadInformation.exifData : undefined; - results.push({ name, type, path: `/files/${filename}`, exif }); + results.push(DashUploadUtils.upload(files[key])); } _success(res, results); resolve(); @@ -188,7 +168,7 @@ export default class UploadManager extends ApiManager { return; } return imageDataUri.outputFile(uri, filesDirectory + filename).then((savedName: string) => { - const ext = path.extname(savedName).toLowerCase(); + const ext = extname(savedName).toLowerCase(); const { pngs, jpgs } = SharedMediaTypes; let resizers = [ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" }, diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 8a429b81b..839aada4b 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -7,6 +7,8 @@ import { ExifData, ExifImage } from 'exif'; import { Opt } from '../new_fields/Doc'; import { SharedMediaTypes } from './SharedMediaTypes'; import { filesDirectory } from '.'; +import { File } from 'formidable'; +import { extname, basename } from "path"; const uploadDirectory = path.join(__dirname, './public/files/'); @@ -45,175 +47,195 @@ export namespace DashUploadUtils { contentType?: string; } - const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${sanitizeExtension(url)}`; - const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); - const sanitizeExtension = (source: string) => { - let extension = path.extname(source); - extension = extension.toLowerCase(); - extension = extension.split("?")[0]; - return extension; - }; - - /** - * Uploads an image specified by the @param source to Dash's /public/files/ - * directory, and returns information generated during that upload - * - * @param {string} source is either the absolute path of an already uploaded image or - * the url of a remote image - * @param {string} filename dictates what to call the image. If not specified, - * the name {@param prefix}_upload_{GUID} - * @param {string} prefix is a string prepended to the generated image name in the - * event that @param filename is not specified - * - * @returns {UploadInformation} This method returns - * 1) the paths to the uploaded images (plural due to resizing) - * 2) the file name of each of the resized images - * 3) the size of the image, in bytes (4432130) - * 4) the content type of the image, i.e. image/(jpeg | png | ...) - */ - export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise<UploadInformation> => { - const metadata = await InspectImage(source); - return UploadInspectedImage(metadata, filename, prefix); - }; - - export interface InspectionResults { - isLocal: boolean; - stream: any; - normalizedUrl: string; - exifData: EnrichedExifData; - contentSize?: number; - contentType?: string; - } - - export interface EnrichedExifData { - data: ExifData; - error?: string; - } - - export enum Partitions { - pdf_text, - images, - videos - } - - export async function buildFilePartitions() { - const pending = Object.keys(Partitions).map(sub => createIfNotExists(filesDirectory + sub)); - return Promise.all(pending); - } - - /** - * Based on the url's classification as local or remote, gleans - * as much information as possible about the specified image - * - * @param source is the path or url to the image in question - */ - export const InspectImage = async (source: string): Promise<InspectionResults> => { - const { isLocal, stream, normalized: normalizedUrl } = classify(source); - const exifData = await parseExifData(source); - const results = { - exifData, - isLocal, - stream, - normalizedUrl - }; - // stop here if local, since request.head() can't handle local paths, only urls on the web - if (isLocal) { - return results; + export function upload(file: File): any { + const { type, path, name } = file; + const filename = basename(path); + const extension = extname(path).toLowerCase(); + if (extension === ".pdf") { + + } else if { + let partition: Opt<string>; + if(imageFormats.includes(extension)) { + partition = DashUploadUtils.Partitions.images; + } else if (videoFormats.includes(extension)) { + partition = DashUploadUtils.Partitions.videos; } - const metadata = (await new Promise<any>((resolve, reject) => { - request.head(source, async (error, res) => { - if (error) { - return reject(error); - } - resolve(res); - }); - })).headers; - return { - contentSize: parseInt(metadata[size]), - contentType: metadata[type], - ...results - }; + let uploadInformation: Opt<DashUploadUtils.UploadInformation>; + if (partition) { + uploadInformation = await DashUploadUtils.UploadImage(`${filesDirectory}/${partition}/${filename}`, filename); + } else { + console.log(`Unable to accommodate, and ignored, the following file upload: ${filename}`); + } + } + const exif = uploadInformation ? uploadInformation.exifData : undefined; + results.push({ name, type, path: `/files/${filename}`, exif }); + +} + +const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${sanitizeExtension(url)}`; +const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); +const sanitizeExtension = (source: string) => { + let extension = path.extname(source); + extension = extension.toLowerCase(); + extension = extension.split("?")[0]; + return extension; +}; + +/** + * Uploads an image specified by the @param source to Dash's /public/files/ + * directory, and returns information generated during that upload + * + * @param {string} source is either the absolute path of an already uploaded image or + * the url of a remote image + * @param {string} filename dictates what to call the image. If not specified, + * the name {@param prefix}_upload_{GUID} + * @param {string} prefix is a string prepended to the generated image name in the + * event that @param filename is not specified + * + * @returns {UploadInformation} This method returns + * 1) the paths to the uploaded images (plural due to resizing) + * 2) the file name of each of the resized images + * 3) the size of the image, in bytes (4432130) + * 4) the content type of the image, i.e. image/(jpeg | png | ...) + */ +export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise<UploadInformation> => { + const metadata = await InspectImage(source); + return UploadInspectedImage(metadata, filename, prefix); +}; + +export interface InspectionResults { + isLocal: boolean; + stream: any; + normalizedUrl: string; + exifData: EnrichedExifData; + contentSize?: number; + contentType?: string; +} + +export interface EnrichedExifData { + data: ExifData; + error?: string; +} + +export enum Partitions { + pdf_text = "pdf_text", + images = "images", + videos = "videos" +} + +export async function buildFilePartitions() { + const pending = Object.keys(Partitions).map(sub => createIfNotExists(filesDirectory + sub)); + return Promise.all(pending); +} + +/** + * Based on the url's classification as local or remote, gleans + * as much information as possible about the specified image + * + * @param source is the path or url to the image in question + */ +export const InspectImage = async (source: string): Promise<InspectionResults> => { + const { isLocal, stream, normalized: normalizedUrl } = classify(source); + const exifData = await parseExifData(source); + const results = { + exifData, + isLocal, + stream, + normalizedUrl }; - - export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, prefix = ""): Promise<UploadInformation> => { - const { isLocal, stream, normalizedUrl, contentSize, contentType, exifData } = metadata; - const resolved = filename ? sanitize(filename) : generate(prefix, normalizedUrl); - const extension = sanitizeExtension(normalizedUrl || resolved); - let information: UploadInformation = { - mediaPaths: [], - fileNames: { clean: resolved }, - exifData, - contentSize, - contentType, - }; - const { pngs, imageFormats, jpgs, videoFormats } = SharedMediaTypes; - return new Promise<UploadInformation>(async (resolve, reject) => { - const resizers = [ - { resizer: sharp().rotate(), suffix: "_o" }, - ...Object.values(Sizes).map(size => ({ - resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), - suffix: size.suffix - })) - ]; - let nonVisual = false; - if (pngs.includes(extension)) { - resizers.forEach(element => element.resizer = element.resizer.png()); - } else if (jpgs.includes(extension)) { - resizers.forEach(element => element.resizer = element.resizer.jpeg()); - } else if (![...imageFormats, ...videoFormats].includes(extension.toLowerCase())) { - nonVisual = true; - } - if (imageFormats.includes(extension)) { - for (let resizer of resizers) { - const suffix = resizer.suffix; - let mediaPath: string; - await new Promise<void>(resolve => { - const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; - information.mediaPaths.push(mediaPath = uploadDirectory + filename); - information.fileNames[suffix] = filename; - stream(normalizedUrl).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) - .on('close', resolve) - .on('error', reject); - }); - } - } - if (!isLocal || nonVisual) { - await new Promise<void>(resolve => { - stream(normalizedUrl).pipe(fs.createWriteStream(uploadDirectory + resolved)).on('close', resolve); - }); + // stop here if local, since request.head() can't handle local paths, only urls on the web + if (isLocal) { + return results; + } + const metadata = (await new Promise<any>((resolve, reject) => { + request.head(source, async (error, res) => { + if (error) { + return reject(error); } - resolve(information); + resolve(res); }); + })).headers; + return { + contentSize: parseInt(metadata[size]), + contentType: metadata[type], + ...results }; - - const classify = (url: string) => { - const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); - return { - isLocal, - stream: isLocal ? fs.createReadStream : request, - normalized: isLocal ? path.normalize(url) : url - }; +}; + +export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, prefix = ""): Promise<UploadInformation> => { + const { isLocal, stream, normalizedUrl, contentSize, contentType, exifData } = metadata; + const resolved = filename ? sanitize(filename) : generate(prefix, normalizedUrl); + const extension = sanitizeExtension(normalizedUrl || resolved); + let information: UploadInformation = { + mediaPaths: [], + fileNames: { clean: resolved }, + exifData, + contentSize, + contentType, }; - - const parseExifData = async (source: string): Promise<EnrichedExifData> => { - return new Promise<EnrichedExifData>(resolve => { - new ExifImage(source, (error, data) => { - let reason: Opt<string> = undefined; - if (error) { - reason = (error as any).code; - } - resolve({ data, error: reason }); + const { pngs, jpgs } = SharedMediaTypes; + return new Promise<UploadInformation>(async (resolve, reject) => { + const resizers = [ + { resizer: sharp().rotate(), suffix: "_o" }, + ...Object.values(Sizes).map(size => ({ + resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), + suffix: size.suffix + })) + ]; + if (pngs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.png()); + } else if (jpgs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.jpeg()); + } + for (let resizer of resizers) { + const suffix = resizer.suffix; + let mediaPath: string; + await new Promise<void>(resolve => { + const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; + information.mediaPaths.push(mediaPath = uploadDirectory + filename); + information.fileNames[suffix] = filename; + stream(normalizedUrl).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) + .on('close', resolve) + .on('error', reject); + }); + } + if (!isLocal) { + await new Promise<void>(resolve => { + stream(normalizedUrl).pipe(fs.createWriteStream(uploadDirectory + resolved)).on('close', resolve); }); - }); - }; - - export const createIfNotExists = async (path: string) => { - if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) { - return true; } - return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null))); + resolve(information); + }); +}; + +const classify = (url: string) => { + const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); + return { + isLocal, + stream: isLocal ? fs.createReadStream : request, + normalized: isLocal ? path.normalize(url) : url }; +}; + +const parseExifData = async (source: string): Promise<EnrichedExifData> => { + return new Promise<EnrichedExifData>(resolve => { + new ExifImage(source, (error, data) => { + let reason: Opt<string> = undefined; + if (error) { + reason = (error as any).code; + } + resolve({ data, error: reason }); + }); + }); +}; + +export const createIfNotExists = async (path: string) => { + if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) { + return true; + } + return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null))); +}; - export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null))); }
\ No newline at end of file diff --git a/src/server/public/files/.gitignore b/src/server/public/files/.gitignore deleted file mode 100644 index c96a04f00..000000000 --- a/src/server/public/files/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore
\ No newline at end of file |