From e8fcbbf57b2a2f443d9c280ce10558cf9d51c632 Mon Sep 17 00:00:00 2001 From: vellichora Date: Sun, 9 Feb 2020 17:11:24 -0500 Subject: can upload collection from mobile to desktop --- src/server/ApiManagers/UploadManager.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 74f45ae62..e76d9b7a2 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -42,6 +42,7 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: "/upload", secureHandler: async ({ req, res }) => { + console.log("/upload register"); const form = new formidable.IncomingForm(); form.uploadDir = pathToDirectory(Directory.parsed_files); form.keepExtensions = true; -- cgit v1.2.3-70-g09d2 From b97d0d1c03f567d53297f8922c7f407cbf0a9b7d Mon Sep 17 00:00:00 2001 From: kimdahey Date: Sat, 29 Feb 2020 13:50:28 -0500 Subject: added supported audio formats --- package-lock.json | 46 ++++++++++++++++----------------- src/server/ApiManagers/UploadManager.ts | 8 ++++++ src/server/SharedMediaTypes.ts | 1 + 3 files changed, 32 insertions(+), 23 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/package-lock.json b/package-lock.json index 2636d7113..c487b11c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1997,7 +1997,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -2585,7 +2585,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -2619,7 +2619,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", @@ -2783,7 +2783,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", @@ -3576,7 +3576,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -3588,7 +3588,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -4126,7 +4126,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -6994,7 +6994,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" @@ -7049,7 +7049,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" @@ -7736,7 +7736,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", @@ -8054,7 +8054,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { @@ -8086,7 +8086,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", @@ -8261,7 +8261,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -8594,7 +8594,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "nice-try": { @@ -8677,7 +8677,7 @@ }, "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" }, "tar": { @@ -12277,7 +12277,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -12290,7 +12290,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -12530,7 +12530,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -14367,7 +14367,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -14642,7 +14642,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", @@ -15499,7 +15499,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -15545,7 +15545,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { @@ -16317,7 +16317,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 8f2a5ea3e..4dc58d611 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -59,6 +59,14 @@ export default class UploadManager extends ApiManager { } }); + register({ + method: Method.GET, + subscription: "/hello", + secureHandler: ({ req, res }) => { + res.send("

world!

"); + } + }); + register({ method: Method.POST, subscription: "/uploadRemoteImage", diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 185e787cc..55c0d14e8 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -10,6 +10,7 @@ export namespace AcceptibleMedia { export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; export const videoFormats = [".mov", ".mp4"]; export const applicationFormats = [".pdf"]; + export const audioFormats = [".wav", ".mp3", ".flac", ".au", ".aiff", ".m4a"]; } export namespace Upload { -- cgit v1.2.3-70-g09d2 From 22bc57fe1f997d5f8f6a471b4b719b50fbd8552a Mon Sep 17 00:00:00 2001 From: kimdahey Date: Sat, 29 Feb 2020 14:16:13 -0500 Subject: created upload audio --- src/server/ApiManagers/UploadManager.ts | 3 ++- src/server/DashUploadUtils.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 4dc58d611..095567be1 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -19,7 +19,8 @@ export enum Directory { videos = "videos", pdfs = "pdfs", text = "text", - pdf_thumbnails = "pdf_thumbnails" + pdf_thumbnails = "pdf_thumbnails", + audio = "audio" } export function serverPathToFile(directory: Directory, filename: string) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index ea4c26ca2..e5aec852f 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -82,6 +82,19 @@ export namespace DashUploadUtils { return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) }; } + async function uploadAudio(file: File) { + const { path: sourcePath } = file; + const dataBuffer = readFileSync(sourcePath); + + await new Promise((resolve, reject) => { + const name = path.basename(sourcePath); + const audioFilename = `${name.substring(0, name.length - 4)}.mp3`; + const writeStream = createWriteStream(serverPathToFile(Directory.audio, audioFilename)); + writeStream.write(result.text, error => error ? reject(error) : resolve()); + }); + return MoveParsedFile(file, Directory.audio); + } + async function UploadPdf(file: File) { const { path: sourcePath } = file; const dataBuffer = readFileSync(sourcePath); -- cgit v1.2.3-70-g09d2 From 8c39fb5678bfdd414249f10b0b80e823370f7228 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 29 Feb 2020 15:26:55 -0500 Subject: consolidate server side google photos files --- src/server/ApiManagers/GooglePhotosManager.ts | 153 +++++++++++++++++++++- src/server/apis/google/GooglePhotosUploadUtils.ts | 150 --------------------- 2 files changed, 149 insertions(+), 154 deletions(-) delete mode 100644 src/server/apis/google/GooglePhotosUploadUtils.ts (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 25c54ee2e..98f6a1404 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -3,12 +3,13 @@ import { Method, _error, _success, _invalid } from "../RouteManager"; import * as path from "path"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; import { BatchedArray, TimeUnit } from "array-batcher"; -import { GooglePhotosUploadUtils } from "../apis/google/GooglePhotosUploadUtils"; import { Opt } from "../../new_fields/Doc"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import { Database } from "../database"; import { red } from "colors"; import { Upload } from "../SharedMediaTypes"; +import request = require('request-promise'); +import { NewMediaItemResult } from "../apis/google/SharedTypes"; const prefix = "google_photos_"; const remoteUploadError = "None of the preliminary uploads to Google's servers was successful."; @@ -64,7 +65,7 @@ export default class GooglePhotosManager extends ApiManager { // set on Google's servers, and would instantly return an error. So, we ease things out and send the photos to upload in // batches of 25, where the next batch is sent 100 millieconds after we receive a response from Google's servers. const failed: GooglePhotosUploadFailure[] = []; - const batched = BatchedArray.from(media, { batchSize: 25 }); + const batched = BatchedArray.from(media, { batchSize: 25 }); const interval = { magnitude: 100, unit: TimeUnit.Milliseconds }; const newMediaItems = await batched.batchedMapPatientInterval( interval, @@ -78,7 +79,7 @@ export default class GooglePhotosManager extends ApiManager { const imageToUpload = InjectSize(url, SizeSuffix.Original); // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token // which acts as a pointer to those bytes that we can use to locate them later on - const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(token, imageToUpload).catch(fail); + const uploadToken = await Uploader.SendBytes(token, imageToUpload).catch(fail); if (!uploadToken) { fail(`${path.extname(url)} is not an accepted extension`); } else { @@ -110,7 +111,7 @@ export default class GooglePhotosManager extends ApiManager { } // STEP 2/2: create the media items and return the API's response to the client, along with any failures - return GooglePhotosUploadUtils.CreateMediaItems(token, newMediaItems, req.body.album).then( + return Uploader.CreateMediaItems(token, newMediaItems, req.body.album).then( results => _success(res, { results, failed }), error => _error(res, mediaError, error) ); @@ -183,4 +184,148 @@ export default class GooglePhotosManager extends ApiManager { }); } +} + +/** + * This namespace encompasses the logic + * necessary to upload images to Google's server, + * and then initialize / create those images in the Photos + * API given the upload tokens returned from the initial + * uploading process. + * + * https://developers.google.com/photos/library/reference/rest/v1/mediaItems/batchCreate + */ +export namespace Uploader { + + /** + * Specifies the structure of the object + * necessary to upload bytes to Google's servers. + * The url is streamed to access the image's bytes, + * and the description is what appears in Google Photos' + * description field. + */ + export interface UploadSource { + url: string; + description: string; + } + + /** + * This is the format needed to pass + * into the BatchCreate API request + * to take a reference to raw uploaded bytes + * and actually create an image in Google Photos. + * + * So, to instantiate this interface you must have already dispatched an upload + * and received an upload token. + */ + export interface NewMediaItem { + description: string; + simpleMediaItem: { + uploadToken: string; + }; + } + + /** + * A utility function to streamline making + * calls to the API's url - accentuates + * the relative path in the caller. + * @param extension the desired + * subset of the API + */ + function prepend(extension: string): string { + return `https://photoslibrary.googleapis.com/v1/${extension}`; + } + + /** + * Factors out the creation of the API request's + * authentication elements stored in the header. + * @param type the contents of the request + * @param token the user-specific Google access token + */ + function headers(type: string, token: string) { + return { + 'Content-Type': `application/${type}`, + 'Authorization': `Bearer ${token}`, + }; + } + + /** + * This is the first step in the remote image creation process. + * Here we upload the raw bytes of the image to Google's servers by + * setting authentication and other required header properties and including + * the raw bytes to the image, to be uploaded, in the body of the request. + * @param bearerToken the user-specific Google access token, specifies the account associated + * with the eventual image creation + * @param url the url of the image to upload + * @param filename an optional name associated with the uploaded image - if not specified + * defaults to the filename (basename) in the url + */ + export const SendBytes = async (bearerToken: string, url: string, filename?: string): Promise => { + // check if the url points to a non-image or an unsupported format + if (!DashUploadUtils.validateExtension(url)) { + return undefined; + } + const body = await request(url, { encoding: null }); // returns a readable stream with the unencoded binary image data + const parameters = { + method: 'POST', + uri: prepend('uploads'), + headers: { + ...headers('octet-stream', bearerToken), + 'X-Goog-Upload-File-Name': filename || path.basename(url), + 'X-Goog-Upload-Protocol': 'raw' + }, + body + }; + return new Promise((resolve, reject) => request(parameters, (error, _response, body) => { + if (error) { + // on rejection, the server logs the error and the offending image + return reject(error); + } + resolve(body); + })); + }; + + /** + * This is the second step in the remote image creation process: having uploaded + * the raw bytes of the image and received / stored pointers (upload tokens) to those + * bytes, we can now instruct the API to finalize the creation of those images by + * submitting a batch create request with the list of upload tokens and the description + * to be associated with reach resulting new image. + * @param bearerToken the user-specific Google access token, specifies the account associated + * with the eventual image creation + * @param newMediaItems a list of objects containing a description and, effectively, the + * pointer to the uploaded bytes + * @param album if included, will add all of the newly created remote images to the album + * with the specified id + */ + export const CreateMediaItems = async (bearerToken: string, newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { + // it's important to note that the API can't handle more than 50 items in each request and + // seems to need at least some latency between requests (spamming it synchronously has led to the server returning errors)... + const batched = BatchedArray.from(newMediaItems, { batchSize: 50 }); + // ...so we execute them in delayed batches and await the entire execution + return batched.batchedMapPatientInterval( + { magnitude: 100, unit: TimeUnit.Milliseconds }, + async (batch: NewMediaItem[], collector: any): Promise => { + const parameters = { + method: 'POST', + headers: headers('json', bearerToken), + uri: prepend('mediaItems:batchCreate'), + body: { newMediaItems: batch } as any, + json: true + }; + // register the target album, if provided + album && (parameters.body.albumId = album.id); + collector.push(...(await new Promise((resolve, reject) => { + request(parameters, (error, _response, body) => { + if (error) { + reject(error); + } else { + resolve(body.newMediaItemResults); + } + }); + }))); + } + ); + }; + } \ No newline at end of file diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts deleted file mode 100644 index d305eed0a..000000000 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ /dev/null @@ -1,150 +0,0 @@ - -import request = require('request-promise'); -import * as path from 'path'; -import { NewMediaItemResult } from './SharedTypes'; -import { BatchedArray, TimeUnit } from 'array-batcher'; -import { DashUploadUtils } from '../../DashUploadUtils'; - -/** - * This namespace encompasses the logic - * necessary to upload images to Google's server, - * and then initialize / create those images in the Photos - * API given the upload tokens returned from the initial - * uploading process. - * - * https://developers.google.com/photos/library/reference/rest/v1/mediaItems/batchCreate - */ -export namespace GooglePhotosUploadUtils { - - /** - * Specifies the structure of the object - * necessary to upload bytes to Google's servers. - * The url is streamed to access the image's bytes, - * and the description is what appears in Google Photos' - * description field. - */ - export interface UploadSource { - url: string; - description: string; - } - - /** - * This is the format needed to pass - * into the BatchCreate API request - * to take a reference to raw uploaded bytes - * and actually create an image in Google Photos. - * - * So, to instantiate this interface you must have already dispatched an upload - * and received an upload token. - */ - export interface NewMediaItem { - description: string; - simpleMediaItem: { - uploadToken: string; - }; - } - - /** - * A utility function to streamline making - * calls to the API's url - accentuates - * the relative path in the caller. - * @param extension the desired - * subset of the API - */ - function prepend(extension: string): string { - return `https://photoslibrary.googleapis.com/v1/${extension}`; - } - - /** - * Factors out the creation of the API request's - * authentication elements stored in the header. - * @param type the contents of the request - * @param token the user-specific Google access token - */ - function headers(type: string, token: string) { - return { - 'Content-Type': `application/${type}`, - 'Authorization': `Bearer ${token}`, - }; - } - - /** - * This is the first step in the remote image creation process. - * Here we upload the raw bytes of the image to Google's servers by - * setting authentication and other required header properties and including - * the raw bytes to the image, to be uploaded, in the body of the request. - * @param bearerToken the user-specific Google access token, specifies the account associated - * with the eventual image creation - * @param url the url of the image to upload - * @param filename an optional name associated with the uploaded image - if not specified - * defaults to the filename (basename) in the url - */ - export const DispatchGooglePhotosUpload = async (bearerToken: string, url: string, filename?: string): Promise => { - // check if the url points to a non-image or an unsupported format - if (!DashUploadUtils.validateExtension(url)) { - return undefined; - } - const body = await request(url, { encoding: null }); // returns a readable stream with the unencoded binary image data - const parameters = { - method: 'POST', - uri: prepend('uploads'), - headers: { - ...headers('octet-stream', bearerToken), - 'X-Goog-Upload-File-Name': filename || path.basename(url), - 'X-Goog-Upload-Protocol': 'raw' - }, - body - }; - return new Promise((resolve, reject) => request(parameters, (error, _response, body) => { - if (error) { - // on rejection, the server logs the error and the offending image - return reject(error); - } - resolve(body); - })); - }; - - /** - * This is the second step in the remote image creation process: having uploaded - * the raw bytes of the image and received / stored pointers (upload tokens) to those - * bytes, we can now instruct the API to finalize the creation of those images by - * submitting a batch create request with the list of upload tokens and the description - * to be associated with reach resulting new image. - * @param bearerToken the user-specific Google access token, specifies the account associated - * with the eventual image creation - * @param newMediaItems a list of objects containing a description and, effectively, the - * pointer to the uploaded bytes - * @param album if included, will add all of the newly created remote images to the album - * with the specified id - */ - export const CreateMediaItems = async (bearerToken: string, newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { - // it's important to note that the API can't handle more than 50 items in each request and - // seems to need at least some latency between requests (spamming it synchronously has led to the server returning errors)... - const batched = BatchedArray.from(newMediaItems, { batchSize: 50 }); - // ...so we execute them in delayed batches and await the entire execution - return batched.batchedMapPatientInterval( - { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: NewMediaItem[], collector: any): Promise => { - const parameters = { - method: 'POST', - headers: headers('json', bearerToken), - uri: prepend('mediaItems:batchCreate'), - body: { newMediaItems: batch } as any, - json: true - }; - // register the target album, if provided - album && (parameters.body.albumId = album.id); - collector.push(...(await new Promise((resolve, reject) => { - request(parameters, (error, _response, body) => { - if (error) { - reject(error); - } else { - resolve(body.newMediaItemResults); - } - }); - }))); - } - ); - }; - -} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 7bf05274e1f3c75217db11bf3d4112431f55e1b5 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 29 Feb 2020 15:31:51 -0500 Subject: allow proper type inference for batched arrays systemwide --- src/server/ApiManagers/GooglePhotosManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 98f6a1404..88219423d 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -69,7 +69,7 @@ export default class GooglePhotosManager extends ApiManager { const interval = { magnitude: 100, unit: TimeUnit.Milliseconds }; const newMediaItems = await batched.batchedMapPatientInterval( interval, - async (batch: any, collector: any, { completedBatches }: any) => { + async (batch, collector, { completedBatches }) => { for (let index = 0; index < batch.length; index++) { const { url, description } = batch[index]; // a local function used to record failure of an upload @@ -305,7 +305,7 @@ export namespace Uploader { // ...so we execute them in delayed batches and await the entire execution return batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: NewMediaItem[], collector: any): Promise => { + async (batch: NewMediaItem[], collector): Promise => { const parameters = { method: 'POST', headers: headers('json', bearerToken), -- cgit v1.2.3-70-g09d2 From eddd9181d69bdf71d695c5adab9660cd8308fa69 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Wed, 4 Mar 2020 20:48:50 -0500 Subject: fixed file location bug --- src/client/views/nodes/AudioBox.tsx | 13 ++++++++----- src/server/ApiManagers/UploadManager.ts | 3 +++ src/server/DashUploadUtils.ts | 17 +++++++---------- src/server/SharedMediaTypes.ts | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 62a479b2a..5ed8fba19 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -44,6 +44,7 @@ export class AudioBox extends DocExtendableComponent { self.stopRecording(); - gumStream.getAudioTracks()[0].stop(); - }, 60 * 60 * 1000); // stop after an hour? + self._stream?.getAudioTracks()[0].stop(); + }, 60 * 1000); // stop after a minute }); } @@ -169,6 +170,7 @@ export class AudioBox extends DocExtendableComponent { - this.pause(); + this.stopRecording(); this._ele!.currentTime = 0; e.stopPropagation(); } @@ -210,6 +212,7 @@ export class AudioBox extends DocExtendableComponent; } + // line 226 is stop button but it doesn't do anything render() { const interactive = this.active() ? "-interactive" : ""; return
{ + const { sources } = req.body; if (Array.isArray(sources)) { const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))); @@ -85,6 +86,7 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: "/uploadDoc", secureHandler: ({ req, res }) => { + const form = new formidable.IncomingForm(); form.keepExtensions = true; // let path = req.body.path; @@ -189,6 +191,7 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: "/inspectImage", secureHandler: async ({ req, res }) => { + const { source } = req.body; if (typeof source === "string") { return res.send(await DashUploadUtils.InspectImage(source)); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 7cbfc4408..fb4e87c08 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -78,7 +78,8 @@ export namespace DashUploadUtils { } case "audio": if (audioFormats.includes(format)) { - return UploadAudio(file); + console.log("1"); + return MoveParsedFile(file, Directory.audio); } } @@ -86,16 +87,9 @@ export namespace DashUploadUtils { return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) }; } - async function uploadAudio(file: File) { + async function UploadAudio(file: File) { const { path: sourcePath } = file; - const dataBuffer = readFileSync(sourcePath); - await new Promise((resolve, reject) => { - const name = path.basename(sourcePath); - const audioFilename = `${name.substring(0, name.length - 4)}.mp3`; - const writeStream = createWriteStream(serverPathToFile(Directory.audio, audioFilename)); - writeStream.write(result.text, error => error ? reject(error) : resolve()); - }); return MoveParsedFile(file, Directory.audio); } @@ -109,6 +103,7 @@ export namespace DashUploadUtils { const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename)); writeStream.write(result.text, error => error ? reject(error) : resolve()); }); + console.log(MoveParsedFile(file, Directory.pdfs)); return MoveParsedFile(file, Directory.pdfs); } @@ -212,8 +207,10 @@ export namespace DashUploadUtils { accessPaths: { agnostic: getAccessPaths(destination, name) } + } - }); + } + ); }); }); } diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 55c0d14e8..3d3683912 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -10,7 +10,7 @@ export namespace AcceptibleMedia { export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; export const videoFormats = [".mov", ".mp4"]; export const applicationFormats = [".pdf"]; - export const audioFormats = [".wav", ".mp3", ".flac", ".au", ".aiff", ".m4a"]; + export const audioFormats = [".wav", ".mp3", ".flac", ".au", ".aiff", ".m4a", ".webm;codecs=opus"]; } export namespace Upload { -- cgit v1.2.3-70-g09d2 From 12329d9de180e8f0bd629ccf6b351baab7782fc5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 4 Mar 2020 23:41:31 -0500 Subject: fixes for video snapshots & following links to anchors in videos --- src/client/documents/Documents.ts | 4 ++-- src/client/util/DocumentManager.ts | 24 ++++++++++++++---------- src/client/util/DragManager.ts | 2 +- src/client/views/nodes/VideoBox.tsx | 1 - src/server/ApiManagers/UploadManager.ts | 2 ++ 5 files changed, 19 insertions(+), 14 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b06ff5465..5f0e63b56 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -549,8 +549,8 @@ export namespace Docs { linkDocProto.anchor2 = target.doc; linkDocProto.anchor1Context = source.ctx; linkDocProto.anchor2Context = target.ctx; - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor2Timecode = target.doc.currentTimecode; + linkDocProto.anchor1Timecode = source.doc.currentTimecode || source.doc.displayTimecode; + linkDocProto.anchor2Timecode = target.doc.currentTimecode || source.doc.displayTimecode; if (linkDocProto.layout_key1 === undefined) { Cast(linkDocProto.proto, Doc, null).layout_key1 = DocuLinkBox.LayoutString("anchor1"); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0ec1d23d9..323d31af2 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -158,16 +158,20 @@ export class DocumentManager { targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); // now find the target document within the context - setTimeout(() => { - const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); - if (retryDocView) { - retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context - } else { - if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); - targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target - } - highlight(); - }, 0); + if (targetDoc.displayTimecode) { // the target should show up once the video scrubs to the display timecode; + targetDocContext.currentTimecode = targetDoc.displayTimecode; + } else { + setTimeout(() => { + const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); + if (retryDocView) { + retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context + } else { + if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); + targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target + } + highlight(); + }, 0); + } } else { // there's no context view so we need to create one first and try again (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext); setTimeout(() => { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index af920c7da..8ddd59237 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -193,7 +193,7 @@ export namespace DragManager { // drag a document and drop it (or make an alias/copy on drop) export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { const addAudioTag = (dropDoc: any) => { - !dropDoc.creationDate && (dropDoc.creationDate = new DateField); + dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline")); return dropDoc; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 7ab650dc9..439f2d85f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -261,7 +261,6 @@ export class VideoBox extends DocAnnotatableComponent {"" + Math.round(curTime)} diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index f872bdf94..08a374fa9 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -206,6 +206,7 @@ export default class UploadManager extends ApiManager { { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" }, { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }, { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" }, + { resizer: sharp().resize(1200, undefined, { withoutEnlargement: true }), suffix: "_o" }, // bcz: this should just be the original image, not a resized version ]; let isImage = false; if (pngs.includes(ext)) { @@ -224,6 +225,7 @@ export default class UploadManager extends ApiManager { const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext); createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path)); }); + } res.send(clientPathToFile(Directory.images, filename + ext)); }); -- cgit v1.2.3-70-g09d2 From 288e4d24b61d281819b7f0b5bb697edbcc9ed173 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 5 Mar 2020 10:34:20 -0500 Subject: masonry view fix to add notes to the top. fixed uploadURI to use image resizer better --- .../collections/CollectionMasonryViewFieldRow.tsx | 24 +++++++++++++--------- src/server/ApiManagers/UploadManager.ts | 7 +++---- 2 files changed, 17 insertions(+), 14 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 6ebd3194d..af3e18a4b 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -8,7 +8,7 @@ import Measure from "react-measure"; import { Doc } from "../../../new_fields/Doc"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { StrCast } from "../../../new_fields/Types"; +import { StrCast, NumCast } from "../../../new_fields/Types"; import { numberRange } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; @@ -139,9 +139,10 @@ export class CollectionMasonryViewFieldRow extends React.Component { this._createAliasSelected = false; const key = StrCast(this.props.parent.props.Document._pivotField); - const newDoc = Docs.Create.TextDocument("", { _height: 18, _width: 200, title: value }); + const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value }); newDoc[key] = this.getValue(this.props.heading); - return this.props.parent.props.addDocument(newDoc); + const docs = this.props.parent.childDocList; + return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument(newDoc); } deleteRow = undoBatch(action(() => { @@ -274,6 +275,15 @@ export class CollectionMasonryViewFieldRow extends React.Component + {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? +
+ +
: null + }
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? -
- -
: null - }
; } @@ -317,7 +321,7 @@ export class CollectionMasonryViewFieldRow extends React.Component + style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey" }}> {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 08a374fa9..50a759c9d 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -7,7 +7,7 @@ import { extname, basename, dirname } from 'path'; import { createReadStream, createWriteStream, unlink } from "fs"; import { publicDirectory, filesDirectory } from ".."; import { Database } from "../database"; -import { DashUploadUtils } from "../DashUploadUtils"; +import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import * as sharp from 'sharp'; import { AcceptibleMedia, Upload } from "../SharedMediaTypes"; import { normalize } from "path"; @@ -199,14 +199,13 @@ export default class UploadManager extends ApiManager { res.status(401).send("incorrect parameters specified"); return; } - return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, filename)).then((savedName: string) => { + return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, SizeSuffix.Original))).then((savedName: string) => { const ext = extname(savedName).toLowerCase(); const { pngs, jpgs } = AcceptibleMedia; const resizers = [ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" }, { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }, { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" }, - { resizer: sharp().resize(1200, undefined, { withoutEnlargement: true }), suffix: "_o" }, // bcz: this should just be the original image, not a resized version ]; let isImage = false; if (pngs.includes(ext)) { @@ -225,7 +224,7 @@ export default class UploadManager extends ApiManager { const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext); createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path)); }); - + } res.send(clientPathToFile(Directory.images, filename + ext)); }); -- cgit v1.2.3-70-g09d2 From 6dd6d455a8a8bf8235b753591acd7761ad6cd91f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 8 Mar 2020 14:58:35 -0400 Subject: removed recommender from branch --- src/client/ClientRecommender.tsx | 8 +- src/client/apis/IBM_Recommender.ts | 66 ++++---- src/server/ApiManagers/UtilManager.ts | 44 +++--- src/server/Recommender.ts | 274 +++++++++++++++++----------------- 4 files changed, 196 insertions(+), 196 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index cb1674943..0e67a6e57 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -5,10 +5,10 @@ import { CognitiveServices, Confidence, Tag, Service } from "./cognitive_service import React = require("react"); import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; -var assert = require('assert'); -var sw = require('stopword'); -var FeedParser = require('feedparser'); -var https = require('https'); +// var assert = require('assert'); +// var sw = require('stopword'); +// var FeedParser = require('feedparser'); +// var https = require('https'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; import { RichTextField } from "../new_fields/RichTextField"; diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts index da6257f28..4e1c541c8 100644 --- a/src/client/apis/IBM_Recommender.ts +++ b/src/client/apis/IBM_Recommender.ts @@ -1,40 +1,40 @@ -import { Opt } from "../../new_fields/Doc"; +// import { Opt } from "../../new_fields/Doc"; -const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'); -const { IamAuthenticator } = require('ibm-watson/auth'); +// const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'); +// const { IamAuthenticator } = require('ibm-watson/auth'); -export namespace IBM_Recommender { +// export namespace IBM_Recommender { - // pass to IBM account is Browngfx1 +// // pass to IBM account is Browngfx1 - const naturalLanguageUnderstanding = new NaturalLanguageUnderstandingV1({ - version: '2019-07-12', - authenticator: new IamAuthenticator({ - apikey: 'tLiYwbRim3CnBcCO4phubpf-zEiGcub1uh0V-sD9OKhw', - }), - url: 'https://gateway-wdc.watsonplatform.net/natural-language-understanding/api' - }); +// const naturalLanguageUnderstanding = new NaturalLanguageUnderstandingV1({ +// version: '2019-07-12', +// authenticator: new IamAuthenticator({ +// apikey: 'tLiYwbRim3CnBcCO4phubpf-zEiGcub1uh0V-sD9OKhw', +// }), +// url: 'https://gateway-wdc.watsonplatform.net/natural-language-understanding/api' +// }); - const analyzeParams = { - 'text': 'this is a test of the keyword extraction feature I am integrating into the program', - 'features': { - 'keywords': { - 'sentiment': true, - 'emotion': true, - 'limit': 3 - }, - } - }; +// const analyzeParams = { +// 'text': 'this is a test of the keyword extraction feature I am integrating into the program', +// 'features': { +// 'keywords': { +// 'sentiment': true, +// 'emotion': true, +// 'limit': 3 +// }, +// } +// }; - export const analyze = async (_parameters: any): Promise> => { - try { - const response = await naturalLanguageUnderstanding.analyze(_parameters); - console.log(response); - return (JSON.stringify(response, null, 2)); - } catch (err) { - console.log('error: ', err); - return undefined; - } - }; +// export const analyze = async (_parameters: any): Promise> => { +// try { +// const response = await naturalLanguageUnderstanding.analyze(_parameters); +// console.log(response); +// return (JSON.stringify(response, null, 2)); +// } catch (err) { +// console.log('error: ', err); +// return undefined; +// } +// }; -} \ No newline at end of file +// } \ No newline at end of file diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts index d18529cf2..ad8119bf4 100644 --- a/src/server/ApiManagers/UtilManager.ts +++ b/src/server/ApiManagers/UtilManager.ts @@ -3,11 +3,11 @@ import { Method } from "../RouteManager"; import { exec } from 'child_process'; import RouteSubscriber from "../RouteSubscriber"; import { red } from "colors"; -import { IBM_Recommender } from "../../client/apis/IBM_Recommender"; -import { Recommender } from "../Recommender"; +// import { IBM_Recommender } from "../../client/apis/IBM_Recommender"; +// import { Recommender } from "../Recommender"; -const recommender = new Recommender(); -recommender.testModel(); +// const recommender = new Recommender(); +// recommender.testModel(); import executeImport from "../../scraping/buxton/final/BuxtonImporter"; export default class UtilManager extends ApiManager { @@ -27,25 +27,25 @@ export default class UtilManager extends ApiManager { } }); - register({ - method: Method.POST, - subscription: "/IBMAnalysis", - secureHandler: async ({ req, res }) => res.send(await IBM_Recommender.analyze(req.body)) - }); + // register({ + // method: Method.POST, + // subscription: "/IBMAnalysis", + // secureHandler: async ({ req, res }) => res.send(await IBM_Recommender.analyze(req.body)) + // }); - register({ - method: Method.POST, - subscription: "/recommender", - secureHandler: async ({ req, res }) => { - const keyphrases = req.body.keyphrases; - const wordvecs = await recommender.vectorize(keyphrases); - let embedding: Float32Array = new Float32Array(); - if (wordvecs && wordvecs.dataSync()) { - embedding = wordvecs.dataSync() as Float32Array; - } - res.send(embedding); - } - }); + // register({ + // method: Method.POST, + // subscription: "/recommender", + // secureHandler: async ({ req, res }) => { + // const keyphrases = req.body.keyphrases; + // const wordvecs = await recommender.vectorize(keyphrases); + // let embedding: Float32Array = new Float32Array(); + // if (wordvecs && wordvecs.dataSync()) { + // embedding = wordvecs.dataSync() as Float32Array; + // } + // res.send(embedding); + // } + // }); register({ diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index 1d2cb3858..aacdb4053 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -1,137 +1,137 @@ -//import { Doc } from "../new_fields/Doc"; -//import { StrCast } from "../new_fields/Types"; -//import { List } from "../new_fields/List"; -//import { CognitiveServices } from "../client/cognitive_services/CognitiveServices"; - -// var w2v = require('word2vec'); -var assert = require('assert'); -var arxivapi = require('arxiv-api-node'); -import requestPromise = require("request-promise"); -import * as use from '@tensorflow-models/universal-sentence-encoder'; -import { Tensor } from "@tensorflow/tfjs-core/dist/tensor"; -require('@tensorflow/tfjs-node'); - -//http://gnuwin32.sourceforge.net/packages/make.htm - -export class Recommender { - - private _model: any; - static Instance: Recommender; - private dimension: number = 0; - private choice: string = ""; // Tensorflow or Word2Vec - - constructor() { - console.log("creating recommender..."); - Recommender.Instance = this; - } - - /*** - * Loads pre-trained model from TF - */ - - public async loadTFModel() { - let self = this; - return new Promise(res => { - use.load().then(model => { - self.choice = "TF"; - self._model = model; - self.dimension = 512; - res(model); - }); - } - - ); - } - - /*** - * Loads pre-trained model from word2vec - */ - - // private loadModel(): Promise { - // let self = this; - // return new Promise(res => { - // w2v.loadModel("./node_modules/word2vec/examples/fixtures/vectors.txt", function (err: any, model: any) { - // self.choice = "WV"; - // self._model = model; - // self.dimension = model.size; - // res(model); - // }); - // }); - // } - - /*** - * Testing - */ - - public async testModel() { - if (!this._model) { - await this.loadTFModel(); - } - if (this._model) { - if (this.choice === "WV") { - let similarity = this._model.similarity('father', 'mother'); - console.log(similarity); - } - else if (this.choice === "TF") { - const model = this._model as use.UniversalSentenceEncoder; - // Embed an array of sentences. - const sentences = [ - 'Hello.', - 'How are you?' - ]; - const embeddings = await this.vectorize(sentences); - if (embeddings) embeddings.print(true /*verbose*/); - // model.embed(sentences).then(embeddings => { - // // `embeddings` is a 2D tensor consisting of the 512-dimensional embeddings for each sentence. - // // So in this example `embeddings` has the shape [2, 512]. - // embeddings.print(true /* verbose */); - // }); - } - } - else { - console.log("model not found :("); - } - } - - /*** - * Uses model to convert words to vectors - */ - - public async vectorize(text: string[]): Promise { - if (!this._model) { - await this.loadTFModel(); - } - if (this._model) { - if (this.choice === "WV") { - let word_vecs = this._model.getVectors(text); - return word_vecs; - } - else if (this.choice === "TF") { - const model = this._model as use.UniversalSentenceEncoder; - return new Promise(res => { - model.embed(text).then(embeddings => { - res(embeddings); - }); - }); - - } - } - } - - // public async trainModel() { - // console.log("phrasing..."); - // w2v.word2vec("./node_modules/word2vec/examples/eng_news-typical_2016_1M-sentences.txt", './node_modules/word2vec/examples/my_phrases.txt', { - // cbow: 1, - // size: 200, - // window: 8, - // negative: 25, - // hs: 0, - // sample: 1e-4, - // threads: 20, - // iter: 200, - // minCount: 2 - // }); - // console.log("phrased!!!"); - // } - -} +// //import { Doc } from "../new_fields/Doc"; +// //import { StrCast } from "../new_fields/Types"; +// //import { List } from "../new_fields/List"; +// //import { CognitiveServices } from "../client/cognitive_services/CognitiveServices"; + +// // var w2v = require('word2vec'); +// var assert = require('assert'); +// var arxivapi = require('arxiv-api-node'); +// import requestPromise = require("request-promise"); +// import * as use from '@tensorflow-models/universal-sentence-encoder'; +// import { Tensor } from "@tensorflow/tfjs-core/dist/tensor"; +// require('@tensorflow/tfjs-node'); + +// //http://gnuwin32.sourceforge.net/packages/make.htm + +// export class Recommender { + +// private _model: any; +// static Instance: Recommender; +// private dimension: number = 0; +// private choice: string = ""; // Tensorflow or Word2Vec + +// constructor() { +// console.log("creating recommender..."); +// Recommender.Instance = this; +// } + +// /*** +// * Loads pre-trained model from TF +// */ + +// public async loadTFModel() { +// let self = this; +// return new Promise(res => { +// use.load().then(model => { +// self.choice = "TF"; +// self._model = model; +// self.dimension = 512; +// res(model); +// }); +// } + +// ); +// } + +// /*** +// * Loads pre-trained model from word2vec +// */ + +// // private loadModel(): Promise { +// // let self = this; +// // return new Promise(res => { +// // w2v.loadModel("./node_modules/word2vec/examples/fixtures/vectors.txt", function (err: any, model: any) { +// // self.choice = "WV"; +// // self._model = model; +// // self.dimension = model.size; +// // res(model); +// // }); +// // }); +// // } + +// /*** +// * Testing +// */ + +// public async testModel() { +// if (!this._model) { +// await this.loadTFModel(); +// } +// if (this._model) { +// if (this.choice === "WV") { +// let similarity = this._model.similarity('father', 'mother'); +// console.log(similarity); +// } +// else if (this.choice === "TF") { +// const model = this._model as use.UniversalSentenceEncoder; +// // Embed an array of sentences. +// const sentences = [ +// 'Hello.', +// 'How are you?' +// ]; +// const embeddings = await this.vectorize(sentences); +// if (embeddings) embeddings.print(true /*verbose*/); +// // model.embed(sentences).then(embeddings => { +// // // `embeddings` is a 2D tensor consisting of the 512-dimensional embeddings for each sentence. +// // // So in this example `embeddings` has the shape [2, 512]. +// // embeddings.print(true /* verbose */); +// // }); +// } +// } +// else { +// console.log("model not found :("); +// } +// } + +// /*** +// * Uses model to convert words to vectors +// */ + +// public async vectorize(text: string[]): Promise { +// if (!this._model) { +// await this.loadTFModel(); +// } +// if (this._model) { +// if (this.choice === "WV") { +// let word_vecs = this._model.getVectors(text); +// return word_vecs; +// } +// else if (this.choice === "TF") { +// const model = this._model as use.UniversalSentenceEncoder; +// return new Promise(res => { +// model.embed(text).then(embeddings => { +// res(embeddings); +// }); +// }); + +// } +// } +// } + +// // public async trainModel() { +// // console.log("phrasing..."); +// // w2v.word2vec("./node_modules/word2vec/examples/eng_news-typical_2016_1M-sentences.txt", './node_modules/word2vec/examples/my_phrases.txt', { +// // cbow: 1, +// // size: 200, +// // window: 8, +// // negative: 25, +// // hs: 0, +// // sample: 1e-4, +// // threads: 20, +// // iter: 200, +// // minCount: 2 +// // }); +// // console.log("phrased!!!"); +// // } + +// } -- cgit v1.2.3-70-g09d2 From e9a16afa46af3ecec0bd7b58f9ca13b85d62a860 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 17 Mar 2020 01:12:21 -0400 Subject: fixed up route handling for relative paths. changed search to dashsearch for server request. --- src/client/util/SearchUtil.ts | 6 +++--- src/server/ApiManagers/SearchManager.ts | 2 +- src/server/RouteManager.ts | 36 ++++++++++++++++++++++----------- src/server/index.ts | 3 ++- 4 files changed, 30 insertions(+), 17 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 2d9c807dd..b597f1e07 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -34,7 +34,7 @@ export namespace SearchUtil { export function Search(query: string, returnDocs: false, options?: SearchParams): Promise; export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { query = query || "*"; //If we just have a filter query, search for * as the query - const rpquery = Utils.prepend("/search"); + const rpquery = Utils.prepend("/dashsearch"); const gotten = await rp.get(rpquery, { qs: { ...options, q: query } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { @@ -52,7 +52,7 @@ export namespace SearchUtil { const newLines: string[][] = []; await Promise.all(fileids.map(async (tr: string, i: number) => { const docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query - const docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } })); + const docResult = JSON.parse(await rp.get(Utils.prepend("/dashsearch"), { qs: { ...options, q: docQuery } })); newIds.push(...docResult.ids); newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i])); })); @@ -121,7 +121,7 @@ export namespace SearchUtil { export async function GetAllDocs() { const query = "*"; - const response = await rp.get(Utils.prepend('/search'), { + const response = await rp.get(Utils.prepend('/dashsearch'), { qs: { start: 0, rows: 10000, q: query }, diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index be17c3105..5f7d1cf6d 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -61,7 +61,7 @@ export class SearchManager extends ApiManager { register({ method: Method.GET, - subscription: "/search", + subscription: "/dashsearch", secureHandler: async ({ req, res }) => { const solrQuery: any = {}; ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index d8265582e..c88f3bb51 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -79,6 +79,7 @@ export default class RouteManager { } } + static routes: string[] = []; /** * * @param initializer @@ -86,11 +87,18 @@ export default class RouteManager { addSupervisedRoute = (initializer: RouteInitializer): void => { const { method, subscription, secureHandler, publicHandler, errorHandler } = initializer; + typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription); + initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root); + initializer.subscription instanceof Array && initializer.subscription.map(sub => { + typeof (sub) === "string" && RouteManager.routes.push(sub); + sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root); + }); const isRelease = this._isRelease; let redirected = ""; const supervised = async (req: Request, res: Response) => { let { user } = req; const { originalUrl: target } = req; + console.log("TARGET: " + target); if (process.env.DB === "MEM" && !user) { user = { id: "guest", email: "", userDocumentId: "guestDocId" }; } @@ -126,26 +134,30 @@ export default class RouteManager { const original = url.replace(start, ""); const theurl = original.match(/http[s]?:\/\/[^\/]*/)![0]; const newdirect = start + encodeURIComponent(theurl + target); - if (newdirect !== redirected) { - redirected = newdirect; - console.log("redirect relative path: " + (theurl + target)); - res.redirect(redirected); - } + console.log("REDIRECT: " + (theurl + target)); + res.redirect(newdirect); } - else { - if (target.startsWith("/doc/")) { - !res.headersSent && setTimeout(() => { + else if (!res.headersSent) { + const which2 = RouteManager.routes.findIndex(r => (r !== "/" || r === target) && target.startsWith(r)); + const which = Array.from(registered.keys()).findIndex(r => (r !== "/" || r === target) && target.startsWith(r)); + console.log("WHICH = " + (which === -1 ? "" : Array.from(registered.keys())[which])); + if (which !== -1) { + setTimeout(() => { + console.log("handled:" + target); if (!res.headersSent) { - res.redirect("/login"); console.log(red(`Initiating fallback for ${target}. Please remove dangling promise from route handler`)); const warning = `request to ${target} fell through - this is a fallback response`; res.send({ warning }); } }, 1000); - } else { - const warning = `request to ${target} fell through - this is a fallback response`; - res.send({ warning }); } + else { + console.log("unhandled:" + target); + res.end(); + } + } else { + console.log("pre-sent:" + target); + res.end(); } }; const subscribe = (subscriber: RouteSubscriber | string) => { diff --git a/src/server/index.ts b/src/server/index.ts index c4c05157a..def36e922 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -117,7 +117,8 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: addSupervisedRoute({ method: Method.GET, subscription: "/*", - secureHandler: ({ res }) => { } + secureHandler: ({ res }) => { + } }); logRegistrationOutcome(); -- cgit v1.2.3-70-g09d2 From b0e121a9d767ca30e5b6732e3aeabbda0e0a7e97 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 18 Mar 2020 17:48:02 -0700 Subject: finalized cleanup --- src/server/ApiManagers/UploadManager.ts | 1 - src/server/DashUploadUtils.ts | 21 +++++++++++++-------- src/server/SharedMediaTypes.ts | 2 +- src/server/index.ts | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 42e33ece0..98f029c7d 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -43,7 +43,6 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: "/uploadFormData", secureHandler: async ({ req, res }) => { - console.log("/upload register"); const form = new formidable.IncomingForm(); form.uploadDir = pathToDirectory(Directory.parsed_files); form.keepExtensions = true; diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index dd99ff746..2af816df8 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -60,7 +60,7 @@ export namespace DashUploadUtils { const types = type.split("/"); const category = types[0]; - const format = `.${types[1]}`; + let format = `.${types[1]}`; switch (category) { case "image": @@ -77,8 +77,12 @@ export namespace DashUploadUtils { return UploadPdf(file); } case "audio": + const components = format.split(";"); + if (components.length > 1) { + format = components[0]; + } if (audioFormats.includes(format)) { - return MoveParsedFile(file, Directory.audio); + return UploadAudio(file, format); } } @@ -86,12 +90,6 @@ export namespace DashUploadUtils { return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) }; } - async function UploadAudio(file: File) { - const { path: sourcePath } = file; - - return MoveParsedFile(file, Directory.audio); - } - async function UploadPdf(file: File) { const { path: sourcePath } = file; const dataBuffer = readFileSync(sourcePath); @@ -105,6 +103,13 @@ export namespace DashUploadUtils { return MoveParsedFile(file, Directory.pdfs); } + const manualSuffixes = [".webm"]; + + async function UploadAudio(file: File, format: string) { + const suffix = manualSuffixes.includes(format) ? format : undefined; + return MoveParsedFile(file, Directory.audio, suffix); + } + /** * Uploads an image specified by the @param source to Dash's /public/files/ * directory, and returns information generated during that upload diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 3d3683912..2495123b7 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -10,7 +10,7 @@ export namespace AcceptibleMedia { export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; export const videoFormats = [".mov", ".mp4"]; export const applicationFormats = [".pdf"]; - export const audioFormats = [".wav", ".mp3", ".flac", ".au", ".aiff", ".m4a", ".webm;codecs=opus"]; + export const audioFormats = [".wav", ".mp3", ".flac", ".au", ".aiff", ".m4a", ".webm"]; } export namespace Upload { diff --git a/src/server/index.ts b/src/server/index.ts index 10205314a..f4446352f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -25,6 +25,7 @@ import { yellow } from "colors"; import { DashSessionAgent } from "./DashSession/DashSessionAgent"; import SessionManager from "./ApiManagers/SessionManager"; import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent"; +import { Utils } from "../Utils"; export const onWindows = process.platform === "win32"; export let sessionAgent: AppliedSessionAgent; @@ -37,6 +38,7 @@ export const filesDirectory = path.resolve(publicDirectory, "files"); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { + // Utils.TraceConsoleLog(); await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); -- cgit v1.2.3-70-g09d2