Skip to content

Commit

Permalink
feat: add wine installation feature
Browse files Browse the repository at this point in the history
  • Loading branch information
3Shain committed Mar 7, 2023
1 parent c80eca6 commit aa2026b
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 56 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ node_modules
.DS_Store

*.session
*.neu
*.neu

/wine*
21 changes: 12 additions & 9 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,27 @@ export async function createApp() {
}
}

return () => (
<div>
If you are seeing this, it means everything works fine!
<br /> Current version: {CURRENT_YAAGL_VERSION}
</div>
);

const { wineReady, wineUpdate } = await checkWine();
// return () => (
// <div>
// If you are seeing this, it means everything works fine!
// <br /> Current version: {CURRENT_YAAGL_VERSION}
// </div>
// );

const { wineReady, wineUpdate, wineUpdateTag } = await checkWine();
const prefixPath = await resolve("./wineprefix");
if (wineReady) {
const wine = await createWine({
installDir: "FIXME",
prefix: "FIXME",
});
return await createLauncher({ aria2, wine });
} else {
return await createWineInstallProgram({
aria2,
wineUpdate: wineUpdate!,
wineUpdateTarGzFile: wineUpdate,
wineAbsPrefix: prefixPath,
wineTag: wineUpdateTag,
});
}
}
4 changes: 3 additions & 1 deletion src/aria2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ export async function createAria2({
const status = await rpc.tellStatus(gid);
if(status.status=='paused') {
await rpc.unpause(gid);
} else if(status.status=='complete') {
return;
} else {
throw new Error('FIXME: implmenet me (aria2.ts)')
throw new Error('FIXME: implmenet me (aria2.ts) '+status.status)
}
} catch (e: any) {
if (e && e["code"] == 1) {
Expand Down
12 changes: 2 additions & 10 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createApp } from "./app";
import { HopeProvider } from "@hope-ui/solid";

import "./styles.css";
import { shutdown } from "./utils";
import { fatal } from "./utils";

if (typeof Neutralino == "undefined") {
console.log(`This app doesn't work on browser.`);
Expand All @@ -24,13 +24,5 @@ if (typeof Neutralino == "undefined") {
);
Neutralino.window.show();
})
.catch(async (e) => {
await Neutralino.os.showMessageBox(
"Fatal error",
`Launcher failed to open\n${e}\n${e.stack}`,
"OK"
);
await shutdown();
Neutralino.app.exit(-1);
});
.catch(fatal);
}
78 changes: 56 additions & 22 deletions src/utils/neu.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import { join } from "path-browserify";

export async function resolve(command: string) {
if (command.startsWith("./")) {
command = join(
import.meta.env.PROD ? window.NL_PATH : window.NL_CWD,
command
);
export async function resolve(path: string) {
if (path.startsWith("./")) {
path = join(import.meta.env.PROD ? window.NL_PATH : window.NL_CWD, path);
// await Neutralino.os.showMessageBox("1", command, "OK");
if (!path.startsWith("/") || path == "/")
throw new Error("Assertation failed " + path);
}
return command;
return path;
}

export async function exec(
command: string,
args: string[]
args: string[],
env?: { [key: string]: string },
sudo: boolean = false
): Promise<Neutralino.os.ExecCommandResult> {
const cmd = `"${await resolve(command)}" ${args.join(" ")}`;
const ret = await Neutralino.os.execCommand(cmd, {});
const cmd = `${
env && typeof env == "object"
? Object.keys(env)
.map((key) => {
return `${key}=${env[key]} `;
})
.join()
: ""
}"${await resolve(command)}" ${args
.map((x) => {
if (x.startsWith('"')||x.startsWith("'")) return x;
if (x.indexOf(" ") > -1) return `"${x}"`;
return x;
})
.join(" ")}`;
const ret = sudo ? await runInSudo(cmd) : await Neutralino.os.execCommand(cmd, {});
if (ret.exitCode != 0) {
throw new Error(
`Command return non-zero code\n${cmd}\nStdOut:\n${ret.stdOut}\nStdErr:\n${ret.stdErr}`
Expand All @@ -25,8 +40,19 @@ export async function exec(
return ret;
}

export async function runInSudo(command: string) {
command = command
.replaceAll('"', "\\\\\\\"")
.replaceAll("'", "\\'");
await log(command);
return await Neutralino.os.execCommand(
`osascript -e $'do shell script "${command}" with administrator privileges'`,
{}
);
}

export function tar_extract(src: string, dst: string) {
return exec("tar", ["-xzvf", src, "-C", dst]);
return exec("tar", ["-zxvf", src, "-C", dst]);
}

export async function spawn(command: string, args: string[]) {
Expand Down Expand Up @@ -63,8 +89,13 @@ export function restart() {
}

export async function fatal(error: any) {
await Neutralino.os.showMessageBox("Fatal error", String(error), "OK");
await Neutralino.app.exit(-1);
await Neutralino.os.showMessageBox(
"Fatal error",
`${String(error)}`,
"OK"
);
await shutdown();
Neutralino.app.exit(-1);
}

export async function appendFile(path: string, content: string) {
Expand All @@ -79,37 +110,40 @@ export async function forceMove(source: string, destination: string) {
]);
}

export async function rmrf_dangerously(target: string) {
return await exec("rm", ["-rf", target]);
}

export async function prompt(title: string, message: string) {
const out = await Neutralino.os.showMessageBox(title, message, "YES_NO");
return out == "YES";
}

const hooks: Array<(forced: boolean)=>Promise<boolean>> = [];
const hooks: Array<(forced: boolean) => Promise<boolean>> = [];

export function addTerminationHook(fn: (forced: boolean)=>Promise<boolean>) {
export function addTerminationHook(fn: (forced: boolean) => Promise<boolean>) {
hooks.push(fn);
const len = hooks.length;
return () => {
if(hooks.length!==len) {
throw new Error('Unexpected behavior!');
if (hooks.length !== len) {
throw new Error("Unexpected behavior!");
}
hooks.pop();
}
};
}

// ??
export async function GLOBAL_onClose(forced: boolean) {
for(const hook of hooks.reverse()) {
if(!await hook(forced)&&!forced) {
for (const hook of hooks.reverse()) {
if (!(await hook(forced)) && !forced) {
return false; // aborted
}
}
return true;
}

export async function shutdown() {
for(const hook of hooks.reverse()) {
for (const hook of hooks.reverse()) {
await hook(true);
}
}

2 changes: 1 addition & 1 deletion src/utils/unix.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { exec, resolve } from "./neu";

export async function xattrRemove(attr: string, path: string) {
return await exec(`xattr -r -d ${attr} ${await resolve(path)}`, []);
return await exec(`xattr`, [ '-r', '-d', attr ,`"${await resolve(path)}"`], {}, true);
}

export async function md5(path: string): Promise<string> {
Expand Down
89 changes: 77 additions & 12 deletions src/wine.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { Aria2 } from "./aria2";
import { fatal, getKey, resolve, restart, setKey, tar_extract } from "./utils";
import { CommonUpdateProgram, createCommonUpdateUI } from "./common-update-ui";
import {
exec,
fatal,
getKey,
log,
resolve,
restart,
rmrf_dangerously,
setKey,
tar_extract,
wait,
} from "./utils";
import { xattrRemove } from "./utils/unix";

export async function createWine(options: { prefix: string }) {
export async function createWine(options: {
installDir: string;
prefix: string;
}) {
async function cmd(command: string, args: string[]) {}

async function launch(program: string) {}
Expand All @@ -20,26 +36,75 @@ export async function checkWine() {
if (wineState == "update") {
return {
wineReady: false,
wineUpdate: await getKey("wine_update_target"),
};
wineUpdate: await getKey("wine_update_url"),
wineUpdateTag: await getKey("wine_update_tag"),
} as const;
}
return { wineReady: true };
return { wineReady: true } as const;
} catch (e) {
return { wineReady: false, wineUpdate: "FIXME_RECOMMENT_WINE" };
// FIXME:
return {
wineReady: false,
wineUpdate:
"https://github.com/3Shain/winecx/releases/download/gi-wine-1.0/wine.tar.gz",
wineUpdateTag: "gi-wine-1.0",
} as const;
}
}

export async function createWineInstallProgram({
// github:
aria2,
wineUpdate,
wineUpdateTarGzFile,
wineAbsPrefix,
wineTag,
}: {
aria2: Aria2;
wineUpdate: string;
wineUpdateTarGzFile: string;
wineAbsPrefix: string;
wineTag: string;
}) {
// await Neutralino.window.setSize({width:500, height:500,maxHeight:500, maxWidth:500,minHeight:500, minWidth:500})
async function* program(): CommonUpdateProgram {
yield ["setStateText", "DOWNLOADING_ENVIROMENT"];
for await (const progress of aria2.doStreamingDownload({
uri: wineUpdateTarGzFile,
absDst: await resolve("./wine.tar.gz"),
})) {
yield [
"setProgress",
Number((progress.completedLength * BigInt(100)) / progress.totalLength),
];
}
yield ["setStateText", "EXTRACT_ENVIROMENT"];
yield ["setUndeterminedProgress"];

const wineBinaryDir = await resolve("./wine");
await rmrf_dangerously(wineBinaryDir);
await exec("mkdir", ["-p", wineBinaryDir]);
const p = await tar_extract(await resolve("./wine.tar.gz"), wineBinaryDir);
await log(p.stdOut);
yield ["setStateText", "CONFIGURING_ENVIROMENT"];

await xattrRemove("com.apple.quarantine", wineBinaryDir);

const wine64Bin = await resolve("./wine/bin/wine64");
const d = await exec(
wine64Bin,
["wineboot", "-u"],
{ WINEPREFIX: `"${wineAbsPrefix}"` }
);
await log(d.stdOut);
const g = await exec(
wine64Bin,
["winecfg", "-v", "win10"],
{ WINEPREFIX: `"${wineAbsPrefix}"` }
);
await log(g.stdOut);
await setKey("wine_state", "ready");
await setKey("wine_tag", wineTag);
await setKey("wine_update_url", null);
await setKey("wine_update_tag", null);
}

return function WineInstall() {
return "TODO";
};
return createCommonUpdateUI(program);
}

0 comments on commit aa2026b

Please sign in to comment.