Rename to miniSCP; migrate legacy data dirs on boot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-12 01:53:26 +02:00
co-authored by Claude Fable 5
parent 320080de6d
commit ba0b2434fb
7 changed files with 54 additions and 24 deletions
+5 -1
View File
@@ -7,6 +7,7 @@ import { SessionRegistry } from "./fs/registry";
import { applyApplicationMenu } from "./menu";
import { createAppRPC } from "./rpc-handlers";
import { ConnectionStore } from "./store/connections";
import { migrateLegacyDataDir } from "./store/paths";
import { SettingsStore } from "./store/settings";
import { AppTray } from "./tray";
import { TransferEngine } from "./transfers/engine";
@@ -31,6 +32,9 @@ async function getMainViewUrl(): Promise<string> {
}
}
// after a rebrand, adopt the previous name's data dir before stores read it
migrateLegacyDataDir();
const registry = new SessionRegistry();
const engine = new TransferEngine(registry);
const store = new ConnectionStore();
@@ -62,7 +66,7 @@ const win = new BrowserWindow({
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
: {}),
// lets the UI pick the fused layout before first paint
preload: `window.__BUNSCP_PLATFORM=${JSON.stringify(process.platform)};`,
preload: `window.__APP_PLATFORM=${JSON.stringify(process.platform)};`,
rpc,
});
+24 -7
View File
@@ -1,18 +1,35 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { BRANDING } from "../../shared/branding";
export function appDataDir(): string {
function dirFor(appName: string): string {
const home = os.homedir();
switch (process.platform) {
case "darwin":
return path.join(home, "Library", "Application Support", BRANDING.appName);
return path.join(home, "Library", "Application Support", appName);
case "win32":
return path.join(
process.env.APPDATA ?? path.join(home, "AppData", "Roaming"),
BRANDING.appName,
);
return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), appName);
default:
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), BRANDING.appName);
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), appName);
}
}
export function appDataDir(): string {
return dirFor(BRANDING.appName);
}
/** One-time move of a previous name's data dir (connections, settings, edits)
* after a rebrand. Runs at boot, before anything touches the stores. */
export function migrateLegacyDataDir(): void {
const current = appDataDir();
if (fs.existsSync(current)) return;
for (const legacy of BRANDING.legacyNames) {
const old = dirFor(legacy);
if (!fs.existsSync(old)) continue;
fs.mkdirSync(path.dirname(current), { recursive: true });
fs.renameSync(old, current);
console.log(`[store] migrated data dir ${legacy} -> ${BRANDING.appName}`);
return;
}
}
+6 -2
View File
@@ -1,11 +1,14 @@
import { pipeline } from "node:stream/promises";
import type { Readable } from "node:stream";
import { BRANDING } from "../../shared/branding";
import type { EntryType, TransferSnapshot, TransferState } from "../../shared/types";
import type { FileSystemProvider } from "../fs/provider";
import type { SessionRegistry } from "../fs/registry";
import { walkTree } from "./walk";
const PROGRESS_INTERVAL_MS = 100;
// same-fs probe temp files stay attributable to the app that wrote them
const PROBE_PREFIX = `.${BRANDING.appName.toLowerCase()}-probe-`;
// workers per job; the shared slot budget is the real limit
const MAX_WORKERS_PER_JOB = 8;
@@ -359,7 +362,8 @@ export class TransferEngine {
dir: string,
): Promise<boolean> {
const token = crypto.randomUUID();
const probePath = dst.join(dir, `.bunscp-probe-${token.slice(0, 8)}`);
const probeName = `${PROBE_PREFIX}${token.slice(0, 8)}`;
const probePath = dst.join(dir, probeName);
try {
await new Promise<void>((resolve, reject) => {
const stream = dst.createWriteStream(probePath);
@@ -368,7 +372,7 @@ export class TransferEngine {
});
try {
const chunks: Buffer[] = [];
for await (const chunk of src.createReadStream(src.join(dir, `.bunscp-probe-${token.slice(0, 8)}`))) {
for await (const chunk of src.createReadStream(src.join(dir, probeName))) {
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
}
const seen = Buffer.concat(chunks).toString("utf8") === token;
+5 -2
View File
@@ -1,8 +1,11 @@
// The single place to rebrand the app. Everything (window title, bundle name,
// app-data directory, UI header, icon) reads from here.
export const BRANDING = {
appName: "BunSCP",
identifier: "es.teknes.bunscp",
appName: "miniSCP",
identifier: "es.teknes.miniscp",
// Previous app names: their app-data folders are migrated on boot
// (see migrateLegacyDataDir in src/bun/store/paths.ts).
legacyNames: ["BunSCP"],
version: "0.1.0",
// macOS build converts this .iconset folder into AppIcon.icns.
// Regenerate from assets/icon.png with scripts/gen-iconset.sh.