You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Tidy settings and sync creation
This commit is contained in:
		| @@ -10,20 +10,30 @@ class Command extends BaseCommand { | ||||
| 	} | ||||
|  | ||||
| 	description() { | ||||
| 		return _('Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.'); | ||||
| 		return _("Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration."); | ||||
| 	} | ||||
|  | ||||
| 	async action(args) { | ||||
|  | ||||
| 		const renderKeyValue = (name) => { | ||||
| 			const value = Setting.value(name); | ||||
| 			if (Setting.isEnum(name)) { | ||||
| 				return _('%s = %s (%s)', name, value, Setting.enumOptionLabel(name, value)); | ||||
| 			} else { | ||||
| 				return _('%s = %s', name, value); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (!args.name && !args.value) { | ||||
| 			let keys = Setting.publicKeys(); | ||||
| 			for (let i = 0; i < keys.length; i++) { | ||||
| 				this.log(keys[i] + ' = ' + Setting.value(keys[i])); | ||||
| 				this.log(renderKeyValue(keys[i])); | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (args.name && !args.value) { | ||||
| 			this.log(args.name + ' = ' + Setting.value(args.name)); | ||||
| 			this.log(renderKeyValue(args.name)); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class Command extends BaseCommand { | ||||
|  | ||||
| 	async action(args) { | ||||
| 		let service = new ReportService(); | ||||
| 		let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target'))); | ||||
| 		let report = await service.status(Setting.value('sync.target')); | ||||
|  | ||||
| 		for (let i = 0; i < report.length; i++) { | ||||
| 			let section = report[i]; | ||||
|   | ||||
| @@ -42,7 +42,7 @@ async function createClients() { | ||||
| 	for (let clientId = 0; clientId < 2; clientId++) { | ||||
| 		let client = createClient(clientId); | ||||
| 		promises.push(fs.remove(client.profileDir)); | ||||
| 		promises.push(execCommand(client, 'config sync.target filesystem').then(() => { return execCommand(client, 'config sync.filesystem.path ' + syncDir); })); | ||||
| 		promises.push(execCommand(client, 'config sync.target 2').then(() => { return execCommand(client, 'config sync.2.path ' + syncDir); })); | ||||
| 		output.push(client); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -70,6 +70,14 @@ msgid "" | ||||
| "current configuration." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s = %s (%s)" | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s = %s" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "Duplicates the notes matching <pattern> to [notebook]. If no notebook is " | ||||
| "specified the note is duplicated in the current notebook." | ||||
| @@ -276,8 +284,8 @@ msgid "Please open this URL in your browser to authenticate the application:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "Please set the \"sync.filesystem.path\" config value to the desired " | ||||
| "synchronisation destination." | ||||
| "Please set the \"sync.2.path\" config value to the desired synchronisation " | ||||
| "destination." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| @@ -338,6 +346,14 @@ msgstr "" | ||||
| msgid "Cannot move note to \"%s\" notebook" | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "Invalid option value: \"%s\". Possible values are: %s." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s (%s)" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Synchronisation target" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -66,6 +66,7 @@ msgstr "Affiche tous les détails de la note." | ||||
| msgid "Cannot find \"%s\"." | ||||
| msgstr "Impossible de trouver \"%s\"." | ||||
|  | ||||
| #, fuzzy | ||||
| msgid "" | ||||
| "Gets or sets a config value. If [value] is not provided, it will show the " | ||||
| "value of [name]. If neither [name] nor [value] is provided, it will list the " | ||||
| @@ -75,6 +76,14 @@ msgstr "" | ||||
| "fournie, la valeur de [nom] est affichée. Si ni le [nom] ni la [valeur] ne " | ||||
| "sont fournies, la configuration complète est affichée." | ||||
|  | ||||
| #, fuzzy, javascript-format | ||||
| msgid "%s = %s (%s)" | ||||
| msgstr "%s %s (%s)" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s = %s" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "Duplicates the notes matching <pattern> to [notebook]. If no notebook is " | ||||
| "specified the note is duplicated in the current notebook." | ||||
| @@ -311,8 +320,8 @@ msgstr "" | ||||
| "logiciel :" | ||||
|  | ||||
| msgid "" | ||||
| "Please set the \"sync.filesystem.path\" config value to the desired " | ||||
| "synchronisation destination." | ||||
| "Please set the \"sync.2.path\" config value to the desired synchronisation " | ||||
| "destination." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| @@ -373,6 +382,14 @@ msgstr "Impossible de copier la note dans le carnet \"%s\"" | ||||
| msgid "Cannot move note to \"%s\" notebook" | ||||
| msgstr "Impossible de déplacer la note vers le carnet \"%s\"" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "Invalid option value: \"%s\". Possible values are: %s." | ||||
| msgstr "" | ||||
|  | ||||
| #, fuzzy, javascript-format | ||||
| msgid "%s (%s)" | ||||
| msgstr "%s %s (%s)" | ||||
|  | ||||
| #, fuzzy | ||||
| msgid "Synchronisation target" | ||||
| msgstr "Cible de la synchronisation : %s" | ||||
|   | ||||
| @@ -70,6 +70,14 @@ msgid "" | ||||
| "current configuration." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s = %s (%s)" | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s = %s" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "Duplicates the notes matching <pattern> to [notebook]. If no notebook is " | ||||
| "specified the note is duplicated in the current notebook." | ||||
| @@ -276,8 +284,8 @@ msgid "Please open this URL in your browser to authenticate the application:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "Please set the \"sync.filesystem.path\" config value to the desired " | ||||
| "synchronisation destination." | ||||
| "Please set the \"sync.2.path\" config value to the desired synchronisation " | ||||
| "destination." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| @@ -338,6 +346,14 @@ msgstr "" | ||||
| msgid "Cannot move note to \"%s\" notebook" | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "Invalid option value: \"%s\". Possible values are: %s." | ||||
| msgstr "" | ||||
|  | ||||
| #, javascript-format | ||||
| msgid "%s (%s)" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Synchronisation target" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ async function localItemsSameAsRemote(locals, expect) { | ||||
| 			expect(!!remote).toBe(true); | ||||
| 			if (!remote) continue; | ||||
|  | ||||
| 			if (syncTargetId() == Database.enumId('syncTarget', 'filesystem')) { | ||||
| 			if (syncTargetId() == Setting.SYNC_TARGET_FILESYSTEM) { | ||||
| 				expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000); | ||||
| 			} else { | ||||
| 				expect(remote.updated_time).toBe(dbItem.updated_time); | ||||
| @@ -142,29 +142,20 @@ describe('Synchronizer', function() { | ||||
| 		await switchClient(2); | ||||
|  | ||||
| 		await synchronizer().start(); | ||||
|  | ||||
| 		await sleep(0.1); | ||||
|  | ||||
| 		let note2 = await Note.load(note1.id); | ||||
| 		note2.title = "Updated on client 2"; | ||||
| 		await Note.save(note2); | ||||
| 		note2 = await Note.load(note2.id); | ||||
|  | ||||
| 		await synchronizer().start(); | ||||
|  | ||||
| 		await switchClient(1); | ||||
|  | ||||
| 		await sleep(0.1); | ||||
|  | ||||
| 		let note2conf = await Note.load(note1.id); | ||||
| 		note2conf.title = "Updated on client 1"; | ||||
| 		await Note.save(note2conf); | ||||
| 		note2conf = await Note.load(note1.id); | ||||
|  | ||||
| 		await synchronizer().start(); | ||||
|  | ||||
| 		let conflictedNotes = await Note.conflictedNotes(); | ||||
|  | ||||
| 		expect(conflictedNotes.length).toBe(1); | ||||
|  | ||||
| 		// Other than the id (since the conflicted note is a duplicate), and the is_conflict property | ||||
|   | ||||
| @@ -29,10 +29,12 @@ Resource.fsDriver_ = fsDriver; | ||||
| const logDir = __dirname + '/../tests/logs'; | ||||
| fs.mkdirpSync(logDir, 0o755); | ||||
|  | ||||
| //const syncTarget = 'filesystem'; | ||||
| const syncTarget = 'memory'; | ||||
| //const syncTargetId_ = Setting.SYNC_TARGET_MEMORY; | ||||
| const syncTargetId_ = Setting.SYNC_TARGET_FILESYSTEM; | ||||
| const syncDir = __dirname + '/../tests/sync'; | ||||
|  | ||||
| const sleepTime = syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM ? 1001 : 200; | ||||
|  | ||||
| const logger = new Logger(); | ||||
| logger.addTarget('file', { path: logDir + '/log.txt' }); | ||||
| logger.setLevel(Logger.LEVEL_DEBUG); | ||||
| @@ -47,7 +49,7 @@ Setting.setConstant('appId', 'net.cozic.joplin-cli'); | ||||
| Setting.setConstant('appType', 'cli'); | ||||
|  | ||||
| function syncTargetId() { | ||||
| 	return JoplinDatabase.enumId('syncTarget', syncTarget); | ||||
| 	return syncTargetId_; | ||||
| } | ||||
|  | ||||
| function sleep(n) { | ||||
| @@ -59,7 +61,7 @@ function sleep(n) { | ||||
| } | ||||
|  | ||||
| async function switchClient(id) { | ||||
| 	await time.msleep(200); // Always leave a little time so that updated_time properties don't overlap | ||||
| 	await time.msleep(sleepTime); // Always leave a little time so that updated_time properties don't overlap | ||||
| 	await Setting.saveAll(); | ||||
|  | ||||
| 	currentClient_ = id; | ||||
| @@ -120,7 +122,7 @@ async function setupDatabaseAndSynchronizer(id = null) { | ||||
| 		synchronizers_[id].setLogger(logger); | ||||
| 	} | ||||
|  | ||||
| 	if (syncTarget == 'filesystem') { | ||||
| 	if (syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM) { | ||||
| 		fs.removeSync(syncDir) | ||||
| 		fs.mkdirpSync(syncDir, 0o755); | ||||
| 	} else { | ||||
| @@ -141,17 +143,19 @@ function synchronizer(id = null) { | ||||
| function fileApi() { | ||||
| 	if (fileApi_) return fileApi_; | ||||
|  | ||||
| 	if (syncTarget == 'filesystem') { | ||||
| 	if (syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM) { | ||||
| 		fs.removeSync(syncDir) | ||||
| 		fs.mkdirpSync(syncDir, 0o755); | ||||
| 		fileApi_ = new FileApi(syncDir, new FileApiDriverLocal()); | ||||
| 		fileApi_.setLogger(logger); | ||||
| 		return fileApi_; | ||||
| 	} else { | ||||
| 		fileApi_ = new FileApi('/root', new FileApiDriverMemory()); | ||||
| 		fileApi_.setLogger(logger); | ||||
| 		return fileApi_; | ||||
| 	}		 | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	fileApi_.setLogger(logger); | ||||
| 	fileApi_.setSyncTargetId(syncTargetId_); | ||||
| 	return fileApi_; | ||||
| } | ||||
|  | ||||
| export { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId }; | ||||
| @@ -41,7 +41,7 @@ class StatusScreenComponent extends BaseScreenComponent { | ||||
|  | ||||
| 	async resfreshScreen() { | ||||
| 		let service = new ReportService(); | ||||
| 		let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target'))); | ||||
| 		let report = await service.status(Setting.value('sync.target')); | ||||
| 		this.setState({ report: report }); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -157,20 +157,6 @@ class Database { | ||||
| 		throw new Error('Unknown enum type or value: ' + type + ', ' + s); | ||||
| 	} | ||||
|  | ||||
| 	static enumName(type, id) { | ||||
| 		if (type == 'syncTarget') { | ||||
| 			if (id === 1) return 'memory'; | ||||
| 			if (id === 2) return 'filesystem'; | ||||
| 			if (id === 3) return 'onedrive'; | ||||
| 		} | ||||
| 		throw new Error('Unknown enum type or id: ' + type + ', ' + id); | ||||
| 	} | ||||
|  | ||||
| 	static enumIds(type) { | ||||
| 		if (type == 'syncTarget') return [1,2,3]; | ||||
| 		throw new Error('Unknown enum type: ' + type); | ||||
| 	} | ||||
|  | ||||
| 	static formatValue(type, value) { | ||||
| 		if (value === null || value === undefined) return null; | ||||
| 		if (type == this.TYPE_INT) return Number(value); | ||||
|   | ||||
| @@ -4,16 +4,21 @@ import moment from 'moment'; | ||||
| import { BaseItem } from 'lib/models/base-item.js'; | ||||
| import { time } from 'lib/time-utils.js'; | ||||
|  | ||||
| // NOTE: when synchronising with the file system the time resolution is the second (unlike milliseconds for OneDrive for instance). | ||||
| // What it means is that if, for example, client 1 changes a note at time t, and client 2 changes the same note within the same second, | ||||
| // both clients will not know about each others updates during the next sync. They will simply both sync their note and whoever | ||||
| // comes last will overwrite (on the remote storage) the note of the other client. Both client will then have a different note at | ||||
| // that point and that will only be resolved if one of them changes the note and sync (if they don't change it, it will never get resolved). | ||||
| //  | ||||
| // This is compound with the fact that we can't have a reliable delta API on the file system so we need to check all the timestamps | ||||
| // every time and rely on this exclusively to know about changes. | ||||
| // | ||||
| // This explains occasional failures of the fuzzing program (it finds that the clients end up with two different notes after sync). To | ||||
| // check that it is indeed the problem, check log-database.txt of both clients, search for the note ID, and most likely both notes | ||||
| // will have been modified at the same exact second at some point. If not, it's another bug that needs to be investigated. | ||||
|  | ||||
| class FileApiDriverLocal { | ||||
|  | ||||
| 	syncTargetId() { | ||||
| 		return 2; | ||||
| 	} | ||||
|  | ||||
| 	syncTargetName() { | ||||
| 		return 'filesystem'; | ||||
| 	} | ||||
|  | ||||
| 	fsErrorToJsError_(error) { | ||||
| 		let msg = error.toString(); | ||||
| 		let output = new Error(msg); | ||||
|   | ||||
| @@ -2,14 +2,6 @@ import { time } from 'lib/time-utils.js'; | ||||
|  | ||||
| class FileApiDriverMemory { | ||||
|  | ||||
| 	syncTargetId() { | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	syncTargetName() { | ||||
| 		return 'memory'; | ||||
| 	} | ||||
|  | ||||
| 	constructor() { | ||||
| 		this.items_ = []; | ||||
| 		this.deletedItems_ = []; | ||||
|   | ||||
| @@ -9,14 +9,6 @@ class FileApiDriverOneDrive { | ||||
| 		this.api_ = api; | ||||
| 	} | ||||
|  | ||||
| 	syncTargetId() { | ||||
| 		return 3; | ||||
| 	} | ||||
|  | ||||
| 	syncTargetName() { | ||||
| 		return 'onedrive'; | ||||
| 	} | ||||
|  | ||||
| 	api() { | ||||
| 		return this.api_; | ||||
| 	} | ||||
|   | ||||
| @@ -7,12 +7,22 @@ class FileApi { | ||||
| 		this.baseDir_ = baseDir; | ||||
| 		this.driver_ = driver; | ||||
| 		this.logger_ = new Logger(); | ||||
| 		this.syncTargetId_ = null; | ||||
| 	} | ||||
|  | ||||
| 	driver() { | ||||
| 		return this.driver_; | ||||
| 	} | ||||
|  | ||||
| 	setSyncTargetId(v) { | ||||
| 		this.syncTargetId_ = v; | ||||
| 	} | ||||
|  | ||||
| 	syncTargetId() { | ||||
| 		if (this.syncTargetId_ === null) throw new Error('syncTargetId has not been set!!'); | ||||
| 		return this.syncTargetId_; | ||||
| 	} | ||||
|  | ||||
| 	supportsDelta() { | ||||
| 		return this.driver_.supportsDelta(); | ||||
| 	} | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { BaseModel } from 'lib/base-model.js'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { Setting } from 'lib/models/setting.js'; | ||||
| import { time } from 'lib/time-utils.js'; | ||||
| import { sprintf } from 'sprintf-js'; | ||||
| import moment from 'moment'; | ||||
| @@ -136,7 +137,7 @@ class BaseItem extends BaseModel { | ||||
| 		await super.batchDelete(ids, options); | ||||
|  | ||||
| 		if (trackDeleted) { | ||||
| 			const syncTargetIds = Database.enumIds('syncTarget'); | ||||
| 			const syncTargetIds = Setting.enumOptionValues('sync.target'); | ||||
| 			let queries = []; | ||||
| 			let now = time.unixMs(); | ||||
| 			for (let i = 0; i < ids.length; i++) { | ||||
| @@ -313,10 +314,6 @@ class BaseItem extends BaseModel { | ||||
| 			limit); | ||||
|  | ||||
| 			let neverSyncedItem = await ItemClass.modelSelectAll(sql); | ||||
| 			//for (let i = 0; i < neverSyncedItem.length; i++) neverSyncedItem[i].sync_time = 0; | ||||
|  | ||||
| 			// console.info(sql); | ||||
| 			// console.info('NEVER', neverSyncedItem); | ||||
|  | ||||
| 			// Secondly get the items that have been synced under this sync target but that have been changed since then | ||||
|  | ||||
| @@ -344,8 +341,6 @@ class BaseItem extends BaseModel { | ||||
| 				changedItems = await ItemClass.modelSelectAll(sql); | ||||
| 			} | ||||
|  | ||||
| 			// console.info('CHANGED', changedItems); | ||||
|  | ||||
| 			const items = neverSyncedItem.concat(changedItems); | ||||
|  | ||||
| 			if (i >= classNames.length - 1) { | ||||
| @@ -353,63 +348,6 @@ class BaseItem extends BaseModel { | ||||
| 			} else { | ||||
| 				if (items.length) return { hasMore: true, items: items }; | ||||
| 			} | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 			//let extraWhere = className == 'Note' ? 'AND is_conflict = 0' : ''; | ||||
|  | ||||
| 			// First get all the items that have never been synced under this sync target | ||||
|  | ||||
| 			// let sql = sprintf(` | ||||
| 			// 	SELECT %s FROM %s items | ||||
| 			// 	LEFT JOIN sync_items t ON t.item_id = items.id | ||||
| 			// 	WHERE (t.id IS NULL OR t.sync_target != %d) %s | ||||
| 			// 	LIMIT %d | ||||
| 			// `, | ||||
| 			// this.db().escapeFields(fieldNames), | ||||
| 			// this.db().escapeField(ItemClass.tableName()), | ||||
| 			// Number(syncTarget), | ||||
| 			// extraWhere, | ||||
| 			// limit); | ||||
|  | ||||
| 			// let neverSyncedItem = await ItemClass.modelSelectAll(sql); | ||||
| 			// for (let i = 0; i < neverSyncedItem.length; i++) neverSyncedItem[i].sync_time = 0; | ||||
|  | ||||
| 			// console.info(sql); | ||||
| 			// console.info('NEVER', neverSyncedItem); | ||||
|  | ||||
| 			// // Secondly get the items that have been synced under this sync target but that have been changed since then | ||||
|  | ||||
| 			// const newLimit = limit - neverSyncedItem.length; | ||||
|  | ||||
| 			// let changedItems = []; | ||||
|  | ||||
| 			// if (newLimit > 0) { | ||||
| 			// 	let sql = sprintf(` | ||||
| 			// 		SELECT %s FROM %s items | ||||
| 			// 		LEFT JOIN sync_items t ON t.item_id = items.id | ||||
| 			// 		WHERE (t.sync_time < items.updated_time AND t.sync_target = %d) %s | ||||
| 			// 		LIMIT %d | ||||
| 			// 	`, | ||||
| 			// 	this.db().escapeFields(fieldNames), | ||||
| 			// 	this.db().escapeField(ItemClass.tableName()), | ||||
| 			// 	Number(syncTarget), | ||||
| 			// 	extraWhere, | ||||
| 			// 	newLimit); | ||||
|  | ||||
| 			// 	changedItems = await ItemClass.modelSelectAll(sql); | ||||
| 			// } | ||||
|  | ||||
| 			// console.info('CHANGED', changedItems); | ||||
|  | ||||
| 			// const items = neverSyncedItem.concat(changedItems); | ||||
|  | ||||
| 			// if (i >= classNames.length - 1) { | ||||
| 			// 	return { hasMore: items.length >= limit, items: items }; | ||||
| 			// } else { | ||||
| 			// 	if (items.length) return { hasMore: true, items: items }; | ||||
| 			// } | ||||
| 		} | ||||
|  | ||||
| 		throw new Error('Unreachable'); | ||||
|   | ||||
| @@ -12,9 +12,9 @@ class Setting extends BaseModel { | ||||
| 		return BaseModel.TYPE_SETTING; | ||||
| 	} | ||||
|  | ||||
| 	static defaultSetting(key) { | ||||
| 		if (!(key in this.defaults_)) throw new Error('Unknown key: ' + key); | ||||
| 		let output = Object.assign({}, this.defaults_[key]); | ||||
| 	static settingMetadata(key) { | ||||
| 		if (!(key in this.metadata_)) throw new Error('Unknown key: ' + key); | ||||
| 		let output = Object.assign({}, this.metadata_[key]); | ||||
| 		output.key = key; | ||||
| 		return output; | ||||
| 	} | ||||
| @@ -22,8 +22,8 @@ class Setting extends BaseModel { | ||||
| 	static keys() { | ||||
| 		if (this.keys_) return this.keys_; | ||||
| 		this.keys_ = []; | ||||
| 		for (let n in this.defaults_) { | ||||
| 			if (!this.defaults_.hasOwnProperty(n)) continue; | ||||
| 		for (let n in this.metadata_) { | ||||
| 			if (!this.metadata_.hasOwnProperty(n)) continue; | ||||
| 			this.keys_.push(n); | ||||
| 		} | ||||
| 		return this.keys_; | ||||
| @@ -31,9 +31,9 @@ class Setting extends BaseModel { | ||||
|  | ||||
| 	static publicKeys() { | ||||
| 		let output = []; | ||||
| 		for (let n in this.defaults_) { | ||||
| 			if (!this.defaults_.hasOwnProperty(n)) continue; | ||||
| 			if (this.defaults_[n].public) output.push(n); | ||||
| 		for (let n in this.metadata_) { | ||||
| 			if (!this.metadata_.hasOwnProperty(n)) continue; | ||||
| 			if (this.metadata_[n].public) output.push(n); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
| @@ -55,15 +55,24 @@ class Setting extends BaseModel { | ||||
| 		if (!this.cache_) throw new Error('Settings have not been initialized!'); | ||||
| 		 | ||||
| 		for (let i = 0; i < this.cache_.length; i++) { | ||||
| 			if (this.cache_[i].key == key) { | ||||
| 				if (this.cache_[i].value === value) return; | ||||
| 				this.cache_[i].value = value; | ||||
| 			let c = this.cache_[i]; | ||||
| 			if (c.key == key) { | ||||
| 				const md = this.settingMetadata(key); | ||||
|  | ||||
| 				if (md.type == 'enum') { | ||||
| 					if (!this.isAllowedEnumOption(key, value)) { | ||||
| 						throw new Error(_('Invalid option value: "%s". Possible values are: %s.', value, this.enumOptionsDoc(key))); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (c.value === value) return; | ||||
| 				c.value = value; | ||||
| 				this.scheduleUpdate(); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		let s = this.defaultSetting(key); | ||||
| 		let s = this.settingMetadata(key); | ||||
| 		s.value = value; | ||||
| 		this.cache_.push(s); | ||||
| 		this.scheduleUpdate(); | ||||
| @@ -84,10 +93,54 @@ class Setting extends BaseModel { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		let s = this.defaultSetting(key); | ||||
| 		let s = this.settingMetadata(key); | ||||
| 		return s.value; | ||||
| 	} | ||||
|  | ||||
| 	static isEnum(key) { | ||||
| 		const md = this.settingMetadata(key); | ||||
| 		return md.type == 'enum'; | ||||
| 	} | ||||
|  | ||||
| 	static enumOptionValues(key) { | ||||
| 		const options = this.enumOptions(key); | ||||
| 		let output = []; | ||||
| 		for (let n in options) { | ||||
| 			if (!options.hasOwnProperty(n)) continue; | ||||
| 			output.push(n); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	static enumOptionLabel(key, value) { | ||||
| 		const options = this.enumOptions(key); | ||||
| 		for (let n in options) { | ||||
| 			if (n == value) return options[n]; | ||||
| 		} | ||||
| 		return ''; | ||||
| 	} | ||||
|  | ||||
| 	static enumOptions(key) { | ||||
| 		if (!this.metadata_[key]) throw new Error('Unknown key: ' + key); | ||||
| 		if (!this.metadata_[key].options) throw new Error('No options for: ' + key); | ||||
| 		return this.metadata_[key].options(); | ||||
| 	} | ||||
|  | ||||
| 	static enumOptionsDoc(key) { | ||||
| 		const options = this.enumOptions(key); | ||||
| 		let output = []; | ||||
| 		for (let n in options) { | ||||
| 			if (!options.hasOwnProperty(n)) continue; | ||||
| 			output.push(_('%s (%s)', n, options[n])); | ||||
| 		} | ||||
| 		return output.join(', '); | ||||
| 	} | ||||
|  | ||||
| 	static isAllowedEnumOption(key, value) { | ||||
| 		const options = this.enumOptions(key); | ||||
| 		return !!options[value]; | ||||
| 	} | ||||
|  | ||||
| 	// Currently only supports objects with properties one level deep | ||||
| 	static object(key) { | ||||
| 		let output = {}; | ||||
| @@ -149,9 +202,9 @@ class Setting extends BaseModel { | ||||
| 		if (!appType) throw new Error('appType is required'); | ||||
|  | ||||
| 		let output = {}; | ||||
| 		for (let key in Setting.defaults_) { | ||||
| 			if (!Setting.defaults_.hasOwnProperty(key)) continue; | ||||
| 			let s = Object.assign({}, Setting.defaults_[key]); | ||||
| 		for (let key in Setting.metadata_) { | ||||
| 			if (!Setting.metadata_.hasOwnProperty(key)) continue; | ||||
| 			let s = Object.assign({}, Setting.metadata_[key]); | ||||
| 			if (!s.public) continue; | ||||
| 			if (s.appTypes && s.appTypes.indexOf(appType) < 0) continue; | ||||
| 			s.value = this.value(key); | ||||
| @@ -162,19 +215,24 @@ class Setting extends BaseModel { | ||||
|  | ||||
| } | ||||
|  | ||||
| Setting.defaults_ = { | ||||
| Setting.SYNC_TARGET_MEMORY = 1; | ||||
| Setting.SYNC_TARGET_FILESYSTEM = 2; | ||||
| Setting.SYNC_TARGET_ONEDRIVE = 3; | ||||
|  | ||||
| Setting.metadata_ = { | ||||
| 	'activeFolderId': { value: '', type: 'string', public: false }, | ||||
| 	'sync.onedrive.auth': { value: '', type: 'string', public: false }, | ||||
| 	'sync.filesystem.path': { value: '', type: 'string', public: true, appTypes: ['cli'] }, | ||||
| 	'sync.target': { value: 'onedrive', type: 'enum', public: true, label: () => _('Synchronisation target'), options: () => ({ | ||||
| 		1: 'Memory', | ||||
| 		2: _('File system'), | ||||
| 		3: _('OneDrive'), | ||||
| 	})}, | ||||
| 	'sync.2.path': { value: '', type: 'string', public: true, appTypes: ['cli'] }, | ||||
| 	'sync.3.auth': { value: '', type: 'string', public: false }, | ||||
| 	'sync.target': { value: 'onedrive', type: 'enum', public: true, label: () => _('Synchronisation target'), options: () => { | ||||
| 		let output = {}; | ||||
| 		output[Setting.SYNC_TARGET_MEMORY] = 'Memory'; | ||||
| 		output[Setting.SYNC_TARGET_FILESYSTEM] = _('File system'); | ||||
| 		output[Setting.SYNC_TARGET_ONEDRIVE] = _('OneDrive'); | ||||
| 		return output; | ||||
| 	}}, | ||||
| 	'sync.context': { value: '', type: 'string', public: false }, | ||||
| 	'editor': { value: '', type: 'string', public: true, appTypes: ['cli'] }, | ||||
| 	'locale': { value: 'en_GB', type: 'string', public: true }, | ||||
| 	//'aliases': { value: '', type: 'string', public: true }, | ||||
| 	'todoFilter': { value: 'all', type: 'enum', public: true, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({ | ||||
| 		all: _('Show all'), | ||||
| 		recent: _('Non-completed and recently completed ones'), | ||||
|   | ||||
| @@ -34,10 +34,10 @@ reg.oneDriveApi = () => { | ||||
|  | ||||
| 	reg.oneDriveApi_.on('authRefreshed', (a) => { | ||||
| 		reg.logger().info('Saving updated OneDrive auth.'); | ||||
| 		Setting.setValue('sync.onedrive.auth', a ? JSON.stringify(a) : null); | ||||
| 		Setting.setValue('sync.3.auth', a ? JSON.stringify(a) : null); | ||||
| 	}); | ||||
|  | ||||
| 	let auth = Setting.value('sync.onedrive.auth'); | ||||
| 	let auth = Setting.value('sync.3.auth'); | ||||
| 	if (auth) { | ||||
| 		try { | ||||
| 			auth = JSON.parse(auth); | ||||
| @@ -61,20 +61,20 @@ reg.synchronizer = async (syncTargetId) => { | ||||
|  | ||||
| 	let fileApi = null; | ||||
|  | ||||
| 	if (syncTargetId == 'onedrive') { | ||||
| 	if (syncTargetId == Setting.SYNC_TARGET_ONEDRIVE) { | ||||
|  | ||||
| 		if (!reg.oneDriveApi().auth()) throw new Error('User is not authentified'); | ||||
| 		let appDir = await reg.oneDriveApi().appDirectory(); | ||||
| 		fileApi = new FileApi(appDir, new FileApiDriverOneDrive(reg.oneDriveApi())); | ||||
|  | ||||
| 	} else if (syncTargetId == 'memory') { | ||||
| 	} else if (syncTargetId == Setting.SYNC_TARGET_MEMORY) { | ||||
|  | ||||
| 		fileApi = new FileApi('joplin', new FileApiDriverMemory()); | ||||
|  | ||||
| 	} else if (syncTargetId == 'filesystem') { | ||||
| 	} else if (syncTargetId == Setting.SYNC_TARGET_FILESYSTEM) { | ||||
|  | ||||
| 		let syncDir = Setting.value('sync.filesystem.path'); | ||||
| 		if (!syncDir) throw new Error(_('Please set the "sync.filesystem.path" config value to the desired synchronisation destination.')); | ||||
| 		let syncDir = Setting.value('sync.2.path'); | ||||
| 		if (!syncDir) throw new Error(_('Please set the "sync.2.path" config value to the desired synchronisation destination.')); | ||||
| 		await shim.fs.mkdirp(syncDir, 0o755); | ||||
| 		fileApi = new FileApi(syncDir, new shim.FileApiDriverLocal()); | ||||
|  | ||||
| @@ -84,6 +84,7 @@ reg.synchronizer = async (syncTargetId) => { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	fileApi.setSyncTargetId(syncTargetId); | ||||
| 	fileApi.setLogger(reg.logger()); | ||||
|  | ||||
| 	let sync = new Synchronizer(reg.db(), fileApi, Setting.value('appType')); | ||||
|   | ||||
| @@ -153,7 +153,7 @@ class Synchronizer { | ||||
|  | ||||
| 		const lastContext = options.context ? options.context : {}; | ||||
|  | ||||
| 		const syncTargetId = this.api().driver().syncTargetId(); | ||||
| 		const syncTargetId = this.api().syncTargetId(); | ||||
|  | ||||
| 		if (this.state() != 'idle') { | ||||
| 			this.logger().info('Synchronization is already in progress. State: ' + this.state()); | ||||
| @@ -176,7 +176,7 @@ class Synchronizer { | ||||
|  | ||||
| 		this.dispatch({ type: 'SYNC_STARTED' }); | ||||
|  | ||||
| 		this.logSyncOperation('starting', null, null, 'Starting synchronization to ' + this.api().driver().syncTargetName() + ' (' + syncTargetId + ')... [' + synchronizationId + ']'); | ||||
| 		this.logSyncOperation('starting', null, null, 'Starting synchronization to target ' + syncTargetId + '... [' + synchronizationId + ']'); | ||||
|  | ||||
| 		try { | ||||
| 			await this.api().mkdir(this.syncDirName_); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user