feat: add ipc
parent
abb46979da
commit
259aa852d8
|
|
@ -32,6 +32,7 @@
|
||||||
"https://deno.land/std@0.160.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24",
|
"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/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
|
||||||
"https://deno.land/std@0.160.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d",
|
"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/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c",
|
||||||
"https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
|
"https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
|
||||||
"https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8",
|
"https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8",
|
||||||
|
|
|
||||||
1
deps.ts
1
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 { 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 { 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 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";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { IPC } from './stdio';
|
||||||
|
export type { Command } from './types';
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export type { Command, ExtractType } from '../../../src/ipc/types';
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
import { WebviewWindow } from '@tauri-apps/api/window'
|
import { WebviewWindow } from '@tauri-apps/api/window'
|
||||||
import { Loading } from 'components/Loading'
|
import { Loading } from 'components/Loading'
|
||||||
import React from 'react'
|
import { IPC, Command } from 'ipc';
|
||||||
|
|
||||||
|
const ipc = new IPC<Command>();
|
||||||
|
|
||||||
export const Home: React.FC = ({ }) => {
|
export const Home: React.FC = ({ }) => {
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
|
|
@ -10,8 +13,14 @@ export const Home: React.FC = ({ }) => {
|
||||||
focus: true,
|
focus: true,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
const onHello = async () => {
|
||||||
|
await ipc.send({ type: 'hello', data: '1234' });
|
||||||
|
const data = await ipc.recvType('hello');
|
||||||
|
console.log(`hello`, data)
|
||||||
|
}
|
||||||
return <>
|
return <>
|
||||||
Hello world! <Loading />
|
Hello world! <Loading />
|
||||||
<button onClick={onClick}>Open the window!</button>
|
<button onClick={onClick}>Open the window!</button>
|
||||||
|
<button onClick={onHello}>Hello</button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
s3si.ts
8
s3si.ts
|
|
@ -1,11 +1,12 @@
|
||||||
import { App, DEFAULT_OPTS } from "./src/app.ts";
|
import { App, DEFAULT_OPTS } from "./src/app.ts";
|
||||||
|
import { runDaemon } from "./src/daemon.ts";
|
||||||
import { showError } from "./src/utils.ts";
|
import { showError } from "./src/utils.ts";
|
||||||
import { flags } from "./deps.ts";
|
import { flags } from "./deps.ts";
|
||||||
|
|
||||||
const parseArgs = (args: string[]) => {
|
const parseArgs = (args: string[]) => {
|
||||||
const parsed = flags.parse(args, {
|
const parsed = flags.parse(args, {
|
||||||
string: ["profilePath", "exporter", "skipMode"],
|
string: ["profilePath", "exporter", "skipMode"],
|
||||||
boolean: ["help", "noProgress", "monitor", "withSummary"],
|
boolean: ["help", "noProgress", "monitor", "withSummary", "daemon"],
|
||||||
alias: {
|
alias: {
|
||||||
"help": "h",
|
"help": "h",
|
||||||
"profilePath": ["p", "profile-path"],
|
"profilePath": ["p", "profile-path"],
|
||||||
|
|
@ -38,6 +39,11 @@ Options:
|
||||||
);
|
);
|
||||||
Deno.exit(0);
|
Deno.exit(0);
|
||||||
}
|
}
|
||||||
|
if (opts.daemon) {
|
||||||
|
await runDaemon();
|
||||||
|
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { IPC } from "./stdio.ts";
|
||||||
|
export { WorkerChannel } from "./channel.ts";
|
||||||
|
|
@ -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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 }
|
||||||
|
>;
|
||||||
Loading…
Reference in New Issue