From 36ad83493d2bd58dc6fe62df6002789ccc1b06a1 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 10 Nov 2019 14:56:58 -0500
Subject: no more RouteStore

---
 src/client/views/MainView.tsx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 39585113b..0c5a1003b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -12,7 +12,6 @@ import { List } from '../../new_fields/List';
 import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { RouteStore } from '../../server/RouteStore';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from '../../Utils';
 import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
@@ -74,7 +73,7 @@ export class MainView extends React.Component {
         this._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
         // causes errors to be generated when modifying an observable outside of an action
         configure({ enforceActions: "observed" });
-        if (window.location.pathname !== RouteStore.home) {
+        if (window.location.pathname !== "/home") {
             let pathname = window.location.pathname.substr(1).split("/");
             if (pathname.length > 1) {
                 let type = pathname[0];
@@ -395,7 +394,7 @@ export class MainView extends React.Component {
                     zoomToScale={emptyFunction}
                     getScale={returnOne}>
                 </DocumentView>
-                <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>
+                <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                     {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                 </button>
             </div></div>;
-- 
cgit v1.2.3-70-g09d2


From e324248724a130a84b459a072dc846f500f8d9b0 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 23 Nov 2019 18:19:27 -0500
Subject: heartbeats

---
 src/client/DocServer.ts                     |  2 ++
 src/client/util/ClientDiagnostics.ts        | 29 +++++++++++++++++++++++++++++
 src/client/views/Main.tsx                   |  2 ++
 src/client/views/MainView.tsx               |  6 ++++--
 src/server/ApiManagers/DiagnosticManager.ts | 26 ++++++++++++++++++++++++++
 src/server/ApiManagers/UserManager.ts       |  2 +-
 src/server/Initialization.ts                | 13 +++++++------
 src/server/index.ts                         |  2 ++
 8 files changed, 73 insertions(+), 9 deletions(-)
 create mode 100644 src/client/util/ClientDiagnostics.ts
 create mode 100644 src/server/ApiManagers/DiagnosticManager.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 2cec1046b..14479694c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -64,6 +64,8 @@ export namespace DocServer {
         }
     }
 
+    let connection_error = false;
+
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
         _cache = {};
         GUID = identifier;
diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts
new file mode 100644
index 000000000..e454cdecb
--- /dev/null
+++ b/src/client/util/ClientDiagnostics.ts
@@ -0,0 +1,29 @@
+import { observable, runInAction } from "mobx";
+import { MainView } from "../views/MainView";
+
+export namespace ClientDiagnostics {
+
+    export function start() {
+
+        let serverPolls = 0;
+        const serverHandle = setInterval(async () => {
+            if (++serverPolls === 20) {
+                alert("Your connection to the server has been terminated.");
+                clearInterval(serverHandle);
+            }
+            await fetch("/serverHeartbeat");
+            serverPolls--;
+        }, 100);
+
+
+        const solrHandle = setInterval(async () => {
+            const response = await fetch("/solrHeartbeat");
+            if (!response) {
+                alert("Looks like SOLR is not running on your machine.");
+                clearInterval(solrHandle);
+            }
+        }, 100);
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b21eb9c8f..dec4a24e4 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,10 +5,12 @@ import * as ReactDOM from 'react-dom';
 import * as React from 'react';
 import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
+import { ClientDiagnostics } from "../util/ClientDiagnostics";
 
 AssignAllExtensions();
 
 (async () => {
+    ClientDiagnostics.start();
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
     await Docs.Prototypes.initialize();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 031926604..d352ad776 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ import {
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction, autorun } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
@@ -414,7 +414,8 @@ export class MainView extends React.Component {
                 <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                     {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                 </button>
-            </div></div>;
+            </div>
+        </div>;
     }
 
     @computed get mainContent() {
@@ -422,6 +423,7 @@ export class MainView extends React.Component {
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" >
                 <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger}>
+                    <div>HEY!</div>
                     <div className="mainView-libraryHandle"
                         style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
                         onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
diff --git a/src/server/ApiManagers/DiagnosticManager.ts b/src/server/ApiManagers/DiagnosticManager.ts
new file mode 100644
index 000000000..b775167b6
--- /dev/null
+++ b/src/server/ApiManagers/DiagnosticManager.ts
@@ -0,0 +1,26 @@
+import ApiManager, { Registration } from "./ApiManager";
+import { Method } from "../RouteManager";
+import request = require('request-promise');
+
+export default class DiagnosticManager extends ApiManager {
+
+    protected initialize(register: Registration): void {
+
+        register({
+            method: Method.GET,
+            subscription: "/serverHeartbeat",
+            onValidation: ({ res }) => res.send(true)
+        });
+
+        register({
+            method: Method.GET,
+            subscription: "/solrHeartbeat",
+            onValidation: async ({ res }) => {
+                const response = await request("http://localhost:8983");
+                res.send(response !== undefined);
+            }
+        });
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 4ee5a2b85..8edeab16d 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -56,7 +56,7 @@ export default class UserManager extends ApiManager {
                 const process = (target: { user: string, duration: number }[]) => {
                     const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration;
                     const sorted = target.sort(comparator);
-                    return sorted.map(({ user, duration }) => `${user} (last active ${msToTime(duration)} ago)`);
+                    return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`);
                 };
 
                 res.render("user_activity.pug", {
diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts
index 7fad5556d..76acb4363 100644
--- a/src/server/Initialization.ts
+++ b/src/server/Initialization.ts
@@ -20,7 +20,6 @@ import * as request from 'request';
 import RouteSubscriber from './RouteSubscriber';
 import { publicDirectory } from '.';
 import { ConsoleColors, logPort } from './ActionUtilities';
-import { WebSocket } from './Websocket/Websocket';
 import { timeMap } from './ApiManagers/UserManager';
 
 /* RouteSetter is a wrapper around the server that prevents the server
@@ -38,11 +37,13 @@ export default async function InitializeServer(options: InitializationOptions) {
     server.use(express.static(publicDirectory));
     server.use("/images", express.static(publicDirectory));
 
-    server.use("*", (req, _res, next) => {
-        const userEmail = req.user?.email;
-        console.log(ConsoleColors.Cyan, req.originalUrl, userEmail ?? "<user logged out>");
-        if (userEmail) {
-            timeMap[userEmail] = Date.now();
+    server.use("*", ({ user, originalUrl }, _res, next) => {
+        if (!originalUrl.includes("Heartbeat")) {
+            const userEmail = user?.email;
+            console.log(ConsoleColors.Cyan, originalUrl, userEmail ?? "<user logged out>");
+            if (userEmail) {
+                timeMap[userEmail] = Date.now();
+            }
         }
         next();
     });
diff --git a/src/server/index.ts b/src/server/index.ts
index 9c48aca45..d02a6005e 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -21,6 +21,7 @@ import UploadManager from "./ApiManagers/UploadManager";
 import { log_execution } from "./ActionUtilities";
 import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
 import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
+import DiagnosticManager from "./ApiManagers/DiagnosticManager";
 
 export const publicDirectory = path.resolve(__dirname, "public");
 export const filesDirectory = path.resolve(publicDirectory, "files") + "/";
@@ -55,6 +56,7 @@ function routeSetter({ isRelease, addSupervisedRoute }: RouteManager) {
         new UserManager(),
         new UploadManager(),
         new DownloadManager(),
+        new DiagnosticManager(),
         new SearchManager(),
         new PDFManager(),
         new DeleteManager(),
-- 
cgit v1.2.3-70-g09d2


From 780240515a06d9d71a4b58a2559d8661478a560f Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 26 Nov 2019 20:34:33 -0500
Subject: cleanup after looking at changed files

---
 src/client/DocServer.ts                       | 2 --
 src/client/util/ClientDiagnostics.ts          | 4 ++--
 src/client/util/Import & Export/ImageUtils.ts | 2 +-
 src/client/views/MainView.tsx                 | 1 -
 src/client/views/search/SearchBox.tsx         | 2 +-
 src/server/ApiManagers/UserManager.ts         | 1 -
 src/server/Initialization.ts                  | 1 -
 src/server/RouteManager.ts                    | 2 ++
 8 files changed, 6 insertions(+), 9 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 14479694c..2cec1046b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -64,8 +64,6 @@ export namespace DocServer {
         }
     }
 
-    let connection_error = false;
-
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
         _cache = {};
         GUID = identifier;
diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts
index 6f82a47db..24f196252 100644
--- a/src/client/util/ClientDiagnostics.ts
+++ b/src/client/util/ClientDiagnostics.ts
@@ -13,7 +13,7 @@ export namespace ClientDiagnostics {
             }
             await fetch("/serverHeartbeat");
             serverPolls--;
-        }, 100);
+        }, 1000 * 15);
 
 
         let executed = false;
@@ -24,7 +24,7 @@ export namespace ClientDiagnostics {
                 executed = true;
                 clearInterval(solrHandle);
             }
-        }, 100);
+        }, 1000 * 15);
 
     }
 
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index ca80f3bca..6a9486f83 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -22,7 +22,7 @@ export namespace ImageUtils {
 
     export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
         const a = document.createElement("a");
-        a.href = Utils.prepend(`imageHierarchyExport/${collection[Id]}`);
+        a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
         a.download = `Dash Export [${StrCast(collection.title)}].zip`;
         a.click();
     };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f59da3cde..5231075a1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -423,7 +423,6 @@ export class MainView extends React.Component {
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" >
                 <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger}>
-                    <div>HEY!</div>
                     <div className="mainView-libraryHandle"
                         style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
                         onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 1337d3f95..ff35542ed 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -89,7 +89,7 @@ export class SearchBox extends React.Component {
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = Utils.prepend("uploadURI");
+            let posting = Utils.prepend("/uploadURI");
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 8edeab16d..0f7d14320 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -9,7 +9,6 @@ interface ActivityUnit {
     duration: number;
 }
 
-
 export default class UserManager extends ApiManager {
 
     protected initialize(register: Registration): void {
diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts
index 76acb4363..8b633a7cd 100644
--- a/src/server/Initialization.ts
+++ b/src/server/Initialization.ts
@@ -40,7 +40,6 @@ export default async function InitializeServer(options: InitializationOptions) {
     server.use("*", ({ user, originalUrl }, _res, next) => {
         if (!originalUrl.includes("Heartbeat")) {
             const userEmail = user?.email;
-            console.log(ConsoleColors.Cyan, originalUrl, userEmail ?? "<user logged out>");
             if (userEmail) {
                 timeMap[userEmail] = Date.now();
             }
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 3aae5734a..3a20d5af5 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,6 +1,7 @@
 import RouteSubscriber from "./RouteSubscriber";
 import { DashUserModel } from "./authentication/models/user_model";
 import * as express from 'express';
+import { ConsoleColors } from "./ActionUtilities";
 
 export enum Method {
     GET,
@@ -52,6 +53,7 @@ export default class RouteManager {
                 try {
                     await toExecute(args);
                 } catch (e) {
+                    console.log(ConsoleColors.Red, target, user?.email ?? "<user logged out>");
                     if (onError) {
                         onError({ ...core, error: e });
                     } else {
-- 
cgit v1.2.3-70-g09d2


From 77ee66de66a411f79bbbc036d379d09be38d172f Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 2 Dec 2019 12:12:58 -0500
Subject: further cleanup

---
 src/Utils.ts                                       |   2 -
 src/client/util/ClientDiagnostics.ts               |  18 +--
 .../util/Import & Export/DirectoryImportBox.tsx    |   3 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   6 +-
 src/server/ApiManagers/DeleteManager.ts            |   1 -
 src/server/ApiManagers/GeneralGoogleManager.ts     |  15 +--
 src/server/ApiManagers/PDFManager.ts               | 134 ++++++++++-----------
 src/server/ApiManagers/UploadManager.ts            |  43 ++++---
 src/server/DashUploadUtils.ts                      |  18 ++-
 src/server/RouteManager.ts                         |  14 +++
 .../authentication/models/current_user_utils.ts    |   3 +-
 src/server/credentials/test.json                   |  14 ---
 13 files changed, 125 insertions(+), 148 deletions(-)
 delete mode 100644 src/server/credentials/test.json

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index b60e9e023..2543743a4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,8 +2,6 @@ import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
-import { EventEmitter } from 'events';
-import { ConsoleColors } from './server/ActionUtilities';
 
 export namespace Utils {
 
diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts
index 7eef935fd..0a213aa1c 100644
--- a/src/client/util/ClientDiagnostics.ts
+++ b/src/client/util/ClientDiagnostics.ts
@@ -12,18 +12,22 @@ export namespace ClientDiagnostics {
             serverPolls--;
         }, 1000 * 15);
 
-
         let executed = false;
-        const handle = async () => {
+        let solrHandle: NodeJS.Timeout | undefined;
+        const handler = async () => {
             const response = await fetch("/solrHeartbeat");
             if (!(await response.json()).running) {
-                !executed && alert("Looks like SOLR is not running on your machine.");
-                executed = true;
-                clearInterval(solrHandle);
+                if (!executed) {
+                    alert("Looks like SOLR is not running on your machine.");
+                    executed = true;
+                    solrHandle && clearInterval(solrHandle);
+                }
             }
         };
-        await handle();
-        const solrHandle = setInterval(handle, 1000 * 15);
+        await handler();
+        if (!executed) {
+            solrHandle = setInterval(handler, 1000 * 15);
+        }
 
     }
 
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index b5e806a97..104d9e099 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -106,7 +106,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
         runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`);
 
-        const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync<any>(async (batch, collector) => {
+        const batched = BatchedArray.from(validated, { batchSize: 15 });
+        const uploads = await batched.batchedMapAsync<any>(async (batch, collector) => {
             const formData = new FormData();
 
             batch.forEach(file => {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5231075a1..85dfd8be2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ import {
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction, runInAction, autorun } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a1bd1527e..a1ae77fef 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
@@ -279,9 +279,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     formData.append('file', file);
                     let dropFileName = file ? file.name : "-empty-";
                     promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
-                        results.map(action((file: any) => {
+                        results.map(action(({ clientAccessPath }: any) => {
                             let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName };
-                            let pathname = Utils.prepend(file.clientAccessPath);
+                            let pathname = Utils.prepend(clientAccessPath);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
                                 doc && (Doc.GetProto(doc).fileUpload = path.basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
                                 doc && this.props.addDocument(doc);
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index f58a28ce5..71818c673 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -56,7 +56,6 @@ export default class DeleteManager extends ApiManager {
             }
         });
 
-
     }
 
 }
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index 171912185..629684e0c 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -20,8 +20,7 @@ export default class GeneralGoogleManager extends ApiManager {
             method: Method.GET,
             subscription: "/readGoogleAccessToken",
             onValidation: async ({ user, res }) => {
-                const userId = user.id;
-                const token = await GoogleApiServerUtils.retrieveAccessToken(userId);
+                const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
                 if (!token) {
                     return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
                 }
@@ -37,18 +36,6 @@ export default class GeneralGoogleManager extends ApiManager {
             }
         });
 
-        register({
-            method: Method.GET,
-            subscription: "/deleteWithGoogleCredentials",
-            onValidation: async ({ res, isRelease }) => {
-                if (isRelease) {
-                    return _permission_denied(res, deletionPermissionError);
-                }
-                await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll();
-                res.redirect("/delete");
-            }
-        });
-
         register({
             method: Method.POST,
             subscription: new RouteSubscriber("/googleDocs").add("sector", "action"),
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index 4bd750aaf..151b48dd9 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -4,10 +4,11 @@ import RouteSubscriber from "../RouteSubscriber";
 import { exists, createReadStream, createWriteStream } from "fs";
 import * as Pdfjs from 'pdfjs-dist';
 import { createCanvas } from "canvas";
-const probe = require("probe-image-size");
+const imageSize = require("probe-image-size");
 import * as express from "express";
 import * as path from "path";
 import { Directory, serverPathToFile, clientPathToFile } from "./UploadManager";
+import { ConsoleColors } from "../ActionUtilities";
 
 export default class PDFManager extends ApiManager {
 
@@ -16,84 +17,77 @@ export default class PDFManager extends ApiManager {
         register({
             method: Method.GET,
             subscription: new RouteSubscriber("thumbnail").add("filename"),
-            onValidation: ({ req, res }) => {
-                let filename = req.params.filename;
-                let noExt = filename.substring(0, filename.length - ".png".length);
-                let pagenumber = parseInt(noExt.split('-')[1]);
-                return new Promise<void>(resolve => {
-                    const path = serverPathToFile(Directory.pdf_thumbnails, filename);
-                    exists(path, (exists: boolean) => {
-                        console.log(`${path} ${exists ? "exists" : "does not exist"}`);
-                        if (exists) {
-                            let input = createReadStream(path);
-                            probe(input, (err: any, { width, height }: any) => {
-                                if (err) {
-                                    console.log(err);
-                                    console.log(`error on ${filename}`);
-                                    return;
-                                }
-                                res.send({
-                                    path: clientPathToFile(Directory.pdf_thumbnails, filename),
-                                    width,
-                                    height
-                                });
-                            });
-                        }
-                        else {
-                            const name = filename.substring(0, filename.length - noExt.split('-')[1].length - ".PNG".length - 1) + ".pdf";
-                            LoadPage(serverPathToFile(Directory.pdfs, name), pagenumber, res);
-                        }
-                        resolve();
-                    });
-                });
-            }
+            onValidation: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res)
         });
 
-        function LoadPage(file: string, pageNumber: number, res: express.Response) {
-            console.log(file);
-            Pdfjs.getDocument(file).promise
-                .then((pdf: Pdfjs.PDFDocumentProxy) => {
-                    let factory = new NodeCanvasFactory();
-                    console.log(pageNumber);
-                    pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => {
-                        console.log("reading " + page);
-                        let viewport = page.getViewport(1 as any);
-                        let canvasAndContext = factory.create(viewport.width, viewport.height);
-                        let renderContext = {
-                            canvasContext: canvasAndContext.context,
-                            canvasFactory: factory,
-                            viewport
-                        };
-                        console.log("read " + pageNumber);
+    }
 
-                        page.render(renderContext).promise
-                            .then(() => {
-                                console.log("saving " + pageNumber);
-                                let stream = canvasAndContext.canvas.createPNGStream();
-                                let filenames = path.basename(file).split(".");
-                                const pngFile = serverPathToFile(Directory.pdf_thumbnails, `${filenames[0]}-${pageNumber}.png`);
-                                let out = createWriteStream(pngFile);
-                                stream.pipe(out);
-                                out.on("finish", () => {
-                                    console.log(`Success! Saved to ${pngFile}`);
-                                    res.send({
-                                        path: pngFile,
-                                        width: viewport.width,
-                                        height: viewport.height
-                                    });
-                                });
-                            }, (reason: string) => {
-                                console.error(reason + ` ${pageNumber}`);
-                            });
+}
+
+function getOrCreateThumbnail(thumbnailName: string, res: express.Response) {
+    const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length);
+    const pageString = noExtension.split('-')[1];
+    const pageNumber = parseInt(pageString);
+    return new Promise<void>(resolve => {
+        const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName);
+        exists(path, (exists: boolean) => {
+            if (exists) {
+                let existingThumbnail = createReadStream(path);
+                imageSize(existingThumbnail, (err: any, { width, height }: any) => {
+                    if (err) {
+                        console.log(ConsoleColors.Red, `In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`);
+                        console.log(err);
+                        return;
+                    }
+                    res.send({
+                        path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName),
+                        width,
+                        height
                     });
                 });
-        }
-
-    }
+            } else {
+                const offset = thumbnailName.length - pageString.length - 5;
+                const name = thumbnailName.substring(0, offset) + ".pdf";
+                const path = serverPathToFile(Directory.pdfs, name);
+                CreateThumbnail(path, pageNumber, res);
+            }
+            resolve();
+        });
+    });
+}
 
+async function CreateThumbnail(file: string, pageNumber: number, res: express.Response) {
+    const documentProxy = await Pdfjs.getDocument(file).promise;
+    const factory = new NodeCanvasFactory();
+    const page = await documentProxy.getPage(pageNumber);
+    const viewport = page.getViewport(1 as any);
+    const { canvas, context } = factory.create(viewport.width, viewport.height);
+    const renderContext = {
+        canvasContext: context,
+        canvasFactory: factory,
+        viewport
+    };
+    await page.render(renderContext).promise;
+    const pngStream = canvas.createPNGStream();
+    const filenames = path.basename(file).split(".");
+    const pngFile = serverPathToFile(Directory.pdf_thumbnails, `${filenames[0]}-${pageNumber}.png`);
+    const out = createWriteStream(pngFile);
+    pngStream.pipe(out);
+    out.on("finish", () => {
+        res.send({
+            path: pngFile,
+            width: viewport.width,
+            height: viewport.height
+        });
+    });
+    out.on("error", error => {
+        console.log(ConsoleColors.Red, `In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`);
+        console.log(error);
+    });
 }
 
 class NodeCanvasFactory {
+
     create = (width: number, height: number) => {
         var canvas = createCanvas(width, height);
         var context = canvas.getContext('2d');
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 2f76871a6..80ae0ad61 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -38,6 +38,27 @@ export default class UploadManager extends ApiManager {
 
     protected initialize(register: Registration): void {
 
+        register({
+            method: Method.POST,
+            subscription: "/upload",
+            onValidation: async ({ req, res }) => {
+                let form = new formidable.IncomingForm();
+                form.uploadDir = pathToDirectory(Directory.parsed_files);
+                form.keepExtensions = true;
+                return new Promise<void>(resolve => {
+                    form.parse(req, async (_err, _fields, files) => {
+                        let results: any[] = [];
+                        for (const key in files) {
+                            const result = await DashUploadUtils.upload(files[key]);
+                            result && results.push(result);
+                        }
+                        _success(res, results);
+                        resolve();
+                    });
+                });
+            }
+        });
+
         register({
             method: Method.POST,
             subscription: "/uploadDoc",
@@ -142,28 +163,6 @@ export default class UploadManager extends ApiManager {
             }
         });
 
-
-        register({
-            method: Method.POST,
-            subscription: "/upload",
-            onValidation: async ({ req, res }) => {
-                let form = new formidable.IncomingForm();
-                form.uploadDir = pathToDirectory(Directory.parsed_files);
-                form.keepExtensions = true;
-                return new Promise<void>(resolve => {
-                    form.parse(req, async (_err, _fields, files) => {
-                        let results: any[] = [];
-                        for (const key in files) {
-                            const result = await DashUploadUtils.upload(files[key]);
-                            result && results.push(result);
-                        }
-                        _success(res, results);
-                        resolve();
-                    });
-                });
-            }
-        });
-
         register({
             method: Method.POST,
             subscription: "/inspectImage",
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index c831eb072..9ccc72e35 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -85,7 +85,8 @@ export namespace DashUploadUtils {
                     return UploadPdf(path);
                 }
         }
-        console.log(ConsoleColors.Red, `Ignoring unsupported file ${name} with upload type (${type}).`);
+
+        console.log(ConsoleColors.Red, `Ignoring unsupported file (${name}) with upload type (${type}).`);
         return { clientAccessPath: undefined };
     }
 
@@ -169,17 +170,12 @@ export namespace DashUploadUtils {
         if (isLocal) {
             return results;
         }
-        const metadata = (await new Promise<any>((resolve, reject) => {
-            request.head(source, async (error, res) => {
-                if (error) {
-                    return reject(error);
-                }
-                resolve(res);
-            });
-        })).headers;
+        const { headers } = (await new Promise<any>((resolve, reject) => {
+            request.head(source, (error, res) => error ? reject(error) : resolve(res));
+        }));
         return {
-            contentSize: parseInt(metadata[size]),
-            contentType: metadata[type],
+            contentSize: parseInt(headers[size]),
+            contentType: headers[type],
             ...results
         };
     };
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 3a20d5af5..7c49485f1 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -26,6 +26,8 @@ export interface RouteInitializer {
     onError?: OnError;
 }
 
+const registered = new Map<string, Set<Method>>();
+
 export default class RouteManager {
     private server: express.Express;
     private _isRelease: boolean;
@@ -89,6 +91,18 @@ export default class RouteManager {
             } else {
                 route = subscriber.build;
             }
+            const existing = registered.get(route);
+            if (existing) {
+                if (existing.has(method)) {
+                    console.log(ConsoleColors.Red, `\nDuplicate registration error: already registered ${route} with Method[${method}]`);
+                    console.log('Please remove duplicate registrations before continuing...\n');
+                    process.exit(0);
+                }
+            } else {
+                const specific = new Set<Method>();
+                specific.add(method);
+                registered.set(route, specific);
+            }
             switch (method) {
                 case Method.GET:
                     this.server.get(route, supervised);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 052aa54a6..ac4462f78 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction } from "mobx";
 import * as rp from 'request-promise';
 import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
@@ -11,7 +11,6 @@ import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
-import { ButtonBox } from "../../../client/views/nodes/ButtonBox";
 import { nullAudio } from "../../../new_fields/URLField";
 import { DragManager } from "../../../client/util/DragManager";
 import { InkingControl } from "../../../client/views/InkingControl";
diff --git a/src/server/credentials/test.json b/src/server/credentials/test.json
deleted file mode 100644
index 0a032cc2d..000000000
--- a/src/server/credentials/test.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "installed": {
-        "client_id": "343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com",
-        "project_id": "quickstart-1565056383187",
-        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
-        "token_uri": "https://oauth2.googleapis.com/token",
-        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
-        "client_secret": "w8KIFSc0MQpmUYHed4qEzn8b",
-        "redirect_uris": [
-            "urn:ietf:wg:oauth:2.0:oob",
-            "http://localhost"
-        ]
-    }
-}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

