diff --git a/ElectronJS/.gitignore b/ElectronJS/.gitignore index f2d8a6f..de8e71e 100644 --- a/ElectronJS/.gitignore +++ b/ElectronJS/.gitignore @@ -16,6 +16,7 @@ execution_instances/* info.log npminstall-debug.log mysql_config.json +macos_command_folder.json EasySpider_en/ EasySpider_zh/ TempUserDataFolder/ diff --git a/ElectronJS/main.js b/ElectronJS/main.js index 7d226ac..73344b2 100644 --- a/ElectronJS/main.js +++ b/ElectronJS/main.js @@ -36,6 +36,64 @@ let config_context = JSON.parse( fs.readFileSync(path.join(task_server.getDir(), `config.json`), "utf8") ); //仅在当前进程中使用,不会写入文件 +const macOSCommandFolderConfigPath = path.join( + task_server.getDir(), + "macos_command_folder.json" +); + +function getMacOSCommandLauncher(folder) { + if (!folder) { + return ""; + } + const absoluteFolder = path.resolve(folder); + const appPath = path.join(absoluteFolder, "EasySpider.app"); + const scriptPath = path.join(absoluteFolder, "execute_macos.sh"); + const executablePath = path.join(absoluteFolder, "easyspider_executestage"); + if (!fs.existsSync(appPath) || !fs.existsSync(executablePath)) { + return ""; + } + // New packages include the wrapper so it can resolve its own directory. + // Retain compatibility with older portable releases that only contain + // the execution-stage binary. + return fs.existsSync(scriptPath) ? scriptPath : executablePath; +} + +function readSavedMacOSCommandFolder() { + try { + const saved = JSON.parse( + fs.readFileSync(macOSCommandFolderConfigPath, "utf8") + ); + return getMacOSCommandLauncher(saved.folder) ? path.resolve(saved.folder) : ""; + } catch { + return ""; + } +} + +function detectMacOSCommandFolder() { + const savedFolder = readSavedMacOSCommandFolder(); + if (savedFolder) { + return savedFolder; + } + if (process.platform !== "darwin" || !app.isPackaged) { + return ""; + } + // This resolves to the directory containing EasySpider.app during a + // normal launch. Under App Translocation it resolves to .../d, which + // does not contain the portable executor and therefore fails validation. + const appBundlePath = path.resolve(process.resourcesPath, "../.."); + const candidate = path.dirname(appBundlePath); + return getMacOSCommandLauncher(candidate) ? candidate : ""; +} + +function saveMacOSCommandFolder(folder) { + const absoluteFolder = path.resolve(folder); + fs.writeFileSync( + macOSCommandFolderConfigPath, + JSON.stringify({folder: absoluteFolder}, null, 2) + ); + return absoluteFolder; +} + if (config.debug) { let logPath = "info.log"; let logFile = fs.createWriteStream(logPath, {flags: "a"}); @@ -1126,7 +1184,19 @@ async function beginInvoke(msg, ws) { ws.send( JSON.stringify({ config_folder: task_server.getDir() + "/", - easyspider_location: task_server.getEasySpiderLocation(), + // Never expose an App Translocation directory as a runnable + // command-line location. macOS uses a validated portable + // release folder instead. + easyspider_location: + process.platform === "darwin" + ? detectMacOSCommandFolder() + : task_server.getEasySpiderLocation(), + macos_command_folder: + process.platform === "darwin" ? detectMacOSCommandFolder() : "", + macos_command_launcher: + process.platform === "darwin" + ? getMacOSCommandLauncher(detectMacOSCommandFolder()) + : "", execution_started, }) ); @@ -1155,6 +1225,59 @@ async function beginInvoke(msg, ws) { } catch { console.log("Cannot get Cookies"); } + } else if (msg.type == 8) { + if (process.platform !== "darwin") { + ws.send(JSON.stringify({ + response_type: "macos_command_folder", + canceled: true, + error: "The portable execution folder is only used on macOS.", + })); + return; + } + const currentFolder = detectMacOSCommandFolder(); + const parentWindow = + invoke_window && !invoke_window.isDestroyed() + ? invoke_window + : mainWindow; + const selection = await dialog.showOpenDialog(parentWindow, { + title: + language === "zh" + ? "选择 EasySpider_MacOS 发布文件夹" + : "Select the EasySpider_MacOS release folder", + defaultPath: currentFolder || app.getPath("downloads"), + properties: ["openDirectory"], + message: + language === "zh" + ? "请选择同时包含 EasySpider.app 和 easyspider_executestage 的文件夹" + : "Select the folder containing EasySpider.app and easyspider_executestage", + }); + if (selection.canceled || selection.filePaths.length === 0) { + ws.send(JSON.stringify({ + response_type: "macos_command_folder", + canceled: true, + })); + return; + } + const selectedFolder = path.resolve(selection.filePaths[0]); + const launcher = getMacOSCommandLauncher(selectedFolder); + if (!launcher) { + ws.send(JSON.stringify({ + response_type: "macos_command_folder", + canceled: false, + error: + language === "zh" + ? "所选文件夹必须同时包含 EasySpider.app 和 easyspider_executestage。" + : "The selected folder must contain both EasySpider.app and easyspider_executestage.", + })); + return; + } + saveMacOSCommandFolder(selectedFolder); + ws.send(JSON.stringify({ + response_type: "macos_command_folder", + canceled: false, + macos_command_folder: selectedFolder, + macos_command_launcher: launcher, + })); } else if (msg.type == 30) { send_message_to_browser( JSON.stringify({ diff --git a/ElectronJS/package_macos.sh b/ElectronJS/package_macos.sh index bf5b4c6..47044b0 100755 --- a/ElectronJS/package_macos.sh +++ b/ElectronJS/package_macos.sh @@ -50,6 +50,7 @@ rm -rf "$STAGE_DIR/Code" "$STAGE_DIR/Sample Tasks" "$STAGE_DIR/Data" \ rm -f "$STAGE_DIR/config.json" "$STAGE_DIR/mysql_config.json" mkdir -p "$STAGE_DIR/Code" "$STAGE_DIR/Sample Tasks" "$STAGE_DIR/Data" \ "$STAGE_DIR/execution_instances" +cp "$SCRIPT_DIR/execute_macos.sh" "$STAGE_DIR/execute_macos.sh" cp "$REPO_ROOT/ExecuteStage"/*.py "$STAGE_DIR/Code/" cp "$REPO_ROOT/ExecuteStage/requirements.txt" "$STAGE_DIR/Code/" cp "$REPO_ROOT/ExecuteStage/Readme.md" "$STAGE_DIR/Code/" diff --git a/ElectronJS/src/taskGrid/executeTask.html b/ElectronJS/src/taskGrid/executeTask.html index a6e66bb..c2fb605 100644 --- a/ElectronJS/src/taskGrid/executeTask.html +++ b/ElectronJS/src/taskGrid/executeTask.html @@ -92,8 +92,17 @@ - - +