AppImage release / appimage (push) Successful in 53s
Drag Baroque charts onto a line, reorder them, set a duration, or remove a note. The AppImage no longer packages oemer, ONNX, or the models. Version 0.2.0. Co-authored-by: catpersonisme <catpersonisme@gmail.com>
125 lines
3.4 KiB
JavaScript
125 lines
3.4 KiB
JavaScript
const { app, BrowserWindow, dialog } = require("electron");
|
|
const { spawn } = require("node:child_process");
|
|
const fs = require("node:fs");
|
|
const http = require("node:http");
|
|
const net = require("node:net");
|
|
const path = require("node:path");
|
|
|
|
app.commandLine.appendSwitch("no-sandbox");
|
|
app.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
|
|
let server;
|
|
|
|
function freePort() {
|
|
return new Promise((resolve, reject) => {
|
|
const probe = net.createServer();
|
|
probe.listen(0, "127.0.0.1", () => {
|
|
const address = probe.address();
|
|
const port = address && typeof address === "object" ? address.port : 0;
|
|
probe.close(() => resolve(port));
|
|
});
|
|
probe.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function waitFor(url, timeoutMs) {
|
|
const started = Date.now();
|
|
return new Promise((resolve, reject) => {
|
|
const tick = () => {
|
|
const request = http.get(url, (response) => {
|
|
response.resume();
|
|
if (response.statusCode && response.statusCode < 500) resolve();
|
|
else retry();
|
|
});
|
|
request.on("error", retry);
|
|
};
|
|
const retry = () => {
|
|
if (Date.now() - started > timeoutMs) reject(new Error(`Timed out waiting for ${url}`));
|
|
else setTimeout(tick, 250);
|
|
};
|
|
tick();
|
|
});
|
|
}
|
|
|
|
function stopServer() {
|
|
if (server && !server.killed) server.kill("SIGTERM");
|
|
}
|
|
|
|
async function open() {
|
|
const resources = process.resourcesPath;
|
|
const standalone = path.join(resources, "standalone");
|
|
const serverJs = path.join(standalone, "server.js");
|
|
if (!fs.existsSync(serverJs)) {
|
|
dialog.showErrorBox(
|
|
"Recorder fingering editor",
|
|
"The bundled app is missing from this AppImage.",
|
|
);
|
|
app.quit();
|
|
return;
|
|
}
|
|
|
|
const port = await freePort();
|
|
const logPath = path.join(app.getPath("temp"), "recorder-tab-converter.log");
|
|
const log = fs.createWriteStream(logPath, { flags: "a" });
|
|
server = spawn(process.execPath, [serverJs], {
|
|
cwd: standalone,
|
|
env: {
|
|
...process.env,
|
|
ELECTRON_RUN_AS_NODE: "1",
|
|
PORT: String(port),
|
|
HOSTNAME: "127.0.0.1",
|
|
NODE_ENV: "production",
|
|
},
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
server.stdout.pipe(log);
|
|
server.stderr.pipe(log);
|
|
server.on("exit", (code) => {
|
|
log.write(`\nserver exit ${code}\n`);
|
|
});
|
|
|
|
const url = `http://127.0.0.1:${port}/`;
|
|
try {
|
|
await waitFor(url, 45_000);
|
|
} catch (error) {
|
|
dialog.showErrorBox(
|
|
"Recorder fingering editor",
|
|
`${error instanceof Error ? error.message : "The app server did not start."}\nLog: ${logPath}`,
|
|
);
|
|
stopServer();
|
|
app.quit();
|
|
return;
|
|
}
|
|
|
|
const window = new BrowserWindow({
|
|
width: 1280,
|
|
height: 880,
|
|
minWidth: 390,
|
|
minHeight: 640,
|
|
show: false,
|
|
title: "Recorder fingering editor",
|
|
backgroundColor: "#f3ecdf",
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
// Renderer sandbox cannot create shared memory here, so the window
|
|
// fails with ERR_FAILED. The page is the local UI and has no Node access.
|
|
sandbox: false,
|
|
},
|
|
});
|
|
window.once("ready-to-show", () => window.show());
|
|
await window.loadURL(url);
|
|
}
|
|
|
|
app.whenReady().then(open).catch((error) => {
|
|
dialog.showErrorBox("Recorder fingering editor", error instanceof Error ? error.message : String(error));
|
|
app.quit();
|
|
});
|
|
|
|
app.on("before-quit", stopServer);
|
|
app.on("window-all-closed", () => {
|
|
stopServer();
|
|
app.quit();
|
|
});
|