diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 2 | ||||
-rw-r--r-- | src/fields/URLField.ts | 2 | ||||
-rw-r--r-- | src/server/ApiManagers/AzureManager.ts | 67 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 61 |
5 files changed, 128 insertions, 6 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5ef033e35..f3f645ca2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1731,7 +1731,7 @@ export namespace DocUtils { return; } const full = { ...options, _width: 400, title: name }; - const pathname = Utils.prepend(result.accessPaths.agnostic.client); + const pathname = result.accessPaths.agnostic.client; const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); if (doc) { const proto = Doc.GetProto(doc); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 909a420fe..d763753a5 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -295,7 +295,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp choosePath(url: URL) { const lower = url.href.toLowerCase(); if (url.protocol === 'data') return url.href; - if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); + if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf("dashblobstore") === -1) return Utils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`; const ext = extname(url.href); diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 8ac20b1e5..8db4d6003 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -25,7 +25,7 @@ export abstract class URLField extends ObjectField { constructor(url: URL | string) { super(); if (typeof url === 'string') { - url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin); + url = (url.startsWith('http') || url.startsWith('https')) ? new URL(url) : new URL(url, window.location.origin); } this.url = url; } diff --git a/src/server/ApiManagers/AzureManager.ts b/src/server/ApiManagers/AzureManager.ts new file mode 100644 index 000000000..12bb98ad0 --- /dev/null +++ b/src/server/ApiManagers/AzureManager.ts @@ -0,0 +1,67 @@ +import { ContainerClient, BlobServiceClient } from "@azure/storage-blob"; +import * as fs from "fs"; +import { Readable, Stream } from "stream"; +const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING; + +export class AzureManager { + private _containerClient: ContainerClient; + private _blobServiceClient: BlobServiceClient; + private static _instance: AzureManager | undefined; + + public static CONTAINER_NAME = "dashmedia"; + public static STORAGE_ACCOUNT_NAME = "dashblobstore"; + + constructor() { + if (!AZURE_STORAGE_CONNECTION_STRING) { + throw new Error("Azure Storage Connection String Not Found"); + } + this._blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING); + this._containerClient = this.BlobServiceClient.getContainerClient(AzureManager.CONTAINER_NAME); + } + + public static get Instance() { + return this._instance = this._instance ?? new AzureManager(); + } + + public get BlobServiceClient() { + return this._blobServiceClient; + } + + public get ContainerClient() { + return this._containerClient; + } + + public static UploadBlob(filename: string, filepath: string, filetype: string) { + const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename); + const blobOptions = { blobHTTPHeaders: { blobContentType: filetype }}; + const stream = fs.createReadStream(filepath); + return blockBlobClient.uploadStream(stream, undefined, undefined, blobOptions); + } + + public static UploadBlobStream(stream: Readable, filename: string, filetype: string) { + const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename); + const blobOptions = { blobHTTPHeaders: { blobContentType: filetype }}; + return blockBlobClient.uploadStream(stream, undefined, undefined, blobOptions); + } + + public static DeleteBlob(filename: string) { + const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename); + return blockBlobClient.deleteIfExists(); + } + + public static async GetBlobs() { + const foundBlobs = []; + for await (const blob of this.Instance.ContainerClient.listBlobsFlat()) { + console.log(`${blob.name}`); + + const blobItem = { + url : `https://${AzureManager.STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${AzureManager.CONTAINER_NAME}/${blob.name}`, + name : blob.name + } + + foundBlobs.push(blobItem); + } + + return foundBlobs; + } +} diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index eaaac4e6d..bff60568b 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -6,7 +6,7 @@ import { createReadStream, createWriteStream, existsSync, readFileSync, rename, import * as path from 'path'; import { basename } from 'path'; import * as sharp from 'sharp'; -import { Stream } from 'stream'; +import { Readable, Stream } from 'stream'; import { filesDirectory, publicDirectory } from '.'; import { Opt } from '../fields/Doc'; import { ParsedPDF } from '../server/PdfTypes'; @@ -17,6 +17,8 @@ import { resolvedServerUrl } from './server_Initialization'; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); +import { AzureManager } from './ApiManagers/AzureManager'; +import axios from 'axios'; const spawn = require('child_process').spawn; const { exec } = require('child_process'); const parse = require('pdf-parse'); @@ -42,6 +44,10 @@ function isLocal() { return /Dash-Web[0-9]*[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/; } +function usingAzure(){ + return process.env.USE_AZURE === 'true'; +} + export namespace DashUploadUtils { export interface Size { width: number; @@ -61,6 +67,9 @@ export namespace DashUploadUtils { const size = 'content-length'; const type = 'content-type'; + const BLOBSTORE_URL = process.env.BLOBSTORE_URL; + const RESIZE_FUNCTION_URL = process.env.RESIZE_FUNCTION_URL; + const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr export async function concatVideos(filePaths: string[]): Promise<Upload.AccessPathInfo> { @@ -182,6 +191,7 @@ export namespace DashUploadUtils { } export async function upload(file: File, overwriteGuid?: string): Promise<Upload.FileResponse> { + const isAzureOn = usingAzure(); const { type, path, name } = file; const types = type?.split('/') ?? []; uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name. @@ -478,17 +488,48 @@ export namespace DashUploadUtils { }; } + /** + * UploadInspectedImage() takes an image with its metadata. If Azure is being used, this method will call the Azure function + * to execute the resizing. If Azure is not used, the function will begin to resize the image. + * + * @param metadata metadata object from InspectImage() + * @param filename the name of the file + * @param prefix the prefix to use, which will be set to '' if none is provided. + * @param cleanUp a boolean indicating if the files should be deleted after upload. True by default. + * @returns the accessPaths for the resized files. + */ export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = '', cleanUp = true): Promise<Upload.ImageInformation> => { const { requestable, source, ...remaining } = metadata; const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`; const { images } = Directory; const information: Upload.ImageInformation = { accessPaths: { - agnostic: getAccessPaths(images, resolved), + agnostic: usingAzure() ? { + client: BLOBSTORE_URL + `/${filename}`, + server: BLOBSTORE_URL + `/${filename}` + } : getAccessPaths(images, resolved) }, ...metadata, }; - const writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images)); + let writtenFiles: { [suffix: string] : string}; + + if (usingAzure()) { + if (!RESIZE_FUNCTION_URL) { + throw new Error("Resize function URL not provided."); + } + + try { + const response = await axios.post(RESIZE_FUNCTION_URL, { + url: requestable + }); + writtenFiles = response.data.writtenFiles; + } catch (err) { + console.error(err); + writtenFiles = {}; + } + } else { + writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images)); + } for (const suffix of Object.keys(writtenFiles)) { information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]); } @@ -533,6 +574,15 @@ export namespace DashUploadUtils { force: true, }; + /** + * outputResizedImages takes in a readable stream and resizes the images according to the sizes defined at the top of this file. + * + * The new images will be saved to the server with the corresponding prefixes. + * @param streamProvider a Stream of the image to process, taken from the /parsed_files location + * @param outputFileName the basename (No suffix) of the outputted file. + * @param outputDirectory the directory to output to, usually Directory.Images + * @returns a map with suffixes as keys and resized filenames as values. + */ export async function outputResizedImages(streamProvider: () => Stream | Promise<Stream>, outputFileName: string, outputDirectory: string) { const writtenFiles: { [suffix: string]: string } = {}; for (const { resizer, suffix } of resizers(path.extname(outputFileName))) { @@ -549,6 +599,11 @@ export namespace DashUploadUtils { return writtenFiles; } + /** + * define the resizers to use + * @param ext the extension + * @returns an array of resizer functions from sharp + */ function resizers(ext: string): DashUploadUtils.ImageResizer[] { return [ { suffix: SizeSuffix.Original }, |