Archived
1
0

Fix coping and moving files around using the file tree (#568)

* Implement write/read buffers in electron fill

This makes cutting and copy files from the file tree work.

* Implement fs.createReadStream

This is used by the file tree to copy files.

* Allow passing proxies back from client to server

This makes things like piping streams possible.

* Synchronously bind to proxy events

This eliminates any chance whatsoever of missing events due to binding
too late.

* Make it possible to bind some events on demand

* Add some protocol documentation
This commit is contained in:
Asher
2019-04-24 10:38:21 -05:00
committed by GitHub
parent 30b8565e2d
commit c9f91e77cd
21 changed files with 546 additions and 278 deletions

View File

@ -2,13 +2,22 @@ import * as cp from "child_process";
import * as net from "net";
import * as stream from "stream";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { ChildProcessModuleProxy, ChildProcessProxy, ChildProcessProxies } from "../../node/modules/child_process";
import { Readable, Writable } from "./stream";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
// tslint:disable completed-docs
export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.ChildProcess {
export interface ClientChildProcessProxy extends ChildProcessProxy, ClientServerProxy<cp.ChildProcess> {}
export interface ClientChildProcessProxies {
childProcess: ClientChildProcessProxy;
stdin?: ClientWritableProxy | null;
stdout?: ClientReadableProxy | null;
stderr?: ClientReadableProxy | null;
}
export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implements cp.ChildProcess {
public readonly stdin: stream.Writable;
public readonly stdout: stream.Readable;
public readonly stderr: stream.Readable;
@ -18,7 +27,7 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
private _killed: boolean = false;
private _pid = -1;
public constructor(proxyPromises: Promise<ChildProcessProxies>) {
public constructor(proxyPromises: Promise<ClientChildProcessProxies>) {
super(proxyPromises.then((p) => p.childProcess));
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
@ -99,8 +108,14 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
}
}
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy, ClientServerProxy {
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;
}
export class ChildProcessModule {
public constructor(private readonly proxy: ChildProcessModuleProxy) {}
public constructor(private readonly proxy: ClientChildProcessModuleProxy) {}
public exec = (
command: string,

View File

@ -1,12 +1,11 @@
import * as fs from "fs";
import { callbackify } from "util";
import { ClientProxy, Batch } from "../../common/proxy";
import { Batch, ClientProxy, ClientServerProxy } from "../../common/proxy";
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
import { FsModuleProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
import { Writable } from "./stream";
import { FsModuleProxy, ReadStreamProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
import { Readable, Writable } from "./stream";
// tslint:disable no-any
// tslint:disable completed-docs
// tslint:disable completed-docs no-any
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
public constructor(private readonly proxy: FsModuleProxy) {
@ -38,7 +37,9 @@ class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.P
}
}
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
interface ClientWatcherProxy extends WatcherProxy, ClientServerProxy<fs.FSWatcher> {}
class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
public close(): void {
this.catch(this.proxy.close());
}
@ -48,7 +49,25 @@ class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
}
}
class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
interface ClientReadStreamProxy extends ReadStreamProxy, ClientServerProxy<fs.ReadStream> {}
class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStream {
public get bytesRead(): number {
throw new Error("not implemented");
}
public get path(): string | Buffer {
throw new Error("not implemented");
}
public close(): void {
this.catch(this.proxy.close());
}
}
interface ClientWriteStreamProxy extends WriteStreamProxy, ClientServerProxy<fs.WriteStream> {}
class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteStream {
public get bytesWritten(): number {
throw new Error("not implemented");
}
@ -62,12 +81,18 @@ class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
}
}
interface ClientFsModuleProxy extends FsModuleProxy, ClientServerProxy {
createReadStream(path: fs.PathLike, options?: any): Promise<ClientReadStreamProxy>;
createWriteStream(path: fs.PathLike, options?: any): Promise<ClientWriteStreamProxy>;
watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<ClientWatcherProxy>;
}
export class FsModule {
private readonly statBatch: StatBatch;
private readonly lstatBatch: LstatBatch;
private readonly readdirBatch: ReaddirBatch;
public constructor(private readonly proxy: FsModuleProxy) {
public constructor(private readonly proxy: ClientFsModuleProxy) {
this.statBatch = new StatBatch(this.proxy);
this.lstatBatch = new LstatBatch(this.proxy);
this.readdirBatch = new ReaddirBatch(this.proxy);
@ -110,6 +135,10 @@ export class FsModule {
);
}
public createReadStream = (path: fs.PathLike, options?: any): fs.ReadStream => {
return new ReadStream(this.proxy.createReadStream(path, options));
}
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
return new WriteStream(this.proxy.createWriteStream(path, options));
}

View File

@ -1,16 +1,18 @@
import * as net from "net";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
import { Duplex } from "./stream";
// tslint:disable completed-docs
export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
interface ClientNetSocketProxy extends NetSocketProxy, ClientServerProxy<net.Socket> {}
export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
private _connecting: boolean = false;
private _destroyed: boolean = false;
public constructor(proxyPromise: Promise<NetSocketProxy> | NetSocketProxy, connecting?: boolean) {
public constructor(proxyPromise: Promise<ClientNetSocketProxy> | ClientNetSocketProxy, connecting?: boolean) {
super(proxyPromise);
if (connecting) {
this._connecting = connecting;
@ -126,12 +128,16 @@ export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
}
}
export class Server extends ClientProxy<NetServerProxy> implements net.Server {
interface ClientNetServerProxy extends NetServerProxy, ClientServerProxy<net.Server> {
onConnection(cb: (proxy: ClientNetSocketProxy) => void): Promise<void>;
}
export class Server extends ClientProxy<ClientNetServerProxy> implements net.Server {
private socketId = 0;
private readonly sockets = new Map<number, net.Socket>();
private _listening: boolean = false;
public constructor(proxyPromise: Promise<NetServerProxy> | NetServerProxy) {
public constructor(proxyPromise: Promise<ClientNetServerProxy> | ClientNetServerProxy) {
super(proxyPromise);
this.catch(this.proxy.onConnection((socketProxy) => {
@ -208,11 +214,17 @@ export class Server extends ClientProxy<NetServerProxy> implements net.Server {
type NodeNet = typeof net;
interface ClientNetModuleProxy extends NetModuleProxy, ClientServerProxy {
createSocket(options?: net.SocketConstructorOpts): Promise<ClientNetSocketProxy>;
createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<ClientNetSocketProxy>;
createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<ClientNetServerProxy>;
}
export class NetModule implements NodeNet {
public readonly Socket: typeof net.Socket;
public readonly Server: typeof net.Server;
public constructor(private readonly proxy: NetModuleProxy) {
public constructor(private readonly proxy: ClientNetModuleProxy) {
// @ts-ignore this is because Socket is missing things from the Stream
// namespace but I'm unsure how best to provide them (finished,
// finished.__promisify__, pipeline, and some others) or if it even matters.

View File

@ -1,15 +1,17 @@
import * as pty from "node-pty";
import { ClientProxy } from "../../common/proxy";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
// tslint:disable completed-docs
export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements pty.IPty {
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy, ClientServerProxy {}
export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> implements pty.IPty {
private _pid = -1;
private _process = "";
public constructor(
private readonly moduleProxy: NodePtyModuleProxy,
private readonly moduleProxy: ClientNodePtyModuleProxy,
private readonly file: string,
private readonly args: string[] | string,
private readonly options: pty.IPtyForkOptions,
@ -18,10 +20,12 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
this.on("process", (process) => this._process = process);
}
protected initialize(proxyPromise: Promise<NodePtyProcessProxy>): void {
super.initialize(proxyPromise);
protected initialize(proxyPromise: Promise<ClientNodePtyProcessProxy>): ClientNodePtyProcessProxy {
const proxy = super.initialize(proxyPromise);
this.catch(this.proxy.getPid().then((p) => this._pid = p));
this.catch(this.proxy.getProcess().then((p) => this._process = p));
return proxy;
}
public get pid(): number {
@ -53,8 +57,12 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
type NodePty = typeof pty;
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy, ClientServerProxy {
spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<ClientNodePtyProcessProxy>;
}
export class NodePtyModule implements NodePty {
public constructor(private readonly proxy: NodePtyModuleProxy) {}
public constructor(private readonly proxy: ClientNodePtyModuleProxy) {}
public spawn = (file: string, args: string[] | string, options: pty.IPtyForkOptions): pty.IPty => {
return new NodePtyProcess(this.proxy, file, args, options);

View File

@ -1,12 +1,14 @@
import * as spdlog from "spdlog";
import { ClientProxy } from "../../common/proxy";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
// tslint:disable completed-docs
class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.RotatingLogger {
interface ClientRotatingLoggerProxy extends RotatingLoggerProxy, ClientServerProxy {}
class RotatingLogger extends ClientProxy<ClientRotatingLoggerProxy> implements spdlog.RotatingLogger {
public constructor(
private readonly moduleProxy: SpdlogModuleProxy,
private readonly moduleProxy: ClientSpdlogModuleProxy,
private readonly name: string,
private readonly filename: string,
private readonly filesize: number,
@ -31,10 +33,14 @@ class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.
}
}
interface ClientSpdlogModuleProxy extends SpdlogModuleProxy, ClientServerProxy {
createLogger(name: string, filePath: string, fileSize: number, fileCount: number): Promise<ClientRotatingLoggerProxy>;
}
export class SpdlogModule {
public readonly RotatingLogger: typeof spdlog.RotatingLogger;
public constructor(private readonly proxy: SpdlogModuleProxy) {
public constructor(private readonly proxy: ClientSpdlogModuleProxy) {
this.RotatingLogger = class extends RotatingLogger {
public constructor(name: string, filename: string, filesize: number, filecount: number) {
super(proxy, name, filename, filesize, filecount);

View File

@ -1,11 +1,14 @@
import * as stream from "stream";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { DuplexProxy, IReadableProxy, WritableProxy } from "../../node/modules/stream";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { isPromise } from "../../common/util";
import { DuplexProxy, ReadableProxy, WritableProxy } from "../../node/modules/stream";
// tslint:disable completed-docs
// tslint:disable completed-docs no-any
export class Writable<T extends WritableProxy = WritableProxy> extends ClientProxy<T> implements stream.Writable {
export interface ClientWritableProxy extends WritableProxy, ClientServerProxy<stream.Writable> {}
export class Writable<T extends ClientWritableProxy = ClientWritableProxy> extends ClientProxy<T> implements stream.Writable {
public get writable(): boolean {
throw new Error("not implemented");
}
@ -50,7 +53,6 @@ export class Writable<T extends WritableProxy = WritableProxy> extends ClientPro
return this.catch(this.proxy.setDefaultEncoding(encoding));
}
// tslint:disable-next-line no-any
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
if (typeof encoding === "function") {
callback = encoding;
@ -65,7 +67,6 @@ export class Writable<T extends WritableProxy = WritableProxy> extends ClientPro
return true; // Always true since we can't get this synchronously.
}
// tslint:disable-next-line no-any
public end(data?: any | (() => void), encoding?: string | (() => void), callback?: (() => void)): void {
if (typeof data === "function") {
callback = data;
@ -88,7 +89,9 @@ export class Writable<T extends WritableProxy = WritableProxy> extends ClientPro
}
}
export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientProxy<T> implements stream.Readable {
export interface ClientReadableProxy extends ReadableProxy, ClientServerProxy<stream.Readable> {}
export class Readable<T extends ClientReadableProxy = ClientReadableProxy> extends ClientProxy<T> implements stream.Readable {
public get readable(): boolean {
throw new Error("not implemented");
}
@ -141,11 +144,20 @@ export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientP
throw new Error("not implemented");
}
public pipe<T>(): T {
throw new Error("not implemented");
public pipe<P extends NodeJS.WritableStream>(destination: P, options?: { end?: boolean }): P {
const writableProxy = (destination as any as Writable).proxyPromise;
if (!writableProxy) {
throw new Error("can only pipe stream proxies");
}
this.catch(
isPromise(writableProxy)
? writableProxy.then((p) => this.proxy.pipe(p, options))
: this.proxy.pipe(writableProxy, options),
);
return destination;
}
// tslint:disable-next-line no-any
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
throw new Error("not implemented");
}
@ -164,7 +176,9 @@ export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientP
}
}
export class Duplex<T extends DuplexProxy = DuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
export interface ClientDuplexProxy extends DuplexProxy, ClientServerProxy<stream.Duplex> {}
export class Duplex<T extends ClientDuplexProxy = ClientDuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
private readonly _readable: Readable;
public constructor(proxyPromise: Promise<T> | T) {
@ -228,7 +242,6 @@ export class Duplex<T extends DuplexProxy = DuplexProxy> extends Writable<T> imp
this._readable.unshift();
}
// tslint:disable-next-line no-any
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
return this._readable[Symbol.asyncIterator]();
}

View File

@ -1,10 +1,13 @@
import * as trash from "trash";
import { ClientServerProxy } from "../../common/proxy";
import { TrashModuleProxy } from "../../node/modules/trash";
// tslint:disable completed-docs
interface ClientTrashModuleProxy extends TrashModuleProxy, ClientServerProxy {}
export class TrashModule {
public constructor(private readonly proxy: TrashModuleProxy) {}
public constructor(private readonly proxy: ClientTrashModuleProxy) {}
public trash = (path: string, options?: trash.Options): Promise<void> => {
return this.proxy.trash(path, options);