feat: add ipc

main
imspace 2023-03-04 21:10:30 +08:00
parent abb46979da
commit 259aa852d8
12 changed files with 196 additions and 2 deletions

View File

@ -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",

View File

@ -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";

2
gui/src/ipc/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { IPC } from './stdio';
export type { Command } from './types';

48
gui/src/ipc/stdio.ts Normal file
View File

@ -0,0 +1,48 @@
import { ExtractType } from "./types";
import { Command, Child } from '@tauri-apps/api/shell'
export class IPC<T extends { type: string }> {
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<Child>;
constructor() {
const command = Command.sidecar('../binaries/s3si', ['--daemon']);
command.stdout.on('data', line => {
this.callback(JSON.parse(line))
})
this.child = command.spawn()
}
async recvType<K extends T["type"]>(
type: K,
): Promise<ExtractType<T, K>> {
const data = await this.recv();
if (data.type !== type) {
throw new Error(`Unexpected type: ${data.type}`);
}
return data as ExtractType<T, K>;
}
async recv(): Promise<T> {
return new Promise<T>((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")
}
}

1
gui/src/ipc/types.ts Normal file
View File

@ -0,0 +1 @@
export type { Command, ExtractType } from '../../../src/ipc/types';

View File

@ -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<Command>();
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! <Loading />
<button onClick={onClick}>Open the window!</button>
<button onClick={onHello}>Hello</button>
</>
}

View File

@ -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,

20
src/daemon.ts Normal file
View File

@ -0,0 +1,20 @@
import { IPC } from "./ipc/mod.ts";
import { Command } from "./ipc/types.ts";
export async function runDaemon() {
const ipc = new IPC<Command>({
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;
}
}
}

54
src/ipc/channel.ts Normal file
View File

@ -0,0 +1,54 @@
/// <reference no-default-lib="true" />
/// <reference lib="ESNext" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
import type { ExtractType } from "./types.ts";
export class WorkerChannel<T extends { type: string }> {
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<K extends T["type"]>(
type: K,
): Promise<ExtractType<T, K>> {
const data = await this.recv();
if (data.type !== type) {
throw new Error(`Unexpected type: ${data.type}`);
}
return data as ExtractType<T, K>;
}
recv(): Promise<T> {
return new Promise<T>((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);
}
}
}

2
src/ipc/mod.ts Normal file
View File

@ -0,0 +1,2 @@
export { IPC } from "./stdio.ts";
export { WorkerChannel } from "./channel.ts";

40
src/ipc/stdio.ts Normal file
View File

@ -0,0 +1,40 @@
/// <reference lib="deno.ns" />
import { io, writeAll } from "../../deps.ts";
import type { ExtractType } from "./types.ts";
export class IPC<T extends { type: string }> {
lines: AsyncIterableIterator<string>;
writer: Deno.Writer;
constructor({ reader, writer }: {
reader: Deno.Reader;
writer: Deno.Writer;
}) {
this.lines = io.readLines(reader);
this.writer = writer;
}
async recvType<K extends T["type"]>(
type: K,
): Promise<ExtractType<T, K>> {
const data = await this.recv();
if (data.type !== type) {
throw new Error(`Unexpected type: ${data.type}`);
}
return data as ExtractType<T, K>;
}
async recv(): Promise<T> {
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"),
);
}
}

10
src/ipc/types.ts Normal file
View File

@ -0,0 +1,10 @@
export type Command = {
type: "hello";
data: string;
};
export type ExtractType<T extends { type: string }, K extends T["type"]> =
Extract<
T,
{ type: K }
>;