Uploader online (#26)
This commit is contained in:
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
114
packages/ide/src/fill/notification.ts
Normal file
114
packages/ide/src/fill/notification.ts
Normal 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}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
18
packages/ide/src/fill/uri.ts
Normal file
18
packages/ide/src/fill/uri.ts
Normal 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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from "./client";
|
||||
export * from "./upload";
|
||||
export * from "./fill/uri";
|
||||
export * from "./fill/notification";
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user