1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

OneDrive delta api

This commit is contained in:
Laurent Cozic 2017-07-18 19:57:49 +00:00
parent 980e4bded1
commit 927894e940
8 changed files with 169 additions and 103 deletions

View File

@ -91,7 +91,11 @@ class Command extends BaseCommand {
this.log(_('Starting synchronization...')); 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(); vorpalUtils.redrawDone();
await app().refreshCurrentFolder(); await app().refreshCurrentFolder();

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n" "Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -316,11 +316,11 @@ msgstr ""
msgid "Starting synchronization..." msgid "Starting synchronization..."
msgstr "" msgstr ""
#: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:99 #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:103
msgid "Done." msgid "Done."
msgstr "" 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 #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60
msgid "Cancelling..." msgid "Cancelling..."
msgstr "" msgstr ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n" "Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2017-07-18 13:27+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
@ -342,11 +342,11 @@ msgstr "Impossible d'initialiser le synchroniseur."
msgid "Starting synchronization..." msgid "Starting synchronization..."
msgstr "Commencement de la synchronisation..." 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." msgid "Done."
msgstr "Terminé." 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 #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60
msgid "Cancelling..." msgid "Cancelling..."
msgstr "Annulation..." msgstr "Annulation..."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n" "Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -316,11 +316,11 @@ msgstr ""
msgid "Starting synchronization..." msgid "Starting synchronization..."
msgstr "" msgstr ""
#: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:99 #: /media/veracrypt22/src/notes/CliClient/app/command-sync.js:103
msgid "Done." msgid "Done."
msgstr "" 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 #: /media/veracrypt22/src/notes/ReactNativeClient/lib/synchronizer.js:60
msgid "Cancelling..." msgid "Cancelling..."
msgstr "" msgstr ""

View File

@ -166,10 +166,55 @@ class FileApiDriverOneDrive {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }
// delta(path) { async delta(path, options = null) {
// let response = await this.api_.exec('GET', this.makePath_(path) + ':/delta'); let output = {
// console.info(response); 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;
}
} }

View File

@ -92,6 +92,11 @@ class FileApi {
return this.driver_.format(); 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 }; export { FileApi };

View File

@ -148,6 +148,7 @@ Setting.defaults_ = {
'sync.onedrive.auth': { value: '', type: 'string', public: false }, 'sync.onedrive.auth': { value: '', type: 'string', public: false },
'sync.filesystem.path': { value: '', type: 'string', public: true }, 'sync.filesystem.path': { value: '', type: 'string', public: true },
'sync.target': { value: 'onedrive', 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 }, 'editor': { value: '', type: 'string', public: true },
'locale': { value: 'en_GB', type: 'string', public: true }, 'locale': { value: 'en_GB', type: 'string', public: true },
'aliases': { value: '', type: 'string', public: true }, 'aliases': { value: '', type: 'string', public: true },

View File

@ -147,6 +147,8 @@ class Synchronizer {
this.onProgress_ = options.onProgress ? options.onProgress : function(o) {}; this.onProgress_ = options.onProgress ? options.onProgress : function(o) {};
this.progressReport_ = { errors: [] }; this.progressReport_ = { errors: [] };
let lastContext = options.context;
const syncTargetId = this.api().driver().syncTargetId(); const syncTargetId = this.api().driver().syncTargetId();
if (this.state() != 'idle') { if (this.state() != 'idle') {
@ -164,6 +166,8 @@ class Synchronizer {
let synchronizationId = time.unixMs().toString(); let synchronizationId = time.unixMs().toString();
let outputContext = {};
this.state_ = 'in_progress'; this.state_ = 'in_progress';
this.dispatch({ type: 'SYNC_STARTED' }); 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 // 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. // or handled as conflicts, so no conflict is possible after this.
let remoteIds = []; let deltaOptions = {};
let context = null; if (lastContext.delta) deltaOptions.context = lastContext.delta;
let listResult = await this.api().delta('', deltaOptions);
outputContext.delta = listResult.context;
while (true) { // let remoteIds = [];
if (this.cancelling()) break; // let context = null;
let listResult = await this.api().list('', { context: context }); // while (true) {
let remotes = listResult.items; // if (this.cancelling()) break;
for (let i = 0; i < remotes.length; i++) {
if (this.cancelling()) break;
let remote = remotes[i]; // let listResult = await this.api().list('', { context: context });
let path = remote.path; // let remotes = listResult.items;
// for (let i = 0; i < remotes.length; i++) {
// if (this.cancelling()) break;
remoteIds.push(BaseItem.pathToId(path)); // let remote = remotes[i];
if (donePaths.indexOf(path) > 0) continue; // let path = remote.path;
let action = null; // remoteIds.push(BaseItem.pathToId(path));
let reason = ''; // if (donePaths.indexOf(path) > 0) continue;
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) 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') { // if (!action) continue;
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);
let newContent = Object.assign({}, content); // if (action == 'createLocal' || action == 'updateLocal') {
let options = { // let content = await this.api().get(path);
autoTimestamp: false, // if (content === null) {
applyMetadataChanges: true, // 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);
nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), // continue;
}; // }
if (action == 'createLocal') options.isNew = true; // content = await BaseItem.unserialize(content);
// let ItemClass = BaseItem.itemClass(content);
if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { // let newContent = Object.assign({}, content);
let localResourceContentPath = Resource.fullPath(newContent); // let options = {
let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id; // autoTimestamp: false,
await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' }); // 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); // await ItemClass.save(newContent, options);
} else {
this.logSyncOperation(action, local, remote, reason);
}
}
if (!listResult.hasMore) break; // this.logSyncOperation(action, local, content, reason);
context = listResult.context; // } else {
} // this.logSyncOperation(action, local, remote, reason);
// }
// }
// ------------------------------------------------------------------------ // if (!listResult.hasMore) break;
// Search, among the local IDs, those that don't exist remotely, which // context = listResult.context;
// means the item has been deleted. // }
// ------------------------------------------------------------------------
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 localFoldersToDelete = [];
let syncItems = await BaseItem.syncedItems(syncTargetId);
for (let i = 0; i < syncItems.length; i++) {
if (this.cancelling()) break;
let syncItem = syncItems[i]; // if (!this.cancelling()) {
if (remoteIds.indexOf(syncItem.item_id) < 0) { // let syncItems = await BaseItem.syncedItems(syncTargetId);
if (syncItem.item_type == Folder.modelType()) { // for (let i = 0; i < syncItems.length; i++) {
localFoldersToDelete.push(syncItem); // if (this.cancelling()) break;
continue;
}
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); // this.logSyncOperation('deleteLocal', { id: syncItem.item_id }, null, 'remote has been deleted');
await ItemClass.delete(syncItem.item_id, { trackDeleted: false });
}
}
}
if (!this.cancelling()) { // let ItemClass = BaseItem.itemClass(syncItem.item_type);
for (let i = 0; i < localFoldersToDelete.length; i++) { // await ItemClass.delete(syncItem.item_id, { trackDeleted: false });
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()) { // if (!this.cancelling()) {
await BaseItem.deleteOrphanSyncItems(); // 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) { } catch (error) {
this.logger().error(error); this.logger().error(error);
this.progressReport_.errors.push(error); this.progressReport_.errors.push(error);
@ -447,6 +456,8 @@ class Synchronizer {
this.progressReport_ = {}; this.progressReport_ = {};
this.dispatch({ type: 'SYNC_COMPLETED' }); this.dispatch({ type: 'SYNC_COMPLETED' });
return outputContext;
} }
} }