From 2e7b466b99a5fafa2ced57a85e823dd3be6ab84c Mon Sep 17 00:00:00 2001 From: Henry H Date: Wed, 14 Feb 2024 21:32:40 -0800 Subject: [PATCH 1/4] Desktop: Resolves #9857: Back up to a subdirectory of the home directory by default --- .eslintignore | 2 +- .gitignore | 2 +- packages/app-desktop/main.js | 4 +- .../utils/restartInSafeModeFromMain.ts | 4 +- .../io.github.jackgruber.backup.diff | 231 +++++++++++++++++- .../default-plugins/pluginRepositories.json | 2 +- packages/lib/BaseApplication.ts | 5 +- packages/lib/determineBaseAppDirs.ts | 23 ++ packages/lib/determineProfileDir.ts | 16 -- packages/lib/models/Setting.ts | 2 + .../lib/services/plugins/PluginService.ts | 2 +- .../desktopDefaultPluginsInfo.ts | 3 +- 12 files changed, 267 insertions(+), 29 deletions(-) create mode 100644 packages/lib/determineBaseAppDirs.ts delete mode 100644 packages/lib/determineProfileDir.ts diff --git a/.eslintignore b/.eslintignore index 6448ee750db..df3606def20 100644 --- a/.eslintignore +++ b/.eslintignore @@ -679,7 +679,7 @@ packages/lib/components/shared/side-menu-shared.js packages/lib/database-driver-better-sqlite.js packages/lib/database.js packages/lib/debug/DebugService.js -packages/lib/determineProfileDir.js +packages/lib/determineBaseAppDirs.js packages/lib/dom.js packages/lib/errorUtils.js packages/lib/errors.js diff --git a/.gitignore b/.gitignore index a44e69d2517..ba5e1ca8eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -659,7 +659,7 @@ packages/lib/components/shared/side-menu-shared.js packages/lib/database-driver-better-sqlite.js packages/lib/database.js packages/lib/debug/DebugService.js -packages/lib/determineProfileDir.js +packages/lib/determineBaseAppDirs.js packages/lib/dom.js packages/lib/errorUtils.js packages/lib/errors.js diff --git a/packages/app-desktop/main.js b/packages/app-desktop/main.js index 1b0ab7d85b5..415ea49b013 100644 --- a/packages/app-desktop/main.js +++ b/packages/app-desktop/main.js @@ -10,7 +10,7 @@ const FsDriverNode = require('@joplin/lib/fs-driver-node').default; const envFromArgs = require('@joplin/lib/envFromArgs'); const packageInfo = require('./packageInfo.js'); const { isCallbackUrl } = require('@joplin/lib/callbackUrlUtils'); -const determineProfileDir = require('@joplin/lib/determineProfileDir').default; +const determineBaseAppDirs = require('@joplin/lib/determineBaseAppDirs').default; // Electron takes the application name from package.json `name` and // displays this in the tray icon toolip and message box titles, however in @@ -45,7 +45,7 @@ const isDebugMode = !!process.argv && process.argv.indexOf('--debug') >= 0; const appId = `net.cozic.joplin${env === 'dev' ? 'dev' : ''}-desktop`; let appName = env === 'dev' ? 'joplindev' : 'joplin'; if (appId.indexOf('-desktop') >= 0) appName += '-desktop'; -const rootProfileDir = determineProfileDir(profileFromArgs, appName); +const { rootProfileDir } = determineBaseAppDirs(profileFromArgs, appName); const settingsPath = `${rootProfileDir}/settings.json`; let autoUploadCrashDumps = false; diff --git a/packages/app-desktop/utils/restartInSafeModeFromMain.ts b/packages/app-desktop/utils/restartInSafeModeFromMain.ts index 32f994e7897..7b65225b018 100644 --- a/packages/app-desktop/utils/restartInSafeModeFromMain.ts +++ b/packages/app-desktop/utils/restartInSafeModeFromMain.ts @@ -5,7 +5,7 @@ import { safeModeFlagFilename } from '@joplin/lib/BaseApplication'; import initProfile from '@joplin/lib/services/profileConfig/initProfile'; import { writeFile } from 'fs-extra'; import { join } from 'path'; -import determineProfileDir from '@joplin/lib/determineProfileDir'; +import determineBaseAppDirs from '@joplin/lib/determineBaseAppDirs'; const restartInSafeModeFromMain = async () => { @@ -21,7 +21,7 @@ const restartInSafeModeFromMain = async () => { shimInit({}); const startFlags = await processStartFlags(bridge().processArgv()); - const rootProfileDir = determineProfileDir(startFlags.matched.profileDir, appName); + const { rootProfileDir } = determineBaseAppDirs(startFlags.matched.profileDir, appName); const { profileDir } = await initProfile(rootProfileDir); // We can't access the database, so write to a file instead. diff --git a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff index 79d97662477..f50689e0574 100644 --- a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff +++ b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff @@ -1,3 +1,230 @@ +diff --git a/src/Backup.ts b/src/Backup.ts +index 2c0c174..2bd03f3 100644 +--- a/src/Backup.ts ++++ b/src/Backup.ts +@@ -4,6 +4,7 @@ import joplin from "api"; + import * as path from "path"; + import backupLogging from "electron-log"; + import * as fs from "fs-extra"; ++import * as os from "os"; + import { sevenZip } from "./sevenZip"; + import * as moment from "moment"; + import { helper } from "./helper"; +@@ -28,6 +29,7 @@ class Backup { + private compressionLevel: number; + private singleJex: boolean; + private createSubfolder: boolean; ++ private createSubfolderPerProfile: boolean; + private backupSetName: string; + private exportFormat: string; + private execFinishCmd: string; +@@ -272,12 +274,10 @@ class Backup { + ); + } + +- if (this.createSubfolder) { +- this.log.verbose("append subFolder"); +- const orgBackupBasePath = this.backupBasePath; +- this.backupBasePath = path.join(this.backupBasePath, "JoplinBackup"); ++ const origBackupBasePath = this.backupBasePath; ++ const handleSubfolderCreation = async () => { + if ( +- fs.existsSync(orgBackupBasePath) && ++ fs.existsSync(origBackupBasePath) && + !fs.existsSync(this.backupBasePath) + ) { + try { +@@ -286,19 +286,56 @@ class Backup { + await this.showError(i18n.__("msg.error.folderCreation", e.message)); + } + } ++ }; ++ ++ if (this.createSubfolder) { ++ this.log.verbose("append subFolder"); ++ this.backupBasePath = path.join(this.backupBasePath, "JoplinBackup"); ++ await handleSubfolderCreation(); + } + +- if (path.normalize(profileDir) === this.backupBasePath) { +- this.backupBasePath = null; +- await this.showError( +- i18n.__("msg.error.backupPathJoplinDir", path.normalize(profileDir)) ++ if (this.createSubfolderPerProfile) { ++ this.log.verbose("append profile subfolder"); ++ // We assume that Joplin's profile structure is the following ++ // rootProfileDir/ ++ // | profileDir/ ++ // | | [[profile content]] ++ // or, if using the default, ++ // rootProfileDir/ ++ // | [[profile content]] ++ const profileRootDir = await joplin.settings.globalValue( ++ "rootProfileDir" + ); ++ const profileCurrentDir = await joplin.settings.globalValue("profileDir"); ++ ++ let profileName = path.basename(profileCurrentDir); ++ if (profileCurrentDir === profileRootDir) { ++ profileName = "default"; ++ } ++ ++ this.backupBasePath = path.join(this.backupBasePath, profileName); ++ await handleSubfolderCreation(); ++ } ++ ++ const handleInvalidPath = async (errorId: string) => { ++ const invalidBackupPath = this.backupBasePath; ++ this.backupBasePath = null; ++ await this.showError(i18n.__(errorId, invalidBackupPath)); ++ }; ++ ++ if (helper.isSubdirectoryOrEqual(this.backupBasePath, os.homedir())) { ++ await handleInvalidPath("msg.error.backupPathContainsHomeDir"); ++ } else if (helper.isSubdirectoryOrEqual(this.backupBasePath, profileDir)) { ++ await handleInvalidPath("msg.error.backupPathContainsJoplinDir"); + } + } + + public async loadSettings() { + this.log.verbose("loadSettings"); + this.createSubfolder = await joplin.settings.value("createSubfolder"); ++ this.createSubfolderPerProfile = await joplin.settings.value( ++ "createSubfolderPerProfile" ++ ); + await this.loadBackupPath(); + this.backupRetention = await joplin.settings.value("backupRetention"); + +@@ -477,6 +514,7 @@ class Backup { + await this.backupNotebooks(); + + const backupDst = await this.makeBackupSet(); ++ await this.writeReadme(backupDst); + + await joplin.settings.setValue( + "lastBackup", +@@ -684,6 +722,16 @@ class Backup { + } + } + ++ private async writeReadme(backupFolder: string) { ++ const readmePath = path.join(backupFolder, "README.md"); ++ this.log.info("writeReadme to", readmePath); ++ const readmeText = i18n.__( ++ "backupReadme", ++ this.backupStartTime.toLocaleString() ++ ); ++ await fs.writeFile(readmePath, readmeText, "utf8"); ++ } ++ + private async backupNotebooks() { + const notebooks = await this.selectNotebooks(); + +diff --git a/src/helper.ts b/src/helper.ts +index 3726fc2..45eba0c 100644 +--- a/src/helper.ts ++++ b/src/helper.ts +@@ -1,4 +1,5 @@ + import joplin from "api"; ++import * as path from "path"; + + export namespace helper { + export async function validFileName(fileName: string) { +@@ -65,4 +66,28 @@ export namespace helper { + + return result; + } ++ ++ // Doesn't resolve simlinks ++ // See https://stackoverflow.com/questions/44892672/how-to-check-if-two-paths-are-the-same-in-npm ++ // for possible alternative implementations. ++ export function isSubdirectoryOrEqual( ++ parent: string, ++ possibleChild: string, ++ ++ // Testing only ++ pathModule: typeof path = path ++ ) { ++ // Appending path.sep to handle this case: ++ // parent: /a/b/test ++ // possibleChild: /a/b/test2 ++ // "/a/b/test2".startsWith("/a/b/test") -> true, but ++ // "/a/b/test2/".startsWith("/a/b/test/") -> false ++ // ++ // Note that .resolve removes trailing slashes. ++ // ++ const normalizedParent = pathModule.resolve(parent) + pathModule.sep; ++ const normalizedChild = pathModule.resolve(possibleChild) + pathModule.sep; ++ ++ return normalizedChild.startsWith(normalizedParent); ++ } + } +diff --git a/src/locales/de_DE.json b/src/locales/de_DE.json +index 9749df5..1f6b902 100644 +--- a/src/locales/de_DE.json ++++ b/src/locales/de_DE.json +@@ -13,7 +13,7 @@ + "Backup": "Backup Fehler für %s: %s", + "fileCopy": "Fehler beim kopieren von Datei/Ordner in %s: %s", + "deleteFile": "Fehler beim löschen von Datei/Ordner in %s: %s", +- "backupPathJoplinDir": "Als Sicherungs Pfad wurde das Joplin profile Verzeichniss (%s) ohne Unterordner ausgewählt, dies ist nicht erlaubt!", ++ "backupPathContainsJoplinDir": "Als Sicherungs Pfad wurde das Joplin profile Verzeichniss (%s) ohne Unterordner ausgewählt, dies ist nicht erlaubt!", + "BackupSetNotSupportedChars": "Der Name des Backup-Sets enthält nicht zulässige Zeichen ( %s )!", + "passwordDoubleQuotes": "Das Passwort enthält \" (Doppelte Anführungszeichen), diese sind wegen eines Bugs nicht erlaubt. Der Passwortschutz für die Backups wurde deaktivert!" + } +diff --git a/src/locales/en_US.json b/src/locales/en_US.json +index 79b6d55..f9d5325 100644 +--- a/src/locales/en_US.json ++++ b/src/locales/en_US.json +@@ -13,7 +13,8 @@ + "Backup": "Backup error for %s: %s", + "fileCopy": "Error on file/folder copy in %s: %s", + "deleteFile": "Error on file/folder delete in %s: %s", +- "backupPathJoplinDir": "The backup path is the Joplin profile directory (%s) without subfolders, this is not allowed!", ++ "backupPathContainsJoplinDir": "The backup path is or contains the Joplin profile directory (%s) without subfolders, this is not allowed!", ++ "backupPathContainsHomeDir": "The backup path is or contains the home directory (%s). Without enabling the subfolder setting, this is not allowed!", + "BackupSetNotSupportedChars": "Backup set name does contain not allowed characters ( %s )!", + "passwordDoubleQuotes": "Password contains \" (double quotes), these are not allowed because of a bug. Password protection for the backup is deactivated!" + } +@@ -57,6 +58,10 @@ + "label": "Create Subfolder", + "description": "Create a subfolder in the the configured `Backup path`. Deactivate only if there is no other data in the `Backup path`!" + }, ++ "createSubfolderPerProfile": { ++ "label": "Create subfolder for Joplin profile", ++ "description": "Create a subfolder within the backup directory for the current profile. This allows multiple profiles from the same Joplin installation to use the same backup directory without overwriting backups made from other profiles. All profiles that use the same backup directory must have this setting enabled." ++ }, + "zipArchive": { + "label": "Create archive", + "description": "If a password protected backups is set, an archive is always created" +@@ -86,6 +91,7 @@ + "description": "Execute command when backup is finished" + } + }, ++ "backupReadme": "# Joplin Backup\n\nThis folder contains one or more backups of data from the Joplin note taking application. The most recent backup was created on %s.\n\nSee the [Simple Backup documentation](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) for information about how to restore from this backup.", + "command": { + "createBackup": "Create backup" + } +diff --git a/src/settings.ts b/src/settings.ts +index bd0c69b..e20c5c2 100644 +--- a/src/settings.ts ++++ b/src/settings.ts +@@ -136,6 +136,15 @@ export namespace Settings { + label: i18n.__("settings.createSubfolder.label"), + description: i18n.__("settings.createSubfolder.description"), + }, ++ createSubfolderPerProfile: { ++ value: false, ++ type: SettingItemType.Bool, ++ section: "backupSection", ++ public: true, ++ advanced: true, ++ label: i18n.__("settings.createSubfolderPerProfile.label"), ++ description: i18n.__("settings.createSubfolderPerProfile.description"), ++ }, + zipArchive: { + value: "no", + type: SettingItemType.String, diff --git a/src/sevenZip.ts b/src/sevenZip.ts index ef2a527..d98c777 100644 --- a/src/sevenZip.ts @@ -38,10 +265,10 @@ index ef2a527..d98c777 100644 export async function setExecutionFlag() { if (process.platform !== "win32") { diff --git a/webpack.config.js b/webpack.config.js -index 34a1797..7b2a480 100644 +index b32f37f..9b445d2 100644 --- a/webpack.config.js +++ b/webpack.config.js -@@ -200,15 +200,9 @@ const pluginConfig = { ...baseConfig, entry: './src/index.ts', +@@ -205,15 +205,9 @@ const pluginConfig = { ...baseConfig, entry: './src/index.ts', path: distDir, }, plugins: [ diff --git a/packages/default-plugins/pluginRepositories.json b/packages/default-plugins/pluginRepositories.json index 9fc02715529..a43a4d4f024 100644 --- a/packages/default-plugins/pluginRepositories.json +++ b/packages/default-plugins/pluginRepositories.json @@ -2,6 +2,6 @@ "io.github.jackgruber.backup": { "cloneUrl": "https://github.com/JackGruber/joplin-plugin-backup.git", "branch": "master", - "commit": "bd49c665bf60c1e0dd9b9862b2ba69cad3d4c9ae" + "commit": "a8f29fdd8153c34862c22a9290c4577dcf4b1834" } } diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 8df9cdd60c1..fc6b472daf4 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -61,7 +61,7 @@ import RotatingLogs from './RotatingLogs'; import { NoteEntity } from './services/database/types'; import { join } from 'path'; import processStartFlags from './utils/processStartFlags'; -import determineProfileDir from './determineProfileDir'; +import determineProfileAndBaseDir from './determineBaseAppDirs'; const appLogger: LoggerWrapper = Logger.create('App'); @@ -639,7 +639,7 @@ export default class BaseApplication { // https://immerjs.github.io/immer/docs/freezing setAutoFreeze(initArgs.env === 'dev'); - const rootProfileDir = options.rootProfileDir ? options.rootProfileDir : determineProfileDir(initArgs.profileDir, appName); + const { rootProfileDir, homeDir } = determineProfileAndBaseDir(options.rootProfileDir ?? initArgs.profileDir, appName); const { profileDir, profileConfig, isSubProfile } = await initProfile(rootProfileDir); this.profileConfig_ = profileConfig; @@ -655,6 +655,7 @@ export default class BaseApplication { Setting.setConstant('pluginDataDir', `${profileDir}/plugin-data`); Setting.setConstant('cacheDir', cacheDir); Setting.setConstant('pluginDir', `${rootProfileDir}/plugins`); + Setting.setConstant('homeDir', homeDir); SyncTargetRegistry.addClass(SyncTargetNone); SyncTargetRegistry.addClass(SyncTargetFilesystem); diff --git a/packages/lib/determineBaseAppDirs.ts b/packages/lib/determineBaseAppDirs.ts new file mode 100644 index 00000000000..92198e09ef3 --- /dev/null +++ b/packages/lib/determineBaseAppDirs.ts @@ -0,0 +1,23 @@ +import { homedir } from 'os'; +import { toSystemSlashes } from './path-utils'; + +export default (profileFromArgs: string, appName: string) => { + let profileDir = ''; + let homeDir = ''; + + if (profileFromArgs) { + profileDir = profileFromArgs; + homeDir = profileDir; + } else if (process && process.env && process.env.PORTABLE_EXECUTABLE_DIR) { + profileDir = `${process.env.PORTABLE_EXECUTABLE_DIR}/JoplinProfile`; + homeDir = process.env.PORTABLE_EXECUTABLE_DIR; + } else { + profileDir = `${homedir()}/.config/${appName}`; + homeDir = homedir(); + } + + return { + rootProfileDir: toSystemSlashes(profileDir, 'linux'), + homeDir: toSystemSlashes(homeDir, 'linux'), + }; +}; diff --git a/packages/lib/determineProfileDir.ts b/packages/lib/determineProfileDir.ts deleted file mode 100644 index b37e576fb7d..00000000000 --- a/packages/lib/determineProfileDir.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { homedir } from 'os'; -import { toSystemSlashes } from './path-utils'; - -export default (profileFromArgs: string, appName: string) => { - let output = ''; - - if (profileFromArgs) { - output = profileFromArgs; - } else if (process && process.env && process.env.PORTABLE_EXECUTABLE_DIR) { - output = `${process.env.PORTABLE_EXECUTABLE_DIR}/JoplinProfile`; - } else { - output = `${homedir()}/.config/${appName}`; - } - - return toSystemSlashes(output, 'linux'); -}; diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 7cb3fbcd8a2..27ecf1e6b7d 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -146,6 +146,7 @@ export interface Constants { pluginDataDir: string; cacheDir: string; pluginDir: string; + homeDir: string; flagOpenDevTools: boolean; syncVersion: number; startupDevPlugins: string[]; @@ -303,6 +304,7 @@ class Setting extends BaseModel { pluginDataDir: '', cacheDir: '', pluginDir: '', + homeDir: '', flagOpenDevTools: false, syncVersion: 3, startupDevPlugins: [], diff --git a/packages/lib/services/plugins/PluginService.ts b/packages/lib/services/plugins/PluginService.ts index 6784c0338a5..cfbee5d909a 100644 --- a/packages/lib/services/plugins/PluginService.ts +++ b/packages/lib/services/plugins/PluginService.ts @@ -29,7 +29,7 @@ export interface Plugins { } export interface SettingAndValue { - [settingName: string]: string; + [settingName: string]: string|number|boolean; } export interface DefaultPluginSettings { diff --git a/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts b/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts index ff7daae62d9..2696280c7bb 100644 --- a/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts +++ b/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts @@ -6,7 +6,8 @@ const getDefaultPluginsInfo = (): DefaultPluginsInfo => { const defaultPlugins = { 'io.github.jackgruber.backup': { settings: { - 'path': `${Setting.value('profileDir')}`, + 'path': `${Setting.value('homeDir')}`, + 'createSubfolderPerProfile': true, }, // Joplin Portable is more likely to run on a device with low write speeds From 4ddbda5e03bf977728253537aac12cd47b31a099 Mon Sep 17 00:00:00 2001 From: Henry H Date: Wed, 14 Feb 2024 22:54:00 -0800 Subject: [PATCH 2/4] Dev mode: Back up to different directory --- .../io.github.jackgruber.backup.diff | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff index f50689e0574..8d98ad7c4d0 100644 --- a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff +++ b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff @@ -1,5 +1,5 @@ diff --git a/src/Backup.ts b/src/Backup.ts -index 2c0c174..2bd03f3 100644 +index 2c0c174..6d492ca 100644 --- a/src/Backup.ts +++ b/src/Backup.ts @@ -4,6 +4,7 @@ import joplin from "api"; @@ -34,7 +34,7 @@ index 2c0c174..2bd03f3 100644 !fs.existsSync(this.backupBasePath) ) { try { -@@ -286,19 +286,56 @@ class Backup { +@@ -286,19 +286,62 @@ class Backup { await this.showError(i18n.__("msg.error.folderCreation", e.message)); } } @@ -69,6 +69,12 @@ index 2c0c174..2bd03f3 100644 + profileName = "default"; + } + ++ // Appending a -dev to the profile name prevents a devmode default Joplin ++ // profile from overwriting a non-devmode Joplin profile. ++ if ((await joplin.settings.globalValue("env")) === "dev") { ++ profileName += "-dev"; ++ } ++ + this.backupBasePath = path.join(this.backupBasePath, profileName); + await handleSubfolderCreation(); + } @@ -95,7 +101,7 @@ index 2c0c174..2bd03f3 100644 await this.loadBackupPath(); this.backupRetention = await joplin.settings.value("backupRetention"); -@@ -477,6 +514,7 @@ class Backup { +@@ -477,6 +520,7 @@ class Backup { await this.backupNotebooks(); const backupDst = await this.makeBackupSet(); @@ -103,7 +109,7 @@ index 2c0c174..2bd03f3 100644 await joplin.settings.setValue( "lastBackup", -@@ -684,6 +722,16 @@ class Backup { +@@ -684,6 +728,16 @@ class Backup { } } From 6e918b30b049b060322f042346f7c5bbc89df409 Mon Sep 17 00:00:00 2001 From: Henry H Date: Sat, 17 Feb 2024 08:45:58 -0800 Subject: [PATCH 3/4] Update README creation behavior --- .../plugin-patches/io.github.jackgruber.backup.diff | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff index 8d98ad7c4d0..880608841e3 100644 --- a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff +++ b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff @@ -1,5 +1,5 @@ diff --git a/src/Backup.ts b/src/Backup.ts -index 2c0c174..6d492ca 100644 +index 2c0c174..9cfbbe1 100644 --- a/src/Backup.ts +++ b/src/Backup.ts @@ -4,6 +4,7 @@ import joplin from "api"; @@ -101,14 +101,14 @@ index 2c0c174..6d492ca 100644 await this.loadBackupPath(); this.backupRetention = await joplin.settings.value("backupRetention"); -@@ -477,6 +520,7 @@ class Backup { - await this.backupNotebooks(); +@@ -478,6 +521,7 @@ class Backup { const backupDst = await this.makeBackupSet(); -+ await this.writeReadme(backupDst); ++ await this.writeReadme(this.backupBasePath); await joplin.settings.setValue( "lastBackup", + this.backupStartTime.getTime() @@ -684,6 +728,16 @@ class Backup { } } From 40014257a5437af4dee55086f5090ff321830b0d Mon Sep 17 00:00:00 2001 From: Henry H Date: Thu, 22 Feb 2024 08:11:45 -0800 Subject: [PATCH 4/4] Update Simple Backup version --- .../io.github.jackgruber.backup.diff | 237 +----------------- .../default-plugins/pluginRepositories.json | 2 +- 2 files changed, 3 insertions(+), 236 deletions(-) diff --git a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff index 880608841e3..79d97662477 100644 --- a/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff +++ b/packages/default-plugins/plugin-patches/io.github.jackgruber.backup.diff @@ -1,236 +1,3 @@ -diff --git a/src/Backup.ts b/src/Backup.ts -index 2c0c174..9cfbbe1 100644 ---- a/src/Backup.ts -+++ b/src/Backup.ts -@@ -4,6 +4,7 @@ import joplin from "api"; - import * as path from "path"; - import backupLogging from "electron-log"; - import * as fs from "fs-extra"; -+import * as os from "os"; - import { sevenZip } from "./sevenZip"; - import * as moment from "moment"; - import { helper } from "./helper"; -@@ -28,6 +29,7 @@ class Backup { - private compressionLevel: number; - private singleJex: boolean; - private createSubfolder: boolean; -+ private createSubfolderPerProfile: boolean; - private backupSetName: string; - private exportFormat: string; - private execFinishCmd: string; -@@ -272,12 +274,10 @@ class Backup { - ); - } - -- if (this.createSubfolder) { -- this.log.verbose("append subFolder"); -- const orgBackupBasePath = this.backupBasePath; -- this.backupBasePath = path.join(this.backupBasePath, "JoplinBackup"); -+ const origBackupBasePath = this.backupBasePath; -+ const handleSubfolderCreation = async () => { - if ( -- fs.existsSync(orgBackupBasePath) && -+ fs.existsSync(origBackupBasePath) && - !fs.existsSync(this.backupBasePath) - ) { - try { -@@ -286,19 +286,62 @@ class Backup { - await this.showError(i18n.__("msg.error.folderCreation", e.message)); - } - } -+ }; -+ -+ if (this.createSubfolder) { -+ this.log.verbose("append subFolder"); -+ this.backupBasePath = path.join(this.backupBasePath, "JoplinBackup"); -+ await handleSubfolderCreation(); - } - -- if (path.normalize(profileDir) === this.backupBasePath) { -- this.backupBasePath = null; -- await this.showError( -- i18n.__("msg.error.backupPathJoplinDir", path.normalize(profileDir)) -+ if (this.createSubfolderPerProfile) { -+ this.log.verbose("append profile subfolder"); -+ // We assume that Joplin's profile structure is the following -+ // rootProfileDir/ -+ // | profileDir/ -+ // | | [[profile content]] -+ // or, if using the default, -+ // rootProfileDir/ -+ // | [[profile content]] -+ const profileRootDir = await joplin.settings.globalValue( -+ "rootProfileDir" - ); -+ const profileCurrentDir = await joplin.settings.globalValue("profileDir"); -+ -+ let profileName = path.basename(profileCurrentDir); -+ if (profileCurrentDir === profileRootDir) { -+ profileName = "default"; -+ } -+ -+ // Appending a -dev to the profile name prevents a devmode default Joplin -+ // profile from overwriting a non-devmode Joplin profile. -+ if ((await joplin.settings.globalValue("env")) === "dev") { -+ profileName += "-dev"; -+ } -+ -+ this.backupBasePath = path.join(this.backupBasePath, profileName); -+ await handleSubfolderCreation(); -+ } -+ -+ const handleInvalidPath = async (errorId: string) => { -+ const invalidBackupPath = this.backupBasePath; -+ this.backupBasePath = null; -+ await this.showError(i18n.__(errorId, invalidBackupPath)); -+ }; -+ -+ if (helper.isSubdirectoryOrEqual(this.backupBasePath, os.homedir())) { -+ await handleInvalidPath("msg.error.backupPathContainsHomeDir"); -+ } else if (helper.isSubdirectoryOrEqual(this.backupBasePath, profileDir)) { -+ await handleInvalidPath("msg.error.backupPathContainsJoplinDir"); - } - } - - public async loadSettings() { - this.log.verbose("loadSettings"); - this.createSubfolder = await joplin.settings.value("createSubfolder"); -+ this.createSubfolderPerProfile = await joplin.settings.value( -+ "createSubfolderPerProfile" -+ ); - await this.loadBackupPath(); - this.backupRetention = await joplin.settings.value("backupRetention"); - -@@ -478,6 +521,7 @@ class Backup { - - const backupDst = await this.makeBackupSet(); - -+ await this.writeReadme(this.backupBasePath); - await joplin.settings.setValue( - "lastBackup", - this.backupStartTime.getTime() -@@ -684,6 +728,16 @@ class Backup { - } - } - -+ private async writeReadme(backupFolder: string) { -+ const readmePath = path.join(backupFolder, "README.md"); -+ this.log.info("writeReadme to", readmePath); -+ const readmeText = i18n.__( -+ "backupReadme", -+ this.backupStartTime.toLocaleString() -+ ); -+ await fs.writeFile(readmePath, readmeText, "utf8"); -+ } -+ - private async backupNotebooks() { - const notebooks = await this.selectNotebooks(); - -diff --git a/src/helper.ts b/src/helper.ts -index 3726fc2..45eba0c 100644 ---- a/src/helper.ts -+++ b/src/helper.ts -@@ -1,4 +1,5 @@ - import joplin from "api"; -+import * as path from "path"; - - export namespace helper { - export async function validFileName(fileName: string) { -@@ -65,4 +66,28 @@ export namespace helper { - - return result; - } -+ -+ // Doesn't resolve simlinks -+ // See https://stackoverflow.com/questions/44892672/how-to-check-if-two-paths-are-the-same-in-npm -+ // for possible alternative implementations. -+ export function isSubdirectoryOrEqual( -+ parent: string, -+ possibleChild: string, -+ -+ // Testing only -+ pathModule: typeof path = path -+ ) { -+ // Appending path.sep to handle this case: -+ // parent: /a/b/test -+ // possibleChild: /a/b/test2 -+ // "/a/b/test2".startsWith("/a/b/test") -> true, but -+ // "/a/b/test2/".startsWith("/a/b/test/") -> false -+ // -+ // Note that .resolve removes trailing slashes. -+ // -+ const normalizedParent = pathModule.resolve(parent) + pathModule.sep; -+ const normalizedChild = pathModule.resolve(possibleChild) + pathModule.sep; -+ -+ return normalizedChild.startsWith(normalizedParent); -+ } - } -diff --git a/src/locales/de_DE.json b/src/locales/de_DE.json -index 9749df5..1f6b902 100644 ---- a/src/locales/de_DE.json -+++ b/src/locales/de_DE.json -@@ -13,7 +13,7 @@ - "Backup": "Backup Fehler für %s: %s", - "fileCopy": "Fehler beim kopieren von Datei/Ordner in %s: %s", - "deleteFile": "Fehler beim löschen von Datei/Ordner in %s: %s", -- "backupPathJoplinDir": "Als Sicherungs Pfad wurde das Joplin profile Verzeichniss (%s) ohne Unterordner ausgewählt, dies ist nicht erlaubt!", -+ "backupPathContainsJoplinDir": "Als Sicherungs Pfad wurde das Joplin profile Verzeichniss (%s) ohne Unterordner ausgewählt, dies ist nicht erlaubt!", - "BackupSetNotSupportedChars": "Der Name des Backup-Sets enthält nicht zulässige Zeichen ( %s )!", - "passwordDoubleQuotes": "Das Passwort enthält \" (Doppelte Anführungszeichen), diese sind wegen eines Bugs nicht erlaubt. Der Passwortschutz für die Backups wurde deaktivert!" - } -diff --git a/src/locales/en_US.json b/src/locales/en_US.json -index 79b6d55..f9d5325 100644 ---- a/src/locales/en_US.json -+++ b/src/locales/en_US.json -@@ -13,7 +13,8 @@ - "Backup": "Backup error for %s: %s", - "fileCopy": "Error on file/folder copy in %s: %s", - "deleteFile": "Error on file/folder delete in %s: %s", -- "backupPathJoplinDir": "The backup path is the Joplin profile directory (%s) without subfolders, this is not allowed!", -+ "backupPathContainsJoplinDir": "The backup path is or contains the Joplin profile directory (%s) without subfolders, this is not allowed!", -+ "backupPathContainsHomeDir": "The backup path is or contains the home directory (%s). Without enabling the subfolder setting, this is not allowed!", - "BackupSetNotSupportedChars": "Backup set name does contain not allowed characters ( %s )!", - "passwordDoubleQuotes": "Password contains \" (double quotes), these are not allowed because of a bug. Password protection for the backup is deactivated!" - } -@@ -57,6 +58,10 @@ - "label": "Create Subfolder", - "description": "Create a subfolder in the the configured `Backup path`. Deactivate only if there is no other data in the `Backup path`!" - }, -+ "createSubfolderPerProfile": { -+ "label": "Create subfolder for Joplin profile", -+ "description": "Create a subfolder within the backup directory for the current profile. This allows multiple profiles from the same Joplin installation to use the same backup directory without overwriting backups made from other profiles. All profiles that use the same backup directory must have this setting enabled." -+ }, - "zipArchive": { - "label": "Create archive", - "description": "If a password protected backups is set, an archive is always created" -@@ -86,6 +91,7 @@ - "description": "Execute command when backup is finished" - } - }, -+ "backupReadme": "# Joplin Backup\n\nThis folder contains one or more backups of data from the Joplin note taking application. The most recent backup was created on %s.\n\nSee the [Simple Backup documentation](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) for information about how to restore from this backup.", - "command": { - "createBackup": "Create backup" - } -diff --git a/src/settings.ts b/src/settings.ts -index bd0c69b..e20c5c2 100644 ---- a/src/settings.ts -+++ b/src/settings.ts -@@ -136,6 +136,15 @@ export namespace Settings { - label: i18n.__("settings.createSubfolder.label"), - description: i18n.__("settings.createSubfolder.description"), - }, -+ createSubfolderPerProfile: { -+ value: false, -+ type: SettingItemType.Bool, -+ section: "backupSection", -+ public: true, -+ advanced: true, -+ label: i18n.__("settings.createSubfolderPerProfile.label"), -+ description: i18n.__("settings.createSubfolderPerProfile.description"), -+ }, - zipArchive: { - value: "no", - type: SettingItemType.String, diff --git a/src/sevenZip.ts b/src/sevenZip.ts index ef2a527..d98c777 100644 --- a/src/sevenZip.ts @@ -271,10 +38,10 @@ index ef2a527..d98c777 100644 export async function setExecutionFlag() { if (process.platform !== "win32") { diff --git a/webpack.config.js b/webpack.config.js -index b32f37f..9b445d2 100644 +index 34a1797..7b2a480 100644 --- a/webpack.config.js +++ b/webpack.config.js -@@ -205,15 +205,9 @@ const pluginConfig = { ...baseConfig, entry: './src/index.ts', +@@ -200,15 +200,9 @@ const pluginConfig = { ...baseConfig, entry: './src/index.ts', path: distDir, }, plugins: [ diff --git a/packages/default-plugins/pluginRepositories.json b/packages/default-plugins/pluginRepositories.json index a43a4d4f024..2aca406e329 100644 --- a/packages/default-plugins/pluginRepositories.json +++ b/packages/default-plugins/pluginRepositories.json @@ -2,6 +2,6 @@ "io.github.jackgruber.backup": { "cloneUrl": "https://github.com/JackGruber/joplin-plugin-backup.git", "branch": "master", - "commit": "a8f29fdd8153c34862c22a9290c4577dcf4b1834" + "commit": "2d814a5466604daced108331d14aedf8e8414d62" } }