mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Improved handling of multiple sync targets
This commit is contained in:
parent
cd6d8ce284
commit
24f61177d1
@ -239,6 +239,8 @@ class Application {
|
||||
|
||||
let fileApi = null;
|
||||
|
||||
// TODO: create file api based on syncTarget
|
||||
|
||||
if (syncTarget == 'onedrive') {
|
||||
const oneDriveApi = reg.oneDriveApi();
|
||||
let driver = new FileApiDriverOneDrive(oneDriveApi);
|
||||
@ -258,7 +260,7 @@ class Application {
|
||||
} else if (syncTarget == 'memory') {
|
||||
fileApi = new FileApi('joplin', new FileApiDriverMemory());
|
||||
fileApi.setLogger(this.logger_);
|
||||
} else if (syncTarget == 'local') {
|
||||
} else if (syncTarget == 'file') {
|
||||
let syncDir = Setting.value('sync.local.path');
|
||||
if (!syncDir) syncDir = Setting.value('profileDir') + '/sync';
|
||||
this.vorpal().log(_('Synchronizing with directory "%s"', syncDir));
|
||||
|
@ -25,7 +25,7 @@ class Command extends BaseCommand {
|
||||
['-r, --reverse', 'Reverses the sorting order.'],
|
||||
['-t, --type <type>', 'Displays only the items of the specific type(s). Can be `n` for notes, `t` for todos, or `nt` for notes and todos (eg. `-tt` would display only the todos, while `-ttd` would display notes and todos.'],
|
||||
['-f, --format <format>', 'Either "text" or "json"'],
|
||||
['-l, --long', 'Use long list format. Format is NOTE_COUNT (for notebook), DATE, NEED_SYNC, TODO_CHECKED (for todos), TITLE'],
|
||||
['-l, --long', 'Use long list format. Format is NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for todos), TITLE'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -90,7 +90,6 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
row.push(time.unixMsToLocalDateTime(item.updated_time));
|
||||
row.push(item.updated_time > item.sync_time ? '*' : ' ');
|
||||
}
|
||||
|
||||
let title = item.title + suffix;
|
||||
|
@ -208,7 +208,6 @@ function compareItems(item1, item2) {
|
||||
let output = [];
|
||||
for (let n in item1) {
|
||||
if (!item1.hasOwnProperty(n)) continue;
|
||||
if (n == 'sync_time') continue;
|
||||
let p1 = item1[n];
|
||||
let p2 = item2[n];
|
||||
|
||||
|
@ -177,7 +177,6 @@ describe('Synchronizer', function() {
|
||||
let noteUpdatedFromRemote = await Note.load(note1.id);
|
||||
for (let n in noteUpdatedFromRemote) {
|
||||
if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue;
|
||||
if (n == 'sync_time') continue;
|
||||
expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n);
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ function setupDatabase(id = null) {
|
||||
// Don't care if the file doesn't exist
|
||||
}).then(() => {
|
||||
databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
|
||||
databases_[id].setLogger(logger);
|
||||
//databases_[id].setLogger(logger);
|
||||
return databases_[id].open({ name: filePath }).then(() => {
|
||||
BaseModel.db_ = databases_[id];
|
||||
return setupDatabase(id);
|
||||
|
@ -49,8 +49,14 @@ class BaseModel {
|
||||
return fields.indexOf(name) >= 0;
|
||||
}
|
||||
|
||||
static fieldNames() {
|
||||
return this.db().tableFieldNames(this.tableName());
|
||||
static fieldNames(withPrefix = false) {
|
||||
let output = this.db().tableFieldNames(this.tableName());
|
||||
if (!withPrefix) return output;
|
||||
let temp = [];
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
temp.push(this.tableName() + '.' + output[i]);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
static fieldType(name) {
|
||||
@ -228,6 +234,10 @@ class BaseModel {
|
||||
|
||||
queries.push(saveQuery);
|
||||
|
||||
if (options.nextQueries && options.nextQueries.length) {
|
||||
queries = queries.concat(options.nextQueries);
|
||||
}
|
||||
|
||||
return this.db().transactionExecBatch(queries).then(() => {
|
||||
o = Object.assign({}, o);
|
||||
o.id = modelId;
|
||||
|
@ -40,7 +40,11 @@ class Database {
|
||||
|
||||
escapeField(field) {
|
||||
if (field == '*') return '*';
|
||||
return '`' + field + '`';
|
||||
let p = field.split('.');
|
||||
if (p.length == 1) return '`' + field + '`';
|
||||
if (p.length == 2) return p[0] + '.`' + p[1] + '`';
|
||||
|
||||
throw new Error('Invalid field format: ' + field);
|
||||
}
|
||||
|
||||
escapeFields(fields) {
|
||||
@ -145,9 +149,23 @@ class Database {
|
||||
if (s == 'INTEGER') s = 'INT';
|
||||
return this['TYPE_' + s];
|
||||
}
|
||||
if (type == 'syncTarget') {
|
||||
if (s == 'memory') return 1;
|
||||
if (s == 'file') return 2;
|
||||
if (s == 'onedrive') return 3;
|
||||
}
|
||||
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 'file';
|
||||
if (id === 3) return 'onedrive';
|
||||
}
|
||||
throw new Error('Unknown enum type or id: ' + type + ', ' + id);
|
||||
}
|
||||
|
||||
static formatValue(type, value) {
|
||||
if (value === null || value === undefined) return null;
|
||||
if (type == this.TYPE_INT) return Number(value);
|
||||
|
@ -5,6 +5,14 @@ import { time } from 'lib/time-utils.js';
|
||||
|
||||
class FileApiDriverLocal {
|
||||
|
||||
syncTargetId() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
syncTargetName() {
|
||||
return 'file';
|
||||
}
|
||||
|
||||
fsErrorToJsError_(error) {
|
||||
let msg = error.toString();
|
||||
let output = new Error(msg);
|
||||
|
@ -2,6 +2,14 @@ import { time } from 'lib/time-utils.js';
|
||||
|
||||
class FileApiDriverMemory {
|
||||
|
||||
syncTargetId() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
syncTargetName() {
|
||||
return 'memory';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.items_ = [];
|
||||
}
|
||||
|
@ -9,6 +9,14 @@ class FileApiDriverOneDrive {
|
||||
this.api_ = api;
|
||||
}
|
||||
|
||||
syncTargetId() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
syncTargetName() {
|
||||
return 'onedrive';
|
||||
}
|
||||
|
||||
api() {
|
||||
return this.api_;
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ class FileApi {
|
||||
this.logger_ = new Logger();
|
||||
}
|
||||
|
||||
driver() {
|
||||
return this.driver_;
|
||||
}
|
||||
|
||||
setLogger(l) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
@ -6,16 +6,13 @@ import { Database } from 'lib/database.js'
|
||||
const structureSql = `
|
||||
CREATE TABLE folders (
|
||||
id TEXT PRIMARY KEY,
|
||||
parent_id TEXT NOT NULL DEFAULT "",
|
||||
title TEXT NOT NULL DEFAULT "",
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
updated_time INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX folders_title ON folders (title);
|
||||
CREATE INDEX folders_updated_time ON folders (updated_time);
|
||||
CREATE INDEX folders_sync_time ON folders (sync_time);
|
||||
|
||||
CREATE TABLE notes (
|
||||
id TEXT PRIMARY KEY,
|
||||
@ -24,7 +21,6 @@ CREATE TABLE notes (
|
||||
body TEXT NOT NULL DEFAULT "",
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0,
|
||||
is_conflict INT NOT NULL DEFAULT 0,
|
||||
latitude NUMERIC NOT NULL DEFAULT 0,
|
||||
longitude NUMERIC NOT NULL DEFAULT 0,
|
||||
@ -42,7 +38,6 @@ CREATE TABLE notes (
|
||||
|
||||
CREATE INDEX notes_title ON notes (title);
|
||||
CREATE INDEX notes_updated_time ON notes (updated_time);
|
||||
CREATE INDEX notes_sync_time ON notes (sync_time);
|
||||
CREATE INDEX notes_is_conflict ON notes (is_conflict);
|
||||
CREATE INDEX notes_is_todo ON notes (is_todo);
|
||||
CREATE INDEX notes_order ON notes (\`order\`);
|
||||
@ -58,27 +53,23 @@ CREATE TABLE tags (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL DEFAULT "",
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
updated_time INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX tags_title ON tags (title);
|
||||
CREATE INDEX tags_updated_time ON tags (updated_time);
|
||||
CREATE INDEX tags_sync_time ON tags (sync_time);
|
||||
|
||||
CREATE TABLE note_tags (
|
||||
id TEXT PRIMARY KEY,
|
||||
note_id TEXT NOT NULL,
|
||||
tag_id TEXT NOT NULL,
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
updated_time INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX note_tags_note_id ON note_tags (note_id);
|
||||
CREATE INDEX note_tags_tag_id ON note_tags (tag_id);
|
||||
CREATE INDEX note_tags_updated_time ON note_tags (updated_time);
|
||||
CREATE INDEX note_tags_sync_time ON note_tags (sync_time);
|
||||
|
||||
CREATE TABLE resources (
|
||||
id TEXT PRIMARY KEY,
|
||||
@ -86,13 +77,11 @@ CREATE TABLE resources (
|
||||
mime TEXT NOT NULL,
|
||||
filename TEXT NOT NULL DEFAULT "",
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
updated_time INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX resources_title ON resources (title);
|
||||
CREATE INDEX resources_updated_time ON resources (updated_time);
|
||||
CREATE INDEX resources_sync_time ON resources (sync_time);
|
||||
|
||||
CREATE TABLE settings (
|
||||
\`key\` TEXT PRIMARY KEY,
|
||||
@ -112,6 +101,19 @@ CREATE TABLE version (
|
||||
version INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sync_items (
|
||||
id INTEGER PRIMARY KEY,
|
||||
sync_target INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0,
|
||||
item_type INT NOT NULL,
|
||||
item_id TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX sync_items_sync_time ON sync_items (sync_time);
|
||||
CREATE INDEX sync_items_sync_target ON sync_items (sync_target);
|
||||
CREATE INDEX sync_items_item_type ON sync_items (item_type);
|
||||
CREATE INDEX sync_items_item_id ON sync_items (item_id);
|
||||
|
||||
INSERT INTO version (version) VALUES (1);
|
||||
`;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { BaseModel } from 'lib/base-model.js';
|
||||
import { Database } from 'lib/database.js';
|
||||
import { time } from 'lib/time-utils.js';
|
||||
import { sprintf } from 'sprintf-js';
|
||||
import moment from 'moment';
|
||||
|
||||
class BaseItem extends BaseModel {
|
||||
@ -31,44 +32,14 @@ class BaseItem extends BaseModel {
|
||||
throw new Error('Invalid class name: ' + name);
|
||||
}
|
||||
|
||||
static async stats() {
|
||||
let output = {
|
||||
items: {},
|
||||
total: {},
|
||||
};
|
||||
|
||||
let itemCount = 0;
|
||||
let syncedCount = 0;
|
||||
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||
let d = BaseItem.syncItemDefinitions_[i];
|
||||
let ItemClass = this.getClass(d.className);
|
||||
let o = {
|
||||
total: await ItemClass.count(),
|
||||
synced: await ItemClass.syncedCount(),
|
||||
};
|
||||
output.items[d.className] = o;
|
||||
itemCount += o.total;
|
||||
syncedCount += o.synced;
|
||||
}
|
||||
|
||||
output.total = {
|
||||
total: itemCount,
|
||||
synced: syncedCount,
|
||||
};
|
||||
|
||||
output.toDelete = {
|
||||
total: await this.deletedItemCount(),
|
||||
};
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async syncedCount() {
|
||||
const ItemClass = this.itemClass(this.modelType());
|
||||
let sql = 'SELECT count(*) as total FROM `' + ItemClass.tableName() + '` WHERE updated_time <= sync_time';
|
||||
if (this.modelType() == BaseModel.TYPE_NOTE) sql += ' AND is_conflict = 0';
|
||||
const r = await this.db().selectOne(sql);
|
||||
return r.total;
|
||||
// TODO
|
||||
return 0;
|
||||
// const ItemClass = this.itemClass(this.modelType());
|
||||
// let sql = 'SELECT count(*) as total FROM `' + ItemClass.tableName() + '` WHERE updated_time <= sync_time';
|
||||
// if (this.modelType() == BaseModel.TYPE_NOTE) sql += ' AND is_conflict = 0';
|
||||
// const r = await this.db().selectOne(sql);
|
||||
// return r.total;
|
||||
}
|
||||
|
||||
static systemPath(itemOrId) {
|
||||
@ -92,13 +63,9 @@ class BaseItem extends BaseModel {
|
||||
}
|
||||
|
||||
// Returns the IDs of the items that have been synced at least once
|
||||
static async syncedItems() {
|
||||
let folders = await this.getClass('Folder').modelSelectAll('SELECT id FROM folders WHERE sync_time > 0');
|
||||
let notes = await this.getClass('Note').modelSelectAll('SELECT id FROM notes WHERE is_conflict = 0 AND sync_time > 0');
|
||||
let resources = await this.getClass('Resource').modelSelectAll('SELECT id FROM resources WHERE sync_time > 0');
|
||||
let tags = await this.getClass('Tag').modelSelectAll('SELECT id FROM tags WHERE sync_time > 0');
|
||||
let noteTags = await this.getClass('NoteTag').modelSelectAll('SELECT id FROM note_tags WHERE sync_time > 0');
|
||||
return folders.concat(notes).concat(resources).concat(tags).concat(noteTags);
|
||||
static async syncedItems(syncTarget) {
|
||||
if (!syncTarget) throw new Error('No syncTarget specified');
|
||||
return await this.db().selectAll('SELECT item_id, item_type FROM sync_items WHERE sync_time > 0 AND sync_target = ?', [syncTarget]);
|
||||
}
|
||||
|
||||
static pathToId(path) {
|
||||
@ -136,14 +103,6 @@ class BaseItem extends BaseModel {
|
||||
|
||||
static async delete(id, options = null) {
|
||||
return this.batchDelete([id], options);
|
||||
// let trackDeleted = true;
|
||||
// if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
||||
|
||||
// await super.delete(id, options);
|
||||
|
||||
// if (trackDeleted) {
|
||||
// await this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.modelType(), id, time.unixMs()]);
|
||||
// }
|
||||
}
|
||||
|
||||
static async batchDelete(ids, options = null) {
|
||||
@ -289,21 +248,54 @@ class BaseItem extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async itemsThatNeedSync(limit = 100) {
|
||||
let items = await this.getClass('Folder').modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
static async itemsThatNeedSync(syncTarget, limit = 100) {
|
||||
const classNames = this.syncItemClassNames();
|
||||
|
||||
items = await this.getClass('Resource').modelSelectAll('SELECT * FROM resources WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
const className = classNames[i];
|
||||
const ItemClass = this.getClass(className);
|
||||
const fieldNames = ItemClass.fieldNames(true);
|
||||
fieldNames.push('sync_time');
|
||||
|
||||
items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit);
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
let sql = sprintf(`
|
||||
SELECT %s FROM %s
|
||||
LEFT JOIN sync_items t ON t.item_id = %s.id
|
||||
WHERE t.id IS NULL OR t.sync_time < %s.updated_time
|
||||
LIMIT %d
|
||||
`,
|
||||
this.db().escapeFields(fieldNames),
|
||||
this.db().escapeField(ItemClass.tableName()),
|
||||
this.db().escapeField(ItemClass.tableName()),
|
||||
this.db().escapeField(ItemClass.tableName()),
|
||||
limit);
|
||||
|
||||
items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
const items = await ItemClass.modelSelectAll(sql);
|
||||
|
||||
items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
return { hasMore: items.length >= limit, items: items };
|
||||
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');
|
||||
|
||||
//return this.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
|
||||
// let items = await this.getClass('Folder').modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Resource').modelSelectAll('SELECT * FROM resources WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// return { hasMore: items.length >= limit, items: items };
|
||||
}
|
||||
|
||||
static syncItemClassNames() {
|
||||
@ -319,6 +311,42 @@ class BaseItem extends BaseModel {
|
||||
throw new Error('Invalid type: ' + type);
|
||||
}
|
||||
|
||||
static updateSyncTimeQueries(syncTarget, item, syncTime) {
|
||||
const itemType = item.type_;
|
||||
const itemId = item.id;
|
||||
if (!itemType || !itemId || syncTime === undefined) throw new Error('Invalid parameters in updateSyncTimeQueries()');
|
||||
|
||||
return [
|
||||
{
|
||||
sql: 'DELETE FROM sync_items WHERE sync_target = ? AND item_type = ? AND item_id = ?',
|
||||
params: [syncTarget, itemType, itemId],
|
||||
},
|
||||
{
|
||||
sql: 'INSERT INTO sync_items (sync_target, item_type, item_id, sync_time) VALUES (?, ?, ?, ?)',
|
||||
params: [syncTarget, itemType, itemId, syncTime],
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static async saveSyncTime(syncTarget, item, syncTime) {
|
||||
const queries = this.updateSyncTimeQueries(syncTarget, item, syncTime);
|
||||
return this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static async deleteOrphanSyncItems() {
|
||||
const classNames = this.syncItemClassNames();
|
||||
|
||||
let queries = [];
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
const className = classNames[i];
|
||||
const ItemClass = this.getClass(className);
|
||||
|
||||
queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (SELECT id FROM ' + ItemClass.tableName() + ')');
|
||||
}
|
||||
|
||||
await this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Also update:
|
||||
|
@ -19,7 +19,7 @@ class Folder extends BaseItem {
|
||||
static async serialize(folder) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
lodash.pull(fieldNames, 'parent_id', 'sync_time');
|
||||
lodash.pull(fieldNames, 'parent_id');
|
||||
return super.serialize(folder, 'folder', fieldNames);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ class NoteTag extends BaseItem {
|
||||
static async serialize(item, type = null, shownKeys = null) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
lodash.pull(fieldNames, 'sync_time');
|
||||
return super.serialize(item, 'note_tag', fieldNames);
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,6 @@ class Note extends BaseItem {
|
||||
static async serialize(note, type = null, shownKeys = null) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
//lodash.pull(fieldNames, 'is_conflict', 'sync_time');
|
||||
lodash.pull(fieldNames, 'sync_time');
|
||||
return super.serialize(note, 'note', fieldNames);
|
||||
}
|
||||
|
||||
@ -68,7 +66,7 @@ class Note extends BaseItem {
|
||||
}
|
||||
|
||||
static previewFields() {
|
||||
return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time', 'sync_time'];
|
||||
return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time'];
|
||||
}
|
||||
|
||||
static previewFieldsSql() {
|
||||
@ -212,7 +210,6 @@ class Note extends BaseItem {
|
||||
|
||||
let newNote = Object.assign({}, originalNote);
|
||||
delete newNote.id;
|
||||
newNote.sync_time = 0;
|
||||
|
||||
for (let n in changes) {
|
||||
if (!changes.hasOwnProperty(n)) continue;
|
||||
|
@ -23,7 +23,6 @@ class Resource extends BaseItem {
|
||||
static async serialize(item, type = null, shownKeys = null) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
lodash.pull(fieldNames, 'sync_time');
|
||||
return super.serialize(item, 'resource', fieldNames);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ class Tag extends BaseItem {
|
||||
static async serialize(item, type = null, shownKeys = null) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
lodash.pull(fieldNames, 'sync_time');
|
||||
return super.serialize(item, 'tag', fieldNames);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ class ReportService {
|
||||
let ItemClass = BaseItem.getClass(d.className);
|
||||
let o = {
|
||||
total: await ItemClass.count(),
|
||||
synced: await ItemClass.syncedCount(),
|
||||
// synced: await ItemClass.syncedCount(), // TODO
|
||||
synced: 0,
|
||||
};
|
||||
output.items[d.className] = o;
|
||||
itemCount += o.total;
|
||||
|
@ -54,6 +54,7 @@ class Synchronizer {
|
||||
if (report.deleteLocal) lines.push(_('Deleted local items: %d.', report.deleteLocal));
|
||||
if (report.deleteRemote) lines.push(_('Deleted remote items: %d.', report.deleteRemote));
|
||||
if (report.state) lines.push(_('State: %s.', report.state.replace(/_/g, ' ')));
|
||||
if (report.errors && report.errors.length) lines.push(_('Last error: %s (stacktrace in log).', report.errors[report.errors.length-1].message));
|
||||
return lines;
|
||||
}
|
||||
|
||||
@ -139,6 +140,8 @@ class Synchronizer {
|
||||
this.onProgress_ = options.onProgress ? options.onProgress : function(o) {};
|
||||
this.progressReport_ = { errors: [] };
|
||||
|
||||
const syncTargetId = this.api().driver().syncTargetId();
|
||||
|
||||
if (this.state() != 'idle') {
|
||||
this.logger().warn('Synchronization is already in progress. State: ' + this.state());
|
||||
return;
|
||||
@ -156,7 +159,7 @@ class Synchronizer {
|
||||
|
||||
this.state_ = 'in_progress';
|
||||
|
||||
this.logSyncOperation('starting', null, null, 'Starting synchronization... [' + synchronizationId + ']');
|
||||
this.logSyncOperation('starting', null, null, 'Starting synchronization to ' + this.api().driver().syncTargetName() + ' (' + syncTargetId + ')... [' + synchronizationId + ']');
|
||||
|
||||
try {
|
||||
await this.api().mkdir(this.syncDirName_);
|
||||
@ -166,7 +169,7 @@ class Synchronizer {
|
||||
while (true) {
|
||||
if (this.cancelling()) break;
|
||||
|
||||
let result = await BaseItem.itemsThatNeedSync();
|
||||
let result = await BaseItem.itemsThatNeedSync(syncTargetId);
|
||||
let locals = result.items;
|
||||
|
||||
for (let i = 0; i < locals.length; i++) {
|
||||
@ -209,7 +212,6 @@ class Synchronizer {
|
||||
|
||||
this.logSyncOperation(action, local, remote, reason);
|
||||
|
||||
|
||||
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || (action == 'itemConflict' && remote))) {
|
||||
let remoteContentPath = this.resourceDirName_ + '/' + local.id;
|
||||
let resourceContent = await Resource.content(local);
|
||||
@ -236,8 +238,8 @@ class Synchronizer {
|
||||
await this.api().setTimestamp(path, local.updated_time);
|
||||
|
||||
if (this.randomFailure(options, 1)) return;
|
||||
|
||||
await ItemClass.save({ id: local.id, sync_time: time.unixMs(), type_: local.type_ }, { autoTimestamp: false });
|
||||
|
||||
await ItemClass.saveSyncTime(syncTargetId, local, time.unixMs());
|
||||
|
||||
} else if (action == 'itemConflict') {
|
||||
|
||||
@ -245,8 +247,8 @@ class Synchronizer {
|
||||
let remoteContent = await this.api().get(path);
|
||||
local = await BaseItem.unserialize(remoteContent);
|
||||
|
||||
local.sync_time = time.unixMs();
|
||||
await ItemClass.save(local, { autoTimestamp: false });
|
||||
const syncTimeQueries = BaseItem.updateSyncTimeQueries(syncTargetId, local, time.unixMs());
|
||||
await ItemClass.save(local, { autoTimestamp: false, nextQueries: syncTimeQueries });
|
||||
} else {
|
||||
await ItemClass.delete(local.id);
|
||||
}
|
||||
@ -266,8 +268,8 @@ class Synchronizer {
|
||||
let remoteContent = await this.api().get(path);
|
||||
local = await BaseItem.unserialize(remoteContent);
|
||||
|
||||
local.sync_time = time.unixMs();
|
||||
await ItemClass.save(local, { autoTimestamp: false });
|
||||
const syncTimeQueries = BaseItem.updateSyncTimeQueries(syncTargetId, local, time.unixMs());
|
||||
await ItemClass.save(local, { autoTimestamp: false, nextQueries: syncTimeQueries });
|
||||
} else {
|
||||
await ItemClass.delete(local.id);
|
||||
}
|
||||
@ -346,10 +348,10 @@ class Synchronizer {
|
||||
let ItemClass = BaseItem.itemClass(content);
|
||||
|
||||
let newContent = Object.assign({}, content);
|
||||
newContent.sync_time = time.unixMs();
|
||||
let options = {
|
||||
autoTimestamp: false,
|
||||
applyMetadataChanges: true,
|
||||
nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()),
|
||||
};
|
||||
if (action == 'createLocal') options.isNew = true;
|
||||
|
||||
@ -381,37 +383,39 @@ class Synchronizer {
|
||||
let localFoldersToDelete = [];
|
||||
|
||||
if (!this.cancelling()) {
|
||||
let items = await BaseItem.syncedItems();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let syncItems = await BaseItem.syncedItems(syncTargetId);
|
||||
for (let i = 0; i < syncItems.length; i++) {
|
||||
if (this.cancelling()) break;
|
||||
|
||||
let item = items[i];
|
||||
if (remoteIds.indexOf(item.id) < 0) {
|
||||
if (item.type_ == Folder.modelType()) {
|
||||
localFoldersToDelete.push(item);
|
||||
let syncItem = syncItems[i];
|
||||
if (remoteIds.indexOf(syncItem.item_id) < 0) {
|
||||
if (syncItem.item_type == Folder.modelType()) {
|
||||
localFoldersToDelete.push(syncItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logSyncOperation('deleteLocal', { id: item.id }, null, 'remote has been deleted');
|
||||
this.logSyncOperation('deleteLocal', { id: syncItem.item_id }, null, 'remote has been deleted');
|
||||
|
||||
let ItemClass = BaseItem.itemClass(item);
|
||||
await ItemClass.delete(item.id, { trackDeleted: false });
|
||||
let ItemClass = BaseItem.itemClass(syncItem.item_type);
|
||||
await ItemClass.delete(syncItem.item_id, { trackDeleted: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.cancelling()) {
|
||||
for (let i = 0; i < localFoldersToDelete.length; i++) {
|
||||
const folder = localFoldersToDelete[i];
|
||||
const noteIds = await Folder.noteIds(folder.id);
|
||||
const syncItem = localFoldersToDelete[i];
|
||||
const noteIds = await Folder.noteIds(syncItem.item_id);
|
||||
if (noteIds.length) { // CONFLICT
|
||||
await Folder.markNotesAsConflict(folder.id);
|
||||
await Folder.delete(folder.id, { deleteChildren: false });
|
||||
} else {
|
||||
await Folder.delete(folder.id);
|
||||
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);
|
||||
|
@ -20,6 +20,7 @@
|
||||
"ReactNativeClient/android/local.properties",
|
||||
"ReactNativeClient/ios",
|
||||
"_vieux",
|
||||
"tests/logs"
|
||||
],
|
||||
"file_exclude_patterns": [
|
||||
"*.map",
|
||||
|
Loading…
Reference in New Issue
Block a user