Archived
1
0

Uploader online (#26)

This commit is contained in:
Asher
2019-01-30 15:40:01 -06:00
committed by Kyle Carberry
parent 62b1e0ef00
commit ebe5e1b1a9
20 changed files with 430 additions and 264 deletions

View File

@ -1,28 +1,12 @@
import { Event } from "@coder/events";
import { field, logger, time, Time } from "@coder/logger";
import { InitData, ISharedProcessData } from "@coder/protocol";
import { retry, Retry } from "./retry";
import { retry } from "./retry";
import { Upload } from "./upload";
import { client } from "./fill/client";
import { Clipboard, clipboard } from "./fill/clipboard";
export interface IURI {
readonly path: string;
readonly fsPath: string;
readonly scheme: string;
}
export interface IURIFactory {
/**
* Convert the object to an instance of a real URI.
*/
create<T extends IURI>(uri: IURI): T;
file(path: string): IURI;
parse(raw: string): IURI;
}
import { INotificationService, NotificationService, IProgressService, ProgressService } from "./fill/notification";
import { IURIFactory } from "./fill/uri";
/**
* A general abstraction of an IDE client.
@ -34,9 +18,10 @@ export interface IURIFactory {
*/
export abstract class Client {
public readonly retry: Retry = retry;
public readonly retry = retry;
public readonly clipboard: Clipboard = clipboard;
public readonly uriFactory: IURIFactory;
public readonly upload = new Upload(new NotificationService(), new ProgressService());
private start: Time | undefined;
private readonly progressElement: HTMLElement | undefined;
private tasks: string[] = [];
@ -187,6 +172,15 @@ export abstract class Client {
return this.sharedProcessDataPromise;
}
public set notificationService(service: INotificationService) {
this.retry.notificationService = service;
this.upload.notificationService = service;
}
public set progressService(service: IProgressService) {
this.upload.progressService = service;
}
/**
* Initialize the IDE.
*/

View File

@ -1,4 +1,10 @@
import { CP } from "@coder/protocol";
import { client } from "./client";
import { promisify } from "./util";
export = new CP(client);
const cp = new CP(client);
// tslint:disable-next-line no-any makes util.promisify return an object
(cp as any).exec[promisify.customPromisifyArgs] = ["stdout", "stderr"];
export = cp;

View File

@ -16,7 +16,7 @@ class Connection implements ReadWriteConnection {
private readonly downEmitter: Emitter<void> = new Emitter();
private readonly messageBuffer: Uint8Array[] = [];
private socketTimeoutDelay = 60 * 1000;
private retryName = "Web socket";
private retryName = "Socket";
private isUp: boolean = false;
private closed: boolean = false;

View File

@ -0,0 +1,114 @@
import { logger, field } from "@coder/logger";
/**
* Handle for a notification that allows it to be closed and updated.
*/
export interface INotificationHandle {
/**
* Closes the notification.
*/
close(): void;
/**
* Update the message.
*/
updateMessage(message: string): void;
/**
* Update the buttons.
*/
updateButtons(buttons: INotificationButton[]): void;
}
/**
* Notification severity.
*/
export enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3,
}
/**
* Notification button.
*/
export interface INotificationButton {
label: string;
run(): void;
}
/**
* Optional notification service.
*/
export interface INotificationService {
/**
* Display an error message.
*/
error(error: Error): void;
/**
* Show a notification.
*/
prompt(severity: Severity, message: string, buttons: INotificationButton[], onCancel: () => void): INotificationHandle;
}
/**
* Updatable progress.
*/
export interface IProgress {
/**
* Report progress. Progress is the completed percentage from 0 to 100.
*/
report(progress: number): void;
}
/**
* Option progress reporting service.
*/
export interface IProgressService {
/**
* Start a new progress bar that resolves & disappears when the task finishes.
*/
start<T>(title: string, task: (progress: IProgress) => Promise<T>, onCancel: () => void): Promise<T>;
}
/**
* Temporary notification service.
*/
export class NotificationService implements INotificationService {
public error(error: Error): void {
logger.error(error.message, field("error", error));
}
public prompt(_severity: Severity, message: string, _buttons: INotificationButton[], _onCancel: () => void): INotificationHandle {
throw new Error(`cannot prompt using the console: ${message}`);
}
}
/**
* Temporary progress service.
*/
export class ProgressService implements IProgressService {
public start<T>(title: string, task: (progress: IProgress) => Promise<T>): Promise<T> {
logger.info(title);
return task({
report: (progress): void => {
logger.info(`${title} progress: ${progress}`);
},
});
}
}

View File

@ -0,0 +1,18 @@
export interface IURI {
readonly path: string;
readonly fsPath: string;
readonly scheme: string;
}
export interface IURIFactory {
/**
* Convert the object to an instance of a real URI.
*/
create<T extends IURI>(uri: IURI): T;
file(path: string): IURI;
parse(raw: string): IURI;
}

View File

@ -1,4 +1,4 @@
export * from "../../../../node_modules/util";
import { implementation } from "util.promisify";
import { implementation } from "../../../../node_modules/util.promisify";
export const promisify = implementation;

View File

@ -1,2 +1,3 @@
export * from "./client";
export * from "./upload";
export * from "./fill/uri";
export * from "./fill/notification";

View File

@ -1,56 +1,5 @@
import { logger } from "@coder/logger";
/**
* Handle for a notification that allows it to be closed and updated.
*/
export interface INotificationHandle {
/**
* Closes the notification.
*/
close(): void;
/**
* Update the message.
*/
updateMessage(message: string): void;
/**
* Update the buttons.
*/
updateButtons(buttons: INotificationButton[]): void;
}
/**
* Notification severity.
*/
enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3,
}
/**
* Notification button.
*/
export interface INotificationButton {
label: string;
run(): void;
}
/**
* Optional notification service.
*/
export interface INotificationService {
/**
* Show a notification.
*/
prompt(severity: Severity, message: string, buttons: INotificationButton[], onCancel: () => void): INotificationHandle;
}
import { NotificationService, INotificationHandle, INotificationService, Severity } from "./fill/notification";
interface IRetryItem {
count?: number;
@ -91,15 +40,16 @@ export class Retry {
// for reasoning.)
private waitDelay = 50;
public constructor(private notificationService?: INotificationService) {
public constructor(private _notificationService: INotificationService) {
this.items = new Map();
}
/**
* Set notification service.
*/
public setNotificationService(notificationService?: INotificationService): void {
this.notificationService = notificationService;
public set notificationService(service: INotificationService) {
this._notificationService = service;
}
public get notificationService(): INotificationService {
return this._notificationService;
}
/**
@ -262,10 +212,6 @@ export class Retry {
* Update, close, or show the notification.
*/
private updateNotification(): void {
if (!this.notificationService) {
return;
}
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
clearTimeout(this.updateTimeout as any);
@ -343,4 +289,4 @@ export class Retry {
// Global instance so we can block other retries when retrying the main
// connection.
export const retry = new Retry();
export const retry = new Retry(new NotificationService());

View File

@ -3,7 +3,8 @@ import { appendFile } from "fs";
import { promisify } from "util";
import { logger, Logger } from "@coder/logger";
import { escapePath } from "@coder/protocol";
import { IURI } from "./uri";
import { IURI } from "./fill/uri";
import { INotificationService, IProgressService, IProgress, Severity } from "./fill/notification";
/**
* Represents an uploadable directory, so we can query for existing files once.
@ -27,47 +28,6 @@ interface IEntry {
});
}
/**
* Updatable progress.
*/
interface IProgress {
/**
* Report progress. Progress is the completed percentage from 0 to 100.
*/
report(progress: number): void;
}
/**
* Service for reporting progress.
*/
interface IProgressService {
/**
* Start a new progress bar that resolves & disappears when the task finishes.
*/
start<T>(title:string, task: (progress: IProgress) => Promise<T>): Promise<T>;
}
/**
* Service for notifications.
*/
interface INotificationService {
/**
* Display an error message.
*/
error(error: Error): void;
/**
* Ask for a decision.
*/
prompt(message: string, choices: string[]): Promise<string>;
}
/**
* Handles file uploads.
*/
@ -76,8 +36,6 @@ export class Upload {
private readonly maxParallelUploads = 100;
private readonly readSize = 32000; // ~32kb max while reading in the file.
private readonly packetSize = 32000; // ~32kb max when writing.
private readonly notificationService: INotificationService;
private readonly progressService: IProgressService;
private readonly logger: Logger;
private readonly currentlyUploadingFiles: Map<string, File>;
private readonly queueByDirectory: Map<string, IUploadableDirectory>;
@ -88,9 +46,10 @@ export class Upload {
private uploadedFilePaths: string[];
private total: number;
public constructor(notificationService: INotificationService, progressService: IProgressService) {
this.notificationService = notificationService;
this.progressService = progressService;
public constructor(
private _notificationService: INotificationService,
private _progressService: IProgressService,
) {
this.logger = logger.named("Upload");
this.currentlyUploadingFiles = new Map();
this.queueByDirectory = new Map();
@ -99,6 +58,22 @@ export class Upload {
this.total = 0;
}
public set notificationService(service: INotificationService) {
this._notificationService = service;
}
public get notificationService(): INotificationService {
return this._notificationService;
}
public set progressService(service: IProgressService) {
this._progressService = service;
}
public get progressService(): IProgressService {
return this._progressService;
}
/**
* Upload dropped files. This will try to upload everything it can. Errors
* will show via notifications. If an upload operation is ongoing, the files
@ -125,6 +100,8 @@ export class Upload {
resolve(uploaded);
};
});
}, () => {
this.cancel();
});
}
this.uploadFiles();
@ -214,8 +191,21 @@ export class Upload {
*/
private async uploadFile(path: string, file: File, existingFiles: string[]): Promise<void> {
if (existingFiles.includes(path)) {
const choice = await this.notificationService.prompt(`${path} already exists. Overwrite?`, ["Yes", "No"]);
if (choice !== "Yes") {
const shouldOverwrite = await new Promise((resolve): void => {
this.notificationService.prompt(
Severity.Error,
`${path} already exists. Overwrite?`,
[{
label: "Yes",
run: (): void => resolve(true),
}, {
label: "No",
run: (): void => resolve(false),
}],
() => resolve(false),
);
});
if (!shouldOverwrite) {
return;
}
}