From 259aa852d80f9032ae65ba4a424ccbdf3970acc0 Mon Sep 17 00:00:00 2001 From: imspace Date: Sat, 4 Mar 2023 21:10:30 +0800 Subject: [PATCH] feat: add ipc --- deno.lock | 1 + deps.ts | 1 + gui/src/ipc/index.ts | 2 ++ gui/src/ipc/stdio.ts | 48 +++++++++++++++++++++++++++++++++++++ gui/src/ipc/types.ts | 1 + gui/src/pages/Home.tsx | 11 ++++++++- s3si.ts | 8 ++++++- src/daemon.ts | 20 ++++++++++++++++ src/ipc/channel.ts | 54 ++++++++++++++++++++++++++++++++++++++++++ src/ipc/mod.ts | 2 ++ src/ipc/stdio.ts | 40 +++++++++++++++++++++++++++++++ src/ipc/types.ts | 10 ++++++++ 12 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 gui/src/ipc/index.ts create mode 100644 gui/src/ipc/stdio.ts create mode 100644 gui/src/ipc/types.ts create mode 100644 src/daemon.ts create mode 100644 src/ipc/channel.ts create mode 100644 src/ipc/mod.ts create mode 100644 src/ipc/stdio.ts create mode 100644 src/ipc/types.ts diff --git a/deno.lock b/deno.lock index 471fe72..42a5e38 100644 --- a/deno.lock +++ b/deno.lock @@ -32,6 +32,7 @@ "https://deno.land/std@0.160.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24", "https://deno.land/std@0.160.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", "https://deno.land/std@0.160.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d", + "https://deno.land/std@0.160.0/streams/conversion.ts": "328afbedee0a7e0c330ac4c7b4c1af569ee53974f970230f6a78f545b93abb9b", "https://deno.land/std@0.160.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", "https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", "https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8", diff --git a/deps.ts b/deps.ts index dd03979..e60c479 100644 --- a/deps.ts +++ b/deps.ts @@ -13,3 +13,4 @@ export * as path from "https://deno.land/std@0.160.0/path/mod.ts"; export { MultiProgressBar } from "https://deno.land/x/progress@v1.2.8/mod.ts"; export { Mutex } from "https://deno.land/x/semaphore@v1.1.1/mod.ts"; export type { DeepReadonly } from "https://deno.land/x/ts_essentials@v9.1.2/mod.ts"; +export { writeAll } from "https://deno.land/std@0.160.0/streams/conversion.ts"; diff --git a/gui/src/ipc/index.ts b/gui/src/ipc/index.ts new file mode 100644 index 0000000..d19e762 --- /dev/null +++ b/gui/src/ipc/index.ts @@ -0,0 +1,2 @@ +export { IPC } from './stdio'; +export type { Command } from './types'; diff --git a/gui/src/ipc/stdio.ts b/gui/src/ipc/stdio.ts new file mode 100644 index 0000000..2ff3e02 --- /dev/null +++ b/gui/src/ipc/stdio.ts @@ -0,0 +1,48 @@ +import { ExtractType } from "./types"; +import { Command, Child } from '@tauri-apps/api/shell' + +export class IPC { + queue: T[] = []; + waiting: ((value: T) => void)[] = []; + callback = (data: unknown) => { + const waiting = this.waiting.shift(); + if (waiting) { + waiting(data as T); + } else { + this.queue.push(data as T); + } + }; + child: Promise; + + constructor() { + const command = Command.sidecar('../binaries/s3si', ['--daemon']); + command.stdout.on('data', line => { + this.callback(JSON.parse(line)) + }) + this.child = command.spawn() + } + + async recvType( + type: K, + ): Promise> { + const data = await this.recv(); + if (data.type !== type) { + throw new Error(`Unexpected type: ${data.type}`); + } + return data as ExtractType; + } + async recv(): Promise { + return new Promise((resolve) => { + const data = this.queue.shift(); + if (data) { + resolve(data); + } else { + this.waiting.push(resolve); + } + }); + } + async send(data: T) { + const child = await this.child; + await child.write(JSON.stringify(data) + "\n") + } +} diff --git a/gui/src/ipc/types.ts b/gui/src/ipc/types.ts new file mode 100644 index 0000000..1986489 --- /dev/null +++ b/gui/src/ipc/types.ts @@ -0,0 +1 @@ +export type { Command, ExtractType } from '../../../src/ipc/types'; diff --git a/gui/src/pages/Home.tsx b/gui/src/pages/Home.tsx index 20dcdc5..71c6540 100644 --- a/gui/src/pages/Home.tsx +++ b/gui/src/pages/Home.tsx @@ -1,6 +1,9 @@ +import React from 'react' import { WebviewWindow } from '@tauri-apps/api/window' import { Loading } from 'components/Loading' -import React from 'react' +import { IPC, Command } from 'ipc'; + +const ipc = new IPC(); export const Home: React.FC = ({ }) => { const onClick = () => { @@ -10,8 +13,14 @@ export const Home: React.FC = ({ }) => { focus: true, }) }; + const onHello = async () => { + await ipc.send({ type: 'hello', data: '1234' }); + const data = await ipc.recvType('hello'); + console.log(`hello`, data) + } return <> Hello world! + } diff --git a/s3si.ts b/s3si.ts index f904804..5020fa3 100644 --- a/s3si.ts +++ b/s3si.ts @@ -1,11 +1,12 @@ import { App, DEFAULT_OPTS } from "./src/app.ts"; +import { runDaemon } from "./src/daemon.ts"; import { showError } from "./src/utils.ts"; import { flags } from "./deps.ts"; const parseArgs = (args: string[]) => { const parsed = flags.parse(args, { string: ["profilePath", "exporter", "skipMode"], - boolean: ["help", "noProgress", "monitor", "withSummary"], + boolean: ["help", "noProgress", "monitor", "withSummary", "daemon"], alias: { "help": "h", "profilePath": ["p", "profile-path"], @@ -38,6 +39,11 @@ Options: ); Deno.exit(0); } +if (opts.daemon) { + await runDaemon(); + + Deno.exit(0); +} const app = new App({ ...DEFAULT_OPTS, diff --git a/src/daemon.ts b/src/daemon.ts new file mode 100644 index 0000000..62ffdfe --- /dev/null +++ b/src/daemon.ts @@ -0,0 +1,20 @@ +import { IPC } from "./ipc/mod.ts"; +import { Command } from "./ipc/types.ts"; + +export async function runDaemon() { + const ipc = new IPC({ + reader: Deno.stdin, + writer: Deno.stdout, + }); + + while (true) { + const cmd = await ipc.recv(); + switch (cmd.type) { + case "hello": + await ipc.send(cmd); + break; + default: + continue; + } + } +} diff --git a/src/ipc/channel.ts b/src/ipc/channel.ts new file mode 100644 index 0000000..a1eece1 --- /dev/null +++ b/src/ipc/channel.ts @@ -0,0 +1,54 @@ +/// +/// +/// +/// +/// + +import type { ExtractType } from "./types.ts"; + +export class WorkerChannel { + queue: T[] = []; + waiting: ((value: T) => void)[] = []; + + constructor(private worker?: Worker) { + const callback = ({ data }: { data: unknown }) => { + const waiting = this.waiting.shift(); + if (waiting) { + waiting(data as T); + } else { + this.queue.push(data as T); + } + }; + if (worker) { + worker.addEventListener("message", callback); + } else { + self.addEventListener("message", callback); + } + } + async recvType( + type: K, + ): Promise> { + const data = await this.recv(); + if (data.type !== type) { + throw new Error(`Unexpected type: ${data.type}`); + } + return data as ExtractType; + } + recv(): Promise { + return new Promise((resolve) => { + const data = this.queue.shift(); + if (data) { + resolve(data); + } else { + this.waiting.push(resolve); + } + }); + } + send(data: T) { + if (this.worker) { + this.worker.postMessage(data); + } else { + self.postMessage(data); + } + } +} diff --git a/src/ipc/mod.ts b/src/ipc/mod.ts new file mode 100644 index 0000000..39d68ba --- /dev/null +++ b/src/ipc/mod.ts @@ -0,0 +1,2 @@ +export { IPC } from "./stdio.ts"; +export { WorkerChannel } from "./channel.ts"; diff --git a/src/ipc/stdio.ts b/src/ipc/stdio.ts new file mode 100644 index 0000000..eaabbd2 --- /dev/null +++ b/src/ipc/stdio.ts @@ -0,0 +1,40 @@ +/// + +import { io, writeAll } from "../../deps.ts"; +import type { ExtractType } from "./types.ts"; + +export class IPC { + lines: AsyncIterableIterator; + writer: Deno.Writer; + constructor({ reader, writer }: { + reader: Deno.Reader; + writer: Deno.Writer; + }) { + this.lines = io.readLines(reader); + this.writer = writer; + } + async recvType( + type: K, + ): Promise> { + const data = await this.recv(); + if (data.type !== type) { + throw new Error(`Unexpected type: ${data.type}`); + } + return data as ExtractType; + } + async recv(): Promise { + const result = await this.lines.next(); + + if (!result.done) { + return JSON.parse(result.value); + } + + throw new Error("EOF"); + } + async send(data: T) { + await writeAll( + this.writer, + new TextEncoder().encode(JSON.stringify(data) + "\n"), + ); + } +} diff --git a/src/ipc/types.ts b/src/ipc/types.ts new file mode 100644 index 0000000..c19944d --- /dev/null +++ b/src/ipc/types.ts @@ -0,0 +1,10 @@ +export type Command = { + type: "hello"; + data: string; +}; + +export type ExtractType = + Extract< + T, + { type: K } + >;