diff --git a/src/main/logwriter.js b/src/main/logwriter.js index cb0e685..bf6097d 100644 --- a/src/main/logwriter.js +++ b/src/main/logwriter.js @@ -139,9 +139,8 @@ async function exportLog(payload = {}) { /** * Export ONLY one LOG (the active tab) as a single .log file carrying the - * experiment-field header on top. Always lands in the in-project LOG_OUTPUT - * folder (ignores any custom output root) so a half-finished experiment can be - * shared for confirmation before the full export. + * experiment-field header on top. Honors the custom Output Root when one is + * set, otherwise falls back to the default LOG_OUTPUT folder. * Returns { ok, single, targetDir, filePath, fileName, files } or { ok:false, error }. */ async function exportSingleLog(payload = {}) { @@ -151,6 +150,7 @@ async function exportSingleLog(payload = {}) { tester = '', testCase = '', notes = '', + outputBase = '', customFields = [], log = {}, abbrevLen, @@ -174,9 +174,9 @@ async function exportSingleLog(payload = {}) { .filter((f) => f.label || f.value) : []; - // Single-LOG output: a dedicated folder under the in-project LOG_OUTPUT, - // with the one .log file (carrying the experiment header) inside it. - const baseDir = defaultOutputDir(); + // Single-LOG output: a dedicated folder under the chosen Output Root (or the + // default LOG_OUTPUT when none is set), with the one .log file inside. + const baseDir = outputBase && outputBase.trim() ? outputBase.trim() : defaultOutputDir(); const now = new Date(); const nameMax = Math.min(40, Math.max(1, parseInt(abbrevLen, 10) || 30)); const folderName = `${dateStamp(now)}_${timeStamp4(now)}_${abbreviate(experimentName, nameMax)}_${typeName}`; diff --git a/src/main/paths.js b/src/main/paths.js index 273a698..a933fc5 100644 --- a/src/main/paths.js +++ b/src/main/paths.js @@ -8,13 +8,14 @@ */ 'use strict'; +const fs = require('fs'); const path = require('path'); const { app } = require('electron'); /** - * Base directory used as the writable root for output. - * - Packaged: the folder that contains the installed M2_LOG.exe - * (per-user install under %LOCALAPPDATA%\Programs\M2_LOG is writable). + * Base directory used as the read root for bundled resources (e.g. highlight + * rules) and as the preferred output root. + * - Packaged: the folder that contains the installed M2_LOG.exe. * - Dev: the project root. */ function appBaseDir() { @@ -22,9 +23,36 @@ function appBaseDir() { return path.join(__dirname, '..', '..'); } -/** Default output root: \LOG_OUTPUT */ +/** True if `dir` can be created and written to (Windows ACLs need a real probe). */ +function canWrite(dir) { + try { + fs.mkdirSync(dir, { recursive: true }); + const probe = path.join(dir, `.m2log-write-test-${process.pid}-${Date.now()}.tmp`); + fs.writeFileSync(probe, ''); + fs.unlinkSync(probe); + return true; + } catch (e) { + return false; + } +} + +let cachedOutputDir = null; + +/** + * Default output root for exports and the LOG Analysis view. + * Prefers \LOG_OUTPUT (portable / per-user install). When the app + * is installed to a protected location (e.g. C:\Program Files) that folder is + * not writable, so we fall back to \M2_LOG\LOG_OUTPUT. + */ function defaultOutputDir() { - return path.join(appBaseDir(), 'LOG_OUTPUT'); + if (cachedOutputDir) return cachedOutputDir; + const primary = path.join(appBaseDir(), 'LOG_OUTPUT'); + if (!app.isPackaged || canWrite(primary)) { + cachedOutputDir = primary; + } else { + cachedOutputDir = path.join(app.getPath('documents'), 'M2_LOG', 'LOG_OUTPUT'); + } + return cachedOutputDir; } module.exports = { appBaseDir, defaultOutputDir }; diff --git a/src/renderer/i18n/en.json b/src/renderer/i18n/en.json index a8c5570..42d6e14 100644 --- a/src/renderer/i18n/en.json +++ b/src/renderer/i18n/en.json @@ -35,7 +35,7 @@ "btn.clear": "Clear", "btn.clear.title": "Clear current LOG", "btn.exportSingle": "Export this LOG", - "btn.exportSingle.title": "Export only the current LOG (with the experiment header) to the in-project LOG_OUTPUT", + "btn.exportSingle.title": "Export only the current LOG (with the experiment header) to the Output Root (or LOG_OUTPUT if empty)", "toast.exportSingleOk": "This LOG exported (with header) to LOG_OUTPUT", "log.type.ph": "LOG type, e.g. UEFI", "log.content.ph": "Paste LOG here...", diff --git a/src/renderer/i18n/zh.json b/src/renderer/i18n/zh.json index 81069b4..9a22fbe 100644 --- a/src/renderer/i18n/zh.json +++ b/src/renderer/i18n/zh.json @@ -35,7 +35,7 @@ "btn.clear": "清除", "btn.clear.title": "清空目前 LOG", "btn.exportSingle": "輸出此 LOG", - "btn.exportSingle.title": "只輸出目前這個 LOG(含實驗欄位表頭)到專案 LOG_OUTPUT", + "btn.exportSingle.title": "只輸出目前這個 LOG(含實驗欄位表頭)到輸出根目錄(留空則為 LOG_OUTPUT)", "toast.exportSingleOk": "已輸出此 LOG(含實驗表頭)到 LOG_OUTPUT", "log.type.ph": "LOG 種類,例如 UEFI", "log.content.ph": "在此貼上 LOG...", diff --git a/src/renderer/index.html b/src/renderer/index.html index 6ef145a..292b481 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -175,7 +175,7 @@

實驗欄位

0 行 · 0 字元 -