You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	OneDrive delta api
This commit is contained in:
		| @@ -91,7 +91,11 @@ class Command extends BaseCommand { | ||||
|  | ||||
| 			this.log(_('Starting synchronization...')); | ||||
|  | ||||
| 			await sync.start(options); | ||||
| 			let context = Setting.value('sync.context'); | ||||
| 			context = context ? JSON.parse(context) : {}; | ||||
| 			options.context = context; | ||||
| 			let newContext = await sync.start(options); | ||||
| 			Setting.setValue('sync.context', JSON.stringify(newContext)); | ||||
| 			vorpalUtils.redrawDone(); | ||||
|  | ||||
| 			await app().refreshCurrentFolder(); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Joplin-CLI 1.0.0\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-07-18 16:33+0100\n" | ||||
| "POT-Creation-Date: 2017-07-18 17:30+0100\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -316,11 +316,11 @@ msgstr "" | ||||
| msgid "Starting synchronization..." | ||||
| msgstr "" | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:99 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:103 | ||||
| msgid "Done." | ||||
| msgstr "" | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:114 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:118 | ||||
| #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60 | ||||
| msgid "Cancelling..." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Joplin-CLI 1.0.0\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-07-18 16:32+0100\n" | ||||
| "POT-Creation-Date: 2017-07-18 17:28+0100\n" | ||||
| "PO-Revision-Date: 2017-07-18 13:27+0100\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: \n" | ||||
| @@ -342,11 +342,11 @@ msgstr "Impossible d'initialiser le synchroniseur." | ||||
| msgid "Starting synchronization..." | ||||
| msgstr "Commencement de la synchronisation..." | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:99 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:103 | ||||
| msgid "Done." | ||||
| msgstr "Terminé." | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:114 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:118 | ||||
| #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60 | ||||
| msgid "Cancelling..." | ||||
| msgstr "Annulation..." | ||||
|   | ||||
| @@ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Joplin-CLI 1.0.0\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-07-18 16:33+0100\n" | ||||
| "POT-Creation-Date: 2017-07-18 17:30+0100\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -316,11 +316,11 @@ msgstr "" | ||||
| msgid "Starting synchronization..." | ||||
| msgstr "" | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:99 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:103 | ||||
| msgid "Done." | ||||
| msgstr "" | ||||
|  | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:114 | ||||
| #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:118 | ||||
| #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60 | ||||
| msgid "Cancelling..." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -166,10 +166,55 @@ class FileApiDriverOneDrive { | ||||
| 		throw new Error('Not implemented'); | ||||
| 	} | ||||
|  | ||||
| 	// delta(path) { | ||||
| 	// 	let response = await this.api_.exec('GET', this.makePath_(path) + ':/delta'); | ||||
| 	// 	console.info(response);		 | ||||
| 	// } | ||||
| 	async delta(path, options = null) { | ||||
| 		let output = { | ||||
| 			context: {}, | ||||
| 			items: [], | ||||
| 		}; | ||||
|  | ||||
| 		let context = options ? options.context : null; | ||||
|  | ||||
| 		let url = null; | ||||
| 		let query = null; | ||||
| 		if (context) { | ||||
| 			url = context; | ||||
| 		} else { | ||||
| 			url = this.makePath_(path) + ':/delta'; | ||||
| 			query = this.itemFilter_(); | ||||
| 		} | ||||
|  | ||||
| 		while (true) { | ||||
| 			let response = await this.api_.execJson('GET', url, query); | ||||
| 			let items = this.makeItems_(response.value); | ||||
| 			output.items = output.items.concat(items); | ||||
|  | ||||
| 			if (response['@odata.nextLink']) { | ||||
| 				url = response['@odata.nextLink']; | ||||
| 			} else { | ||||
| 				if (!response['@odata.deltaLink']) { | ||||
| 					throw new Error('Delta link missing: ' + JSON.stringify(response)); | ||||
| 				} | ||||
| 				output.context = response['@odata.deltaLink']; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// https://dev.onedrive.com/items/view_delta.htm | ||||
| 		// The same item may appear more than once in a delta feed, for various reasons. You should use the last occurrence you see. | ||||
| 		// So remove any duplicate item from the array. | ||||
| 		let temp = []; | ||||
| 		let seenPaths = []; | ||||
| 		for (let i = output.items.length - 1; i >= 0; i--) { | ||||
| 			let item = output.items[i]; | ||||
| 			if (seenPaths.indexOf(item.path) >= 0) continue; | ||||
| 			temp.splice(0, 0, item); | ||||
| 			seenPaths.push(item.path); | ||||
| 		} | ||||
|  | ||||
| 		output.items = temp; | ||||
|  | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -92,6 +92,11 @@ class FileApi { | ||||
| 		return this.driver_.format(); | ||||
| 	} | ||||
|  | ||||
| 	delta(path, options = null) { | ||||
| 		this.logger().debug('delta ' + this.fullPath_(path)); | ||||
| 		return this.driver_.delta(this.fullPath_(path), options); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| export { FileApi }; | ||||
| @@ -148,6 +148,7 @@ Setting.defaults_ = { | ||||
| 	'sync.onedrive.auth': { value: '', type: 'string', public: false }, | ||||
| 	'sync.filesystem.path': { value: '', type: 'string', public: true },	 | ||||
| 	'sync.target': { value: 'onedrive', type: 'string', public: true }, | ||||
| 	'sync.context': { value: '', type: 'string', public: false }, | ||||
| 	'editor': { value: '', type: 'string', public: true }, | ||||
| 	'locale': { value: 'en_GB', type: 'string', public: true }, | ||||
| 	'aliases': { value: '', type: 'string', public: true }, | ||||
|   | ||||
| @@ -147,6 +147,8 @@ class Synchronizer { | ||||
| 		this.onProgress_ = options.onProgress ? options.onProgress : function(o) {}; | ||||
| 		this.progressReport_ = { errors: [] }; | ||||
|  | ||||
| 		let lastContext = options.context; | ||||
|  | ||||
| 		const syncTargetId = this.api().driver().syncTargetId(); | ||||
|  | ||||
| 		if (this.state() != 'idle') { | ||||
| @@ -164,6 +166,8 @@ class Synchronizer { | ||||
|  | ||||
| 		let synchronizationId = time.unixMs().toString(); | ||||
|  | ||||
| 		let outputContext = {}; | ||||
|  | ||||
| 		this.state_ = 'in_progress'; | ||||
|  | ||||
| 		this.dispatch({ type: 'SYNC_STARTED' }); | ||||
| @@ -315,116 +319,121 @@ class Synchronizer { | ||||
| 			// At this point all the local items that have changed have been pushed to remote | ||||
| 			// or handled as conflicts, so no conflict is possible after this. | ||||
|  | ||||
| 			let remoteIds = []; | ||||
| 			let context = null; | ||||
| 			let deltaOptions = {}; | ||||
| 			if (lastContext.delta) deltaOptions.context = lastContext.delta; | ||||
| 			let listResult = await this.api().delta('', deltaOptions); | ||||
| 			outputContext.delta = listResult.context; | ||||
|  | ||||
| 			while (true) { | ||||
| 				if (this.cancelling()) break; | ||||
| 			// let remoteIds = []; | ||||
| 			// let context = null; | ||||
|  | ||||
| 				let listResult = await this.api().list('', { context: context }); | ||||
| 				let remotes = listResult.items; | ||||
| 				for (let i = 0; i < remotes.length; i++) { | ||||
| 					if (this.cancelling()) break; | ||||
| 			// while (true) { | ||||
| 			// 	if (this.cancelling()) break; | ||||
|  | ||||
| 					let remote = remotes[i]; | ||||
| 					let path = remote.path; | ||||
| 			// 	let listResult = await this.api().list('', { context: context }); | ||||
| 			// 	let remotes = listResult.items; | ||||
| 			// 	for (let i = 0; i < remotes.length; i++) { | ||||
| 			// 		if (this.cancelling()) break; | ||||
|  | ||||
| 					remoteIds.push(BaseItem.pathToId(path)); | ||||
| 					if (donePaths.indexOf(path) > 0) continue; | ||||
| 			// 		let remote = remotes[i]; | ||||
| 			// 		let path = remote.path; | ||||
|  | ||||
| 					let action = null; | ||||
| 					let reason = ''; | ||||
| 					let local = await BaseItem.loadItemByPath(path); | ||||
| 					if (!local) { | ||||
| 						action = 'createLocal'; | ||||
| 						reason = 'remote exists but local does not'; | ||||
| 					} else { | ||||
| 						if (remote.updated_time > local.updated_time) { | ||||
| 							action = 'updateLocal'; | ||||
| 							reason = sprintf('remote is more recent than local'); | ||||
| 						} | ||||
| 					} | ||||
| 			// 		remoteIds.push(BaseItem.pathToId(path)); | ||||
| 			// 		if (donePaths.indexOf(path) > 0) continue; | ||||
|  | ||||
| 					if (!action) continue; | ||||
| 			// 		let action = null; | ||||
| 			// 		let reason = ''; | ||||
| 			// 		let local = await BaseItem.loadItemByPath(path); | ||||
| 			// 		if (!local) { | ||||
| 			// 			action = 'createLocal'; | ||||
| 			// 			reason = 'remote exists but local does not'; | ||||
| 			// 		} else { | ||||
| 			// 			if (remote.updated_time > local.updated_time) { | ||||
| 			// 				action = 'updateLocal'; | ||||
| 			// 				reason = sprintf('remote is more recent than local'); | ||||
| 			// 			} | ||||
| 			// 		} | ||||
|  | ||||
| 					if (action == 'createLocal' || action == 'updateLocal') { | ||||
| 						let content = await this.api().get(path); | ||||
| 						if (content === null) { | ||||
| 							this.logger().warn('Remote has been deleted between now and the list() call? In that case it will be handled during the next sync: ' + path); | ||||
| 							continue; | ||||
| 						} | ||||
| 						content = await BaseItem.unserialize(content); | ||||
| 						let ItemClass = BaseItem.itemClass(content); | ||||
| 			// 		if (!action) continue; | ||||
|  | ||||
| 						let newContent = Object.assign({}, content); | ||||
| 						let options = { | ||||
| 							autoTimestamp: false, | ||||
| 							applyMetadataChanges: true, | ||||
| 							nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), | ||||
| 						}; | ||||
| 						if (action == 'createLocal') options.isNew = true; | ||||
| 			// 		if (action == 'createLocal' || action == 'updateLocal') { | ||||
| 			// 			let content = await this.api().get(path); | ||||
| 			// 			if (content === null) { | ||||
| 			// 				this.logger().warn('Remote has been deleted between now and the list() call? In that case it will be handled during the next sync: ' + path); | ||||
| 			// 				continue; | ||||
| 			// 			} | ||||
| 			// 			content = await BaseItem.unserialize(content); | ||||
| 			// 			let ItemClass = BaseItem.itemClass(content); | ||||
|  | ||||
| 						if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { | ||||
| 							let localResourceContentPath = Resource.fullPath(newContent); | ||||
| 							let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id; | ||||
| 							await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' }); | ||||
| 						} | ||||
| 			// 			let newContent = Object.assign({}, content); | ||||
| 			// 			let options = { | ||||
| 			// 				autoTimestamp: false, | ||||
| 			// 				applyMetadataChanges: true, | ||||
| 			// 				nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), | ||||
| 			// 			}; | ||||
| 			// 			if (action == 'createLocal') options.isNew = true; | ||||
|  | ||||
| 						await ItemClass.save(newContent, options); | ||||
| 			// 			if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { | ||||
| 			// 				let localResourceContentPath = Resource.fullPath(newContent); | ||||
| 			// 				let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id; | ||||
| 			// 				await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' }); | ||||
| 			// 			} | ||||
|  | ||||
| 						this.logSyncOperation(action, local, content, reason); | ||||
| 					} else { | ||||
| 						this.logSyncOperation(action, local, remote, reason); | ||||
| 					} | ||||
| 				} | ||||
| 			// 			await ItemClass.save(newContent, options); | ||||
|  | ||||
| 				if (!listResult.hasMore) break; | ||||
| 				context = listResult.context; | ||||
| 			} | ||||
| 			// 			this.logSyncOperation(action, local, content, reason); | ||||
| 			// 		} else { | ||||
| 			// 			this.logSyncOperation(action, local, remote, reason); | ||||
| 			// 		} | ||||
| 			// 	} | ||||
|  | ||||
| 			// ------------------------------------------------------------------------ | ||||
| 			// Search, among the local IDs, those that don't exist remotely, which | ||||
| 			// means the item has been deleted. | ||||
| 			// ------------------------------------------------------------------------ | ||||
| 			// 	if (!listResult.hasMore) break; | ||||
| 			// 	context = listResult.context; | ||||
| 			// } | ||||
|  | ||||
| 			if (this.randomFailure(options, 4)) return; | ||||
| 			// // ------------------------------------------------------------------------ | ||||
| 			// // Search, among the local IDs, those that don't exist remotely, which | ||||
| 			// // means the item has been deleted. | ||||
| 			// // ------------------------------------------------------------------------ | ||||
|  | ||||
| 			let localFoldersToDelete = []; | ||||
| 			// if (this.randomFailure(options, 4)) return; | ||||
|  | ||||
| 			if (!this.cancelling()) { | ||||
| 				let syncItems = await BaseItem.syncedItems(syncTargetId); | ||||
| 				for (let i = 0; i < syncItems.length; i++) { | ||||
| 					if (this.cancelling()) break; | ||||
| 			// let localFoldersToDelete = []; | ||||
|  | ||||
| 					let syncItem = syncItems[i]; | ||||
| 					if (remoteIds.indexOf(syncItem.item_id) < 0) { | ||||
| 						if (syncItem.item_type == Folder.modelType()) { | ||||
| 							localFoldersToDelete.push(syncItem); | ||||
| 							continue; | ||||
| 						} | ||||
| 			// if (!this.cancelling()) { | ||||
| 			// 	let syncItems = await BaseItem.syncedItems(syncTargetId); | ||||
| 			// 	for (let i = 0; i < syncItems.length; i++) { | ||||
| 			// 		if (this.cancelling()) break; | ||||
|  | ||||
| 						this.logSyncOperation('deleteLocal', { id: syncItem.item_id }, null, 'remote has been deleted'); | ||||
| 			// 		let syncItem = syncItems[i]; | ||||
| 			// 		if (remoteIds.indexOf(syncItem.item_id) < 0) { | ||||
| 			// 			if (syncItem.item_type == Folder.modelType()) { | ||||
| 			// 				localFoldersToDelete.push(syncItem); | ||||
| 			// 				continue; | ||||
| 			// 			} | ||||
|  | ||||
| 						let ItemClass = BaseItem.itemClass(syncItem.item_type); | ||||
| 						await ItemClass.delete(syncItem.item_id, { trackDeleted: false }); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			// 			this.logSyncOperation('deleteLocal', { id: syncItem.item_id }, null, 'remote has been deleted'); | ||||
|  | ||||
| 			if (!this.cancelling()) { | ||||
| 				for (let i = 0; i < localFoldersToDelete.length; i++) { | ||||
| 					const syncItem = localFoldersToDelete[i]; | ||||
| 					const noteIds = await Folder.noteIds(syncItem.item_id); | ||||
| 					if (noteIds.length) { // CONFLICT | ||||
| 						await Folder.markNotesAsConflict(syncItem.item_id); | ||||
| 					} | ||||
| 					await Folder.delete(syncItem.item_id, { deleteChildren: false }); | ||||
| 				} | ||||
| 			} | ||||
| 			// 			let ItemClass = BaseItem.itemClass(syncItem.item_type); | ||||
| 			// 			await ItemClass.delete(syncItem.item_id, { trackDeleted: false }); | ||||
| 			// 		} | ||||
| 			// 	} | ||||
| 			// } | ||||
|  | ||||
| 			if (!this.cancelling()) { | ||||
| 				await BaseItem.deleteOrphanSyncItems(); | ||||
| 			} | ||||
| 			// if (!this.cancelling()) { | ||||
| 			// 	for (let i = 0; i < localFoldersToDelete.length; i++) { | ||||
| 			// 		const syncItem = localFoldersToDelete[i]; | ||||
| 			// 		const noteIds = await Folder.noteIds(syncItem.item_id); | ||||
| 			// 		if (noteIds.length) { // CONFLICT | ||||
| 			// 			await Folder.markNotesAsConflict(syncItem.item_id); | ||||
| 			// 		} | ||||
| 			// 		await Folder.delete(syncItem.item_id, { deleteChildren: false }); | ||||
| 			// 	} | ||||
| 			// } | ||||
|  | ||||
| 			// if (!this.cancelling()) { | ||||
| 			// 	await BaseItem.deleteOrphanSyncItems(); | ||||
| 			// } | ||||
| 		} catch (error) { | ||||
| 			this.logger().error(error); | ||||
| 			this.progressReport_.errors.push(error); | ||||
| @@ -447,6 +456,8 @@ class Synchronizer { | ||||
| 		this.progressReport_ = {}; | ||||
|  | ||||
| 		this.dispatch({ type: 'SYNC_COMPLETED' }); | ||||
|  | ||||
| 		return outputContext; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user