Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 42 additions & 16 deletions apps/zui/src/core/main/main-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,52 +31,78 @@ import * as zui from "src/zui"
import log from "electron-log"
import {ElectronZedClient} from "../electron-zed-client"
import {ElectronZedLake} from "../electron-zed-lake"
import {DefaultLake} from "src/models/default-lake"
import {DomainModel} from "../domain-model"

export class MainObject {
public isQuitting = false
abortables = new Abortables()
emitter = new EventEmitter()
lake: ElectronZedLake

static async boot(params: Partial<MainArgs> = {}) {
const args = {...mainDefaults(), ...params}
const session = createSession(args.appState)
const data = decodeSessionState(await session.load())
const windows = new WindowManager(data)
const store = createMainStore(data?.globalState)
DomainModel.store = store
const appMeta = await getAppMeta()
const lake = new ElectronZedLake({
root: args.lakeRoot,
port: args.lakePort,
logs: args.lakeLogs,
bin: zdeps.zed,
corsOrigins: ["*"],
})
return new MainObject(lake, windows, store, session, args, appMeta)
return new MainObject(windows, store, session, args, appMeta)
}

// Only call this from boot
constructor(
readonly lake: ElectronZedLake,
readonly windows: WindowManager,
readonly store: ReduxStore<State, any>,
readonly session: Session,
readonly args: MainArgs,
readonly appMeta: AppMeta
) {}
) {
this.lake = this.initLake()
}

async start() {
if (this.args.lake) {
if (!(await this.lake.start())) {
log.error("Failed to start lake process after 5 seconds")
}
async stopLake() {
const result = await this.lake.stop()
if (result) {
log.info("Lake stopped:", this.lake.asJSON())
} else {
log.error("Failed to stop lake: ", this.lake.asJSON())
}
return result
}

startLake() {
this.lake = this.initLake()
const result = this.lake.start()
if (result) {
log.info("Lake started:", this.lake.asJSON())
} else {
log.error("Failed to start lake:", this.lake.asJSON())
}
return result
}

initLake() {
return new ElectronZedLake({
root: this.args.lakeRoot,
addr: DefaultLake.listenAddr,
port: this.args.lakePort,
logs: this.args.lakeLogs,
bin: zdeps.zed,
corsOrigins: ["*"],
})
}

async start() {
if (this.args.lake) await this.startLake()
if (this.args.devtools) await installExtensions()
await this.windows.init()
}

async stop() {
await this.abortables.abortAll()
await this.lake.stop()
await this.stopLake()
}

async resetState() {
Expand Down
14 changes: 10 additions & 4 deletions apps/zui/src/core/on-state-change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import {Store} from "src/js/state/types"
export function onStateChange(
store: Store,
selector: Selector,
onChange: (value: any) => void
onChange: (value: any) => void,
options: {skipInitial?: boolean} = {}
) {
let initial = true
let current = undefined

function listener() {
const next = selector(store.getState())
if (next !== current) {
current = next
const value = selector(store.getState())
if (initial) {
initial = false
current = value
if (!options.skipInitial) onChange(current)
} else if (value !== current) {
current = value
onChange(current)
}
}
Expand Down
14 changes: 12 additions & 2 deletions apps/zui/src/domain/configurations/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@ export class ConfigurationsApi {
}
}

watch(namespace: string, name: string, onChange: (val: any) => void) {
onStateChange(this.store, ConfigPropValues.get(namespace, name), onChange)
watch(
namespace: string,
name: string,
onChange: (val: any) => void,
options: {skipInitial?: boolean} = {}
) {
onStateChange(
this.store,
ConfigPropValues.get(namespace, name),
onChange,
options
)
}
}
16 changes: 16 additions & 0 deletions apps/zui/src/electron/run-main/run-configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,20 @@ export function runConfigurations() {
},
},
})
configurations.create({
name: "defaultLake",
title: "Zed Lake Service",
properties: {
address: {
name: "listenAddr",
label: "Listen For Incoming Connections From...",
type: "string",
enum: [
["Localhost only", "localhost"],
["Anywhere", ""],
],
defaultValue: "localhost",
},
},
})
}
1 change: 1 addition & 0 deletions apps/zui/src/initializers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * as shortcuts from "./shortcuts"
export * as userTasks from "./user-tasks"
export * as windowEvents from "./window-events"
export * as defaultLake from "./default-lake"
export * as watchListenAddr from "./watch-listen-addr"
14 changes: 14 additions & 0 deletions apps/zui/src/initializers/watch-listen-addr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {MainObject} from "src/core/main/main-object"
import {configurations} from "src/zui"

export function initialize(main: MainObject) {
configurations.watch(
"defaultLake",
"listenAddr",
async () => {
await main.stopLake()
await main.startLake()
},
{skipInitial: true}
)
}
8 changes: 8 additions & 0 deletions apps/zui/src/models/default-lake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {DomainModel} from "src/core/domain-model"
import ConfigPropValues from "src/js/state/ConfigPropValues"

export class DefaultLake extends DomainModel {
static get listenAddr() {
return this.select(ConfigPropValues.get("defaultLake", "listenAddr"))
}
}
44 changes: 23 additions & 21 deletions packages/zed-node/src/lake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { ChildProcess, spawn, SpawnOptions } from 'child_process';
import { mkdirpSync } from 'fs-extra';
import { join } from 'path';
import { getZedPath } from './binpath';
import { waitFor } from './util';

type ConstructorOpts = {
root: string;
logs: string;
addr: string;
port?: number;
bin?: string;
corsOrigins?: string[];
};

export class Lake {
fetch = globalThis.fetch;
lake?: ChildProcess;
root: string;
addr: string;
port: number;
logs: string;
bin: string;
Expand All @@ -22,13 +26,21 @@ export class Lake {
constructor(opts: ConstructorOpts) {
this.root = opts.root;
this.logs = opts.logs;
this.addr = opts.addr ?? 'localhost';
this.port = opts.port || 9867;
this.bin = opts.bin || getZedPath();
this.cors = opts.corsOrigins || [];
}

addr(): string {
return `localhost:${this.port}`;
asJSON() {
return {
root: this.root,
logs: this.logs,
addr: this.addr,
port: this.port,
bin: this.bin,
cors: this.cors,
};
}

start() {
Expand All @@ -38,7 +50,7 @@ export class Lake {
const args = [
'serve',
'-l',
this.addr(),
`${this.addr}:${this.port}`,
'-lake',
this.root,
'-manage=5m',
Expand All @@ -47,6 +59,7 @@ export class Lake {
'-log.path',
join(this.logs, 'zlake.log'),
];

for (const origin of this.cors) {
args.push(`--cors.origin=${origin}`);
}
Expand All @@ -72,18 +85,18 @@ export class Lake {
return waitFor(async () => this.isUp());
}

async stop(): Promise<boolean> {
stop(): Promise<boolean> {
if (this.lake) {
this.lake.kill('SIGTERM');
return waitFor(() => this.isDown());
} else {
return true;
return Promise.resolve(true);
}
}

async isUp() {
try {
const response = await this.fetch(`http://${this.addr()}/status`);
const response = await this.fetch(this.statusUrl);
const text = await response.text();
return text === 'ok';
} catch (e) {
Expand All @@ -94,21 +107,10 @@ export class Lake {
async isDown() {
return !(await this.isUp());
}
}

async function waitFor(condition: () => Promise<boolean>) {
let giveUp = false;
const id = setTimeout(() => {
giveUp = true;
}, 5000);

while (!giveUp) {
if (await condition()) break;
await sleep(50);
get statusUrl() {
// 'localhost' will always get us to the zed service,
// even when the addr is set to an empty string.
return `http://localhost:${this.port}/status`;
}

clearTimeout(id);
return !giveUp;
}

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
17 changes: 17 additions & 0 deletions packages/zed-node/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,20 @@ export function arrayWrap<T>(value: T | T[]): T[] {
if (Array.isArray(value)) return value;
else return [value];
}

export async function waitFor(condition: () => Promise<boolean>) {
let giveUp = false;
const id = setTimeout(() => {
giveUp = true;
}, 5000);

while (!giveUp) {
if (await condition()) break;
await sleep(50);
}

clearTimeout(id);
return !giveUp;
}

export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));