diff --git a/.eslintignore b/.eslintignore index cd2be4a55..812906926 100644 --- a/.eslintignore +++ b/.eslintignore @@ -444,6 +444,8 @@ packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js packages/app-desktop/tools/copy7Zip.js packages/app-desktop/tools/notarizeMacApp.js packages/app-desktop/tools/renameReleaseAssets.js +packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js +packages/app-desktop/utils/7zip/pathToBundled7Zip.js packages/app-desktop/utils/checkForUpdatesUtils.test.js packages/app-desktop/utils/checkForUpdatesUtils.js packages/app-desktop/utils/checkForUpdatesUtilsTestData.js diff --git a/.gitignore b/.gitignore index ca8193cc9..bb5e41ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -424,6 +424,8 @@ packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js packages/app-desktop/tools/copy7Zip.js packages/app-desktop/tools/notarizeMacApp.js packages/app-desktop/tools/renameReleaseAssets.js +packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js +packages/app-desktop/utils/7zip/pathToBundled7Zip.js packages/app-desktop/utils/checkForUpdatesUtils.test.js packages/app-desktop/utils/checkForUpdatesUtils.js packages/app-desktop/utils/checkForUpdatesUtilsTestData.js diff --git a/packages/app-desktop/services/plugins/PluginRunner.ts b/packages/app-desktop/services/plugins/PluginRunner.ts index 81d2b12d5..ef48c18df 100644 --- a/packages/app-desktop/services/plugins/PluginRunner.ts +++ b/packages/app-desktop/services/plugins/PluginRunner.ts @@ -7,6 +7,7 @@ import Setting from '@joplin/lib/models/Setting'; import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds'; import shim from '@joplin/lib/shim'; import Logger from '@joplin/utils/Logger'; +import getPathToExecutable7Zip from '../../utils/7zip/getPathToExecutable7Zip'; // import BackOffHandler from './BackOffHandler'; const ipcRenderer = require('electron').ipcRenderer; @@ -120,11 +121,15 @@ export default class PluginRunner extends BasePluginRunner { bridge().electronApp().registerPluginWindow(plugin.id, pluginWindow); + const libraryData = { + pathTo7za: await getPathToExecutable7Zip(), + }; + void pluginWindow.loadURL(`${require('url').format({ pathname: require('path').join(__dirname, 'plugin_index.html'), protocol: 'file:', slashes: true, - })}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}`); + })}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}&libraryData=${encodeURIComponent(JSON.stringify(libraryData))}`); if (plugin.devMode) { pluginWindow.webContents.once('dom-ready', () => { diff --git a/packages/app-desktop/services/plugins/plugin_index.js b/packages/app-desktop/services/plugins/plugin_index.js index f42f1b99c..94fa53684 100644 --- a/packages/app-desktop/services/plugins/plugin_index.js +++ b/packages/app-desktop/services/plugins/plugin_index.js @@ -2,7 +2,6 @@ // TODO: Not sure if that will work once packaged in Electron const sandboxProxy = require('../../vendor/lib/@joplin/lib/services/plugins/sandboxProxy.js'); const ipcRenderer = require('electron').ipcRenderer; - const nodePath = require('path'); const ipcRendererSend = (message, args) => { try { @@ -15,6 +14,7 @@ const urlParams = new URLSearchParams(window.location.search); const pluginId = urlParams.get('pluginId'); + const libraryData = JSON.parse(decodeURIComponent(urlParams.get('libraryData'))); let eventId_ = 1; const eventHandlers_ = {}; @@ -63,20 +63,7 @@ // 7zip-bin is required by one of the default plugins (simple-backup) if (modulePath === '7zip-bin') { - // 7zip-bin is very large -- return the path to a version of 7zip - // copied from 7zip-bin. - const executableName = process.platform === 'win32' ? '7za.exe' : '7za'; - - let rootDir = nodePath.dirname(nodePath.dirname(__dirname)); - - // When bundled, __dirname points to a file within app.asar. The build/ directory - // is outside of app.asar, and thus, we need an extra dirname(...). - if (nodePath.basename(rootDir).startsWith('app.asar')) { - rootDir = nodePath.dirname(rootDir); - } - - const pathTo7za = nodePath.join(rootDir, 'build', '7zip', executableName); - return { path7za: nodePath.resolve(pathTo7za) }; + return { path7za: libraryData.pathTo7za }; } throw new Error(`Module not found: ${modulePath}`); diff --git a/packages/app-desktop/utils/7zip/getPathToExecutable7Zip.ts b/packages/app-desktop/utils/7zip/getPathToExecutable7Zip.ts new file mode 100644 index 000000000..b75b312d9 --- /dev/null +++ b/packages/app-desktop/utils/7zip/getPathToExecutable7Zip.ts @@ -0,0 +1,58 @@ +import Setting from '@joplin/lib/models/Setting'; +import pathToBundled7Zip from './pathToBundled7Zip'; +import { join } from 'path'; +import shim from '@joplin/lib/shim'; + +const pathTo7Za_: undefined|string = undefined; + +const getPathToExecutable7Zip = async () => { + if (pathTo7Za_) { + return pathTo7Za_; + } + + const { baseDir: bundled7ZipDir, executableName, fullPath: bundled7ZipExecutablePath } = pathToBundled7Zip(); + let pathTo7Za = bundled7ZipExecutablePath; + + // On Linux (and perhaps Free/OpenBSD?), the bundled 7zip binary can't be executed + // in its default location and must be moved. + if (!shim.isMac() && !shim.isWindows()) { + const targetDir = join(Setting.value('cacheDir'), '7zip'); + + const fsDriver = shim.fsDriver(); + const executablePath = join(targetDir, executableName); + + let needsUpdate; + + // The 7Zip binary may already be copied, in which case, it may not need to be updated. + if (await shim.fsDriver().exists(targetDir)) { + const currentChecksum = await fsDriver.md5File(executablePath); + const bundledChecksum = await fsDriver.md5File(bundled7ZipExecutablePath); + + if (currentChecksum !== bundledChecksum) { + needsUpdate = true; + await shim.fsDriver().remove(targetDir); + } else { + needsUpdate = false; + } + } else { + needsUpdate = true; + } + + if (needsUpdate) { + await shim.fsDriver().mkdir(targetDir); + await shim.fsDriver().copy(bundled7ZipDir, targetDir); + + // Make executable. + // Self: read+write+execute + // Group: read+execute + // Other: none + await shim.fsDriver().chmod(executablePath, 0o750); + } + + pathTo7Za = executablePath; + } + + return pathTo7Za; +}; + +export default getPathToExecutable7Zip; diff --git a/packages/app-desktop/utils/7zip/pathToBundled7Zip.ts b/packages/app-desktop/utils/7zip/pathToBundled7Zip.ts new file mode 100644 index 000000000..939043ab4 --- /dev/null +++ b/packages/app-desktop/utils/7zip/pathToBundled7Zip.ts @@ -0,0 +1,21 @@ +import { join, resolve, basename, dirname } from 'path'; + +const pathToBundled7Zip = () => { + // 7zip-bin is very large -- return the path to a version of 7zip + // copied from 7zip-bin. + const executableName = process.platform === 'win32' ? '7za.exe' : '7za'; + + let rootDir = dirname(dirname(__dirname)); + + // When bundled, __dirname points to a file within app.asar. The build/ directory + // is outside of app.asar, and thus, we need an extra dirname(...). + if (basename(rootDir).startsWith('app.asar')) { + rootDir = dirname(rootDir); + } + + const baseDir = join(rootDir, 'build', '7zip'); + + return { baseDir, executableName, fullPath: resolve(join(baseDir, executableName)) }; +}; + +export default pathToBundled7Zip;