From a424e3c899e68d515bdca388618dbf02a0f1b199 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 7 Sep 2020 22:12:51 +0100 Subject: [PATCH] Desktop, Cli: Fixes #3689: Fixed note export when there are folders with non-existing parents. Also fixed long path issue on Windows. --- CliClient/tests/pathUtils.js | 2 +- CliClient/tests/services_InteropService.js | 24 +++++++++++++- .../gui/KeymapConfig/utils/getLabel.js | 33 ------------------- ReactNativeClient/lib/path-utils.js | 5 ++- .../lib/services/InteropService.js | 6 ++-- 5 files changed, 32 insertions(+), 38 deletions(-) delete mode 100644 ElectronClient/gui/KeymapConfig/utils/getLabel.js diff --git a/CliClient/tests/pathUtils.js b/CliClient/tests/pathUtils.js index d4b4b0c7b..6ffd55a94 100644 --- a/CliClient/tests/pathUtils.js +++ b/CliClient/tests/pathUtils.js @@ -24,7 +24,7 @@ describe('pathUtils', function() { ['no space at the end ', 'no space at the end'], ['nor dots...', 'nor dots'], [' no space before either', 'no space before either'], - ['thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong', 'thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong'], + ['thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong', 'thatsreallylongthatsreallylongthatsreallylongthats'], ]; for (let i = 0; i < testCases.length; i++) { diff --git a/CliClient/tests/services_InteropService.js b/CliClient/tests/services_InteropService.js index 166241a33..61cf0a2d0 100644 --- a/CliClient/tests/services_InteropService.js +++ b/CliClient/tests/services_InteropService.js @@ -3,7 +3,7 @@ require('app-module-path').addPath(__dirname); const { time } = require('lib/time-utils.js'); -const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const { asyncTest, fileContentEqual, expectNotThrow, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); const InteropService = require('lib/services/InteropService.js'); const Folder = require('lib/models/Folder.js'); const Note = require('lib/models/Note.js'); @@ -442,4 +442,26 @@ describe('services_InteropService', function() { expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote2.md`)).toBe(true); })); + it('should not try to export folders with a non-existing parent', asyncTest(async () => { + // Handles and edge case where user has a folder but this folder with a parent + // that doesn't exist. Can happen for example in this case: + // + // - folder1/folder2 + // - Client 1 sync folder2, but not folder1 + // - Client 2 sync and get folder2 only + // - Client 2 export data + // => Crash if we don't handle this case + + await Folder.save({ title: 'orphan', parent_id: '0c5bbd8a1b5a48189484a412a7e534cc' }); + + const service = new InteropService(); + + const result = await service.export({ + path: exportDir(), + format: 'md', + }); + + expect(result.warnings.length).toBe(0); + })); + }); diff --git a/ElectronClient/gui/KeymapConfig/utils/getLabel.js b/ElectronClient/gui/KeymapConfig/utils/getLabel.js deleted file mode 100644 index 9963ef63b..000000000 --- a/ElectronClient/gui/KeymapConfig/utils/getLabel.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -const CommandService_1 = require('../../../lib/services/CommandService'); -const { _ } = require('lib/locale'); -const { shim } = require('lib/shim'); -const commandService = CommandService_1.default.instance(); -const getLabel = (commandName) => { - if (commandService.exists(commandName)) { return commandService.label(commandName); } - // Some commands are not registered in CommandService at the moment - // Following hard-coded labels are used as a workaround - switch (commandName) { - case 'quit': - return _('Quit'); - case 'insertTemplate': - return _('Insert template'); - case 'zoomActualSize': - return _('Actual Size'); - case 'gotoAnything': - return _('Goto Anything...'); - case 'help': - return _('Website and documentation'); - case 'hideApp': - return _('Hide Joplin'); - case 'closeWindow': - return _('Close Window'); - case 'config': - return shim.isMac() ? _('Preferences') : _('Options'); - default: - throw new Error(`Command: ${commandName} is unknown`); - } -}; -exports.default = getLabel; -// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0TGFiZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJnZXRMYWJlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlFQUFrRTtBQUVsRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBQ3BDLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7QUFFckMsTUFBTSxjQUFjLEdBQUcsd0JBQWMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUVqRCxNQUFNLFFBQVEsR0FBRyxDQUFDLFdBQW1CLEVBQUUsRUFBRTtJQUN4QyxJQUFJLGNBQWMsQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDO1FBQUUsT0FBTyxjQUFjLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRWpGLG1FQUFtRTtJQUNuRSx1REFBdUQ7SUFFdkQsUUFBUSxXQUFXLEVBQUU7UUFDckIsS0FBSyxNQUFNO1lBQ1YsT0FBTyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEIsS0FBSyxnQkFBZ0I7WUFDcEIsT0FBTyxDQUFDLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUM3QixLQUFLLGdCQUFnQjtZQUNwQixPQUFPLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUN6QixLQUFLLGNBQWM7WUFDbEIsT0FBTyxDQUFDLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUM5QixLQUFLLE1BQU07WUFDVixPQUFPLENBQUMsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO1FBQ3ZDLEtBQUssU0FBUztZQUNiLE9BQU8sQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ3pCLEtBQUssYUFBYTtZQUNqQixPQUFPLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUMxQixLQUFLLFFBQVE7WUFDWixPQUFPLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDdkQ7WUFDQyxNQUFNLElBQUksS0FBSyxDQUFDLFlBQVksV0FBVyxhQUFhLENBQUMsQ0FBQztLQUN0RDtBQUNGLENBQUMsQ0FBQztBQUVGLGtCQUFlLFFBQVEsQ0FBQyJ9 diff --git a/ReactNativeClient/lib/path-utils.js b/ReactNativeClient/lib/path-utils.js index 5d6081056..afab955e0 100644 --- a/ReactNativeClient/lib/path-utils.js +++ b/ReactNativeClient/lib/path-utils.js @@ -61,7 +61,10 @@ for (let i = 0; i < 32; i++) { const friendlySafeFilename_blackListNames = ['.', '..', 'CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']; function friendlySafeFilename(e, maxLength = null) { - if (maxLength === null) maxLength = 255; + // Although Windows supports paths up to 255 characters, but that includes the filename and its + // parent directory path. Also there's generally no good reason for dir or file names + // to be so long, so keep it at 50, which should prevent various errors. + if (maxLength === null) maxLength = 50; if (!e || !e.replace) return _('Untitled'); let output = ''; diff --git a/ReactNativeClient/lib/services/InteropService.js b/ReactNativeClient/lib/services/InteropService.js index ae6ec306b..4432eed70 100644 --- a/ReactNativeClient/lib/services/InteropService.js +++ b/ReactNativeClient/lib/services/InteropService.js @@ -276,7 +276,9 @@ class InteropService { const exportedNoteIds = []; let resourceIds = []; - const folderIds = await Folder.allIds(); + + // Recursively get all the folders that have valid parents + const folderIds = await Folder.childrenIds('', true); let fullSourceFolderIds = sourceFolderIds.slice(); for (let i = 0; i < sourceFolderIds.length; i++) { @@ -327,7 +329,7 @@ class InteropService { await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]); } - const exporter = this.newModuleFromPath_('exporter', options);// this.newModuleByFormat_('exporter', exportFormat); + const exporter = this.newModuleFromPath_('exporter', options); await exporter.init(exportPath, options); const typeOrder = [BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE, BaseModel.TYPE_NOTE, BaseModel.TYPE_TAG, BaseModel.TYPE_NOTE_TAG];