Skip to content

Commit

Permalink
feat: support switching arch when downloading nodejs
Browse files Browse the repository at this point in the history
Signed-off-by: The1111mp <[email protected]>
  • Loading branch information
1111mp committed Feb 26, 2024
1 parent 63cf2f1 commit e8b7086
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/main/deps/get-node/arch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const PLATFORMS: { [kek: string]: string } = {
s390: 's390x',
s390x: 's390x',
x32: 'x86',
x86: 'x86',
x64: 'x64',
};

Expand Down
26 changes: 11 additions & 15 deletions src/main/deps/get-node/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,41 @@
import { once } from 'node:events';
import { createWriteStream } from 'node:fs';
import { once } from "node:events";
import { createWriteStream } from "node:fs";

import fetchNodeWebsite from '../fetch-node-website';
import { checkChecksum } from './checksum';
import fetchNodeWebsite from "../fetch-node-website";
import { checkChecksum } from "./checksum";

import type { Request } from 'got';
import type { Options as FetchNodeOptions } from '../fetch-node-website';
import type { Request } from "got";
import type { Options as FetchNodeOptions } from "../fetch-node-website";

// Make HTTP request to retrieve a Node.js binary.
// Also make another HTTP request to calculate the checksum.
export const fetchNodeUrl = async (
version: string,
filepath: string,
fetchOpts: FetchNodeOptions,
fetchOpts: FetchNodeOptions
) => {
const response = await fetchNodeWebsite(`v${version}/${filepath}`, fetchOpts);
const checksumError = checkChecksum({
version,
filepath,
response,
fetchOpts,
fetchOpts
});
return { response, checksumError };
};

// `response` `error` events do not necessarily make piped streams error, so we
// need to await either.
export const promiseOrFetchError = async (
promise: Promise<void>,
response: Request,
) => {
export const promiseOrFetchError = async (promise: Promise<void>, response: Request) => {
await Promise.race([promise, throwOnFetchError(response)]);
};

const throwOnFetchError = async (response: Request) => {
const [error] = await once(response, 'error');
const [error] = await once(response, "error");
throw error;
};

// Persist stream to a `node[.exe]` file
export const writeNodeBinary = (tmpFile: string) =>
createWriteStream(tmpFile, { mode: NODE_MODE });
export const writeNodeBinary = (tmpFile: string) => createWriteStream(tmpFile, { mode: NODE_MODE });

const NODE_MODE = 0o755;
43 changes: 24 additions & 19 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import loadLocale from "./locale";
import { Closer, Themes } from "../types";

import type { MenuItemConstructorOptions, OpenDialogOptions } from "electron";
import type { Arch } from "./deps/get-node/archive/types";

let mainWindow: BrowserWindow | null = null,
updater: AppUpdater | null = null,
Expand Down Expand Up @@ -417,26 +418,30 @@ Promise.resolve().then(() => {
return versions;
});

ipcMain.handle("get-node", async (_event, { id, version }: { id: string; version: string }) => {
const abortController = new AbortController();
controllers.set(id, abortController);

try {
const result = await getNode(version, {
output: setting.directory,
mirror: setting.mirror,
signal: abortController.signal,
onProgress: (data) => {
mainWindow?.webContents.send("get-node:progress", id, data);
}
});
return result;
} catch (err) {
return Promise.reject(err.message);
} finally {
controllers.delete(id);
ipcMain.handle(
"get-node",
async (_event, { id, arch, version }: { id: string; arch: Arch; version: string }) => {
const abortController = new AbortController();
controllers.set(id, abortController);

try {
const result = await getNode(version, {
arch,
output: setting.directory,
mirror: setting.mirror,
signal: abortController.signal,
onProgress: (data) => {
mainWindow?.webContents.send("get-node:progress", id, data);
}
});
return result;
} catch (err) {
return Promise.reject(err.message);
} finally {
controllers.delete(id);
}
}
});
);

ipcMain.handle(
"uninstall-node-version",
Expand Down
3 changes: 2 additions & 1 deletion src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ const electronHandler = {
getInstalledNodeVersions: async (refresh: boolean = false): Promise<string[]> =>
ipcRenderer.invoke("installed-node-versions", refresh),

getNode: async (args: { id: string; version: string }) => ipcRenderer.invoke("get-node", args),
getNode: async (args: { id: string; arch: string; version: string }) =>
ipcRenderer.invoke("get-node", args),
controllerAbort: (id: string) => ipcRenderer.invoke("controller:abort", id),

useNodeVersion: (version: string) => ipcRenderer.invoke("use-version", version),
Expand Down
119 changes: 82 additions & 37 deletions src/renderer/src/pages/versions/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
Button,
Label,
LabelCopyable,
Progress
Progress,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue
} from "@renderer/components/ui";
import { toast } from "sonner";

Expand All @@ -24,17 +30,25 @@ type Props = {
onRefrresh: () => void;
};

const archs = ["arm64", "x64", "x86"];

export const InfoModal = forwardRef<Ref, Props>(({ onRefrresh }, ref) => {
const [open, setOpen] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [path, setPath] = useState<string>();
const [progress, setProgress] = useState<Nvmd.ProgressData>();

const record = useRef<Nvmd.Version>();
const arch = useRef<HTMLSpanElement>(null);
const archOption = useRef<string[]>(archs);
const uuid = useRef<string>();

const i18n = useI18n();

const systemArch = ["x86", "x32", "ia32"].includes(window.Context.arch)
? "x86"
: window.Context.arch;

useImperativeHandle(ref, () => ({
show: onShow
}));
Expand All @@ -47,7 +61,19 @@ export const InfoModal = forwardRef<Ref, Props>(({ onRefrresh }, ref) => {
}, []);

const onShow: Ref["show"] = (data) => {
const { files } = data,
platform = window.Context.platform;
const newArchs = archOption.current.filter((arch) => {
const name =
platform === "darwin"
? `osx-${arch}`
: platform === "win32"
? `win-${arch}`
: `${platform}-${arch}`;
return !!files.find((file) => file.includes(name));
});
record.current = data;
archOption.current = newArchs;
setOpen(true);
};

Expand All @@ -59,6 +85,7 @@ export const InfoModal = forwardRef<Ref, Props>(({ onRefrresh }, ref) => {
try {
const { path } = await window.Context.getNode({
id: uuid.current!,
arch: arch.current?.innerText || systemArch,
version: record.current!.version.slice(1)
});
setPath(path);
Expand Down Expand Up @@ -118,42 +145,60 @@ export const InfoModal = forwardRef<Ref, Props>(({ onRefrresh }, ref) => {
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
{path && path !== "error" ? null : loading ? (
<Button variant="destructive" onClick={onAbort}>
{i18n("Cancel")}
</Button>
) : (
<Button
variant="secondary"
onClick={() => {
setOpen(false);
}}
>
{i18n("Cancel")}
</Button>
)}
{path && path !== "error" ? (
<Button
loading={loading}
onClick={() => {
onRefrresh();
setOpen(false);
setTimeout(() => {
record.current = undefined;
uuid.current = undefined;
setPath(undefined);
setProgress(undefined);
}, 0);
}}
>
{i18n("OK")}
</Button>
) : (
<Button loading={loading} onClick={onStart}>
{path === "error" ? i18n("Retry") : i18n("Start-Install")}
</Button>
)}
<AlertDialogFooter className="sm:justify-between">
<p className="flex items-center space-x-2">
<Select disabled={loading} defaultValue={systemArch}>
<SelectTrigger className="w-24 h-6">
<SelectValue ref={arch} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{archOption.current.map((arch) => (
<SelectItem key={arch} value={arch}>
{arch}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</p>
<div className="flex items-center space-x-2">
{path && path !== "error" ? null : loading ? (
<Button variant="destructive" onClick={onAbort}>
{i18n("Cancel")}
</Button>
) : (
<Button
variant="secondary"
onClick={() => {
setOpen(false);
}}
>
{i18n("Cancel")}
</Button>
)}
{path && path !== "error" ? (
<Button
loading={loading}
onClick={() => {
onRefrresh();
setOpen(false);
setTimeout(() => {
record.current = undefined;
uuid.current = undefined;
setPath(undefined);
setProgress(undefined);
}, 0);
}}
>
{i18n("OK")}
</Button>
) : (
<Button loading={loading} onClick={onStart}>
{path === "error" ? i18n("Retry") : i18n("Start-Install")}
</Button>
)}
</div>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down

0 comments on commit e8b7086

Please sign in to comment.