mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
synchronizer
This commit is contained in:
parent
43f2c6c756
commit
fe277e0cac
@ -171,10 +171,12 @@ class BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then((r) => {
|
||||||
o = Object.assign({}, o);
|
o = Object.assign({}, o);
|
||||||
o.id = query.id;
|
o.id = query.id;
|
||||||
return o;
|
return o;
|
||||||
|
}).catch((error) => {
|
||||||
|
Log.error('Cannot save model', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,10 @@ class LoginScreenComponent extends React.Component {
|
|||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'Navigation/BACK',
|
type: 'Navigation/BACK',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Registry.api().setSession(session.id);
|
||||||
|
|
||||||
|
Registry.synchronizer().start();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({ errorMessage: _('Could not login: %s)', error.message) });
|
this.setState({ errorMessage: _('Could not login: %s)', error.message) });
|
||||||
});
|
});
|
||||||
|
@ -117,7 +117,7 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-21.sqlite' }, (db) => {
|
this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-26.sqlite' }, (db) => {
|
||||||
Log.info('Database was open successfully');
|
Log.info('Database was open successfully');
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
Log.error('Cannot open database: ', error);
|
Log.error('Cannot open database: ', error);
|
||||||
@ -333,6 +333,29 @@ class Database {
|
|||||||
|
|
||||||
Log.info(this.tableFields_);
|
Log.info(this.tableFields_);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// }).then(() => {
|
||||||
|
// let p = this.exec('DELETE FROM notes').then(() => {
|
||||||
|
// return this.exec('DELETE FROM folders');
|
||||||
|
// }).then(() => {
|
||||||
|
// return this.exec('DELETE FROM changes');
|
||||||
|
// }).then(() => {
|
||||||
|
// return this.exec('DELETE FROM settings WHERE `key` = "sync.lastRevId"');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return p.then(() => {
|
||||||
|
// return this.exec('UPDATE settings SET `value` = "' + uuid.create() + '" WHERE `key` = "clientId"');
|
||||||
|
// }).then(() => {
|
||||||
|
// return this.exec('DELETE FROM settings WHERE `key` != "clientId"');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return p;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error && error.code != 0) {
|
if (error && error.code != 0) {
|
||||||
Log.error(error);
|
Log.error(error);
|
||||||
|
@ -33,6 +33,15 @@ class Registry {
|
|||||||
return this.db_;
|
return this.db_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setSynchronizer(s) {
|
||||||
|
this.synchronizer_ = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronizer() {
|
||||||
|
if (!this.synchronizer_) throw new Error('Accessing synchronizer before it has been initialised');
|
||||||
|
return this.synchronizer_;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Registry };
|
export { Registry };
|
@ -164,7 +164,7 @@ class AppComponent extends React.Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let db = new Database();
|
let db = new Database();
|
||||||
//db.setDebugEnabled(Registry.debugMode());
|
//db.setDebugEnabled(Registry.debugMode());
|
||||||
db.setDebugEnabled(false);
|
db.setDebugEnabled(true);
|
||||||
|
|
||||||
BaseModel.dispatch = this.props.dispatch;
|
BaseModel.dispatch = this.props.dispatch;
|
||||||
BaseModel.db_ = db;
|
BaseModel.db_ = db;
|
||||||
@ -199,6 +199,7 @@ class AppComponent extends React.Component {
|
|||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let synchronizer = new Synchronizer(db, Registry.api());
|
let synchronizer = new Synchronizer(db, Registry.api());
|
||||||
|
Registry.setSynchronizer(synchronizer);
|
||||||
synchronizer.start();
|
synchronizer.start();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
Log.error('Initialization error:', error);
|
Log.error('Initialization error:', error);
|
||||||
|
@ -27,12 +27,20 @@ class Synchronizer {
|
|||||||
return this.api_;
|
return this.api_;
|
||||||
}
|
}
|
||||||
|
|
||||||
switchState(state) {
|
processState(state) {
|
||||||
Log.info('Sync: switching state to: ' + state);
|
// if (this.state() == state) {
|
||||||
|
// Log.info('Sync: cannot switch to same state: ' + state);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
Log.info('Sync: processing: ' + state);
|
||||||
|
this.state_ = state;
|
||||||
|
|
||||||
if (state == 'downloadChanges') {
|
if (state == 'downloadChanges') {
|
||||||
let maxRevId = null;
|
let maxRevId = null;
|
||||||
|
let hasMore = false;
|
||||||
this.api().get('synchronizer', { last_id: Setting.value('sync.lastRevId') }).then((syncOperations) => {
|
this.api().get('synchronizer', { last_id: Setting.value('sync.lastRevId') }).then((syncOperations) => {
|
||||||
|
hasMore = syncOperations.has_more;
|
||||||
let chain = [];
|
let chain = [];
|
||||||
for (let i = 0; i < syncOperations.items.length; i++) {
|
for (let i = 0; i < syncOperations.items.length; i++) {
|
||||||
let syncOp = syncOperations.items[i];
|
let syncOp = syncOperations.items[i];
|
||||||
@ -57,6 +65,7 @@ class Synchronizer {
|
|||||||
if (syncOp.type == 'update') {
|
if (syncOp.type == 'update') {
|
||||||
chain.push(() => {
|
chain.push(() => {
|
||||||
return ItemClass.load(syncOp.item_id).then((item) => {
|
return ItemClass.load(syncOp.item_id).then((item) => {
|
||||||
|
if (!item) return;
|
||||||
item = ItemClass.applyPatch(item, syncOp.item);
|
item = ItemClass.applyPatch(item, syncOp.item);
|
||||||
return ItemClass.save(item, { trackChanges: false });
|
return ItemClass.save(item, { trackChanges: false });
|
||||||
});
|
});
|
||||||
@ -71,13 +80,17 @@ class Synchronizer {
|
|||||||
}
|
}
|
||||||
return promiseChain(chain);
|
return promiseChain(chain);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
Log.info('All items synced.');
|
Log.info('All items synced. has_more = ', hasMore);
|
||||||
if (maxRevId) {
|
if (maxRevId) {
|
||||||
Setting.setValue('sync.lastRevId', maxRevId);
|
Setting.setValue('sync.lastRevId', maxRevId);
|
||||||
return Setting.saveAll();
|
return Setting.saveAll();
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.switchState('uploadingChanges');
|
if (hasMore) {
|
||||||
|
this.processState('downloadChanges');
|
||||||
|
} else {
|
||||||
|
this.processState('uploadingChanges');
|
||||||
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
Log.warn('Sync error', error);
|
Log.warn('Sync error', error);
|
||||||
});
|
});
|
||||||
@ -112,11 +125,13 @@ class Synchronizer {
|
|||||||
return this.api().patch(path + '/' + item.id, null, item);
|
return this.api().patch(path + '/' + item.id, null, item);
|
||||||
});
|
});
|
||||||
} else if (c.type == Change.TYPE_DELETE) {
|
} else if (c.type == Change.TYPE_DELETE) {
|
||||||
return this.api().delete(path + '/' + c.item_id);
|
p = this.api().delete(path + '/' + c.item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.then(() => {
|
return p.then(() => {
|
||||||
processedChangeIds = processedChangeIds.concat(c.ids);
|
processedChangeIds = processedChangeIds.concat(c.ids);
|
||||||
|
}).catch((error) => {
|
||||||
|
Log.warn('Failed applying changes', c.ids);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -142,7 +157,7 @@ class Synchronizer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.switchState('downloadChanges');
|
this.processState('downloadChanges');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,10 +45,12 @@ class FoldersController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isMethod('PUT')) {
|
if ($request->isMethod('PUT')) {
|
||||||
if (!$folder) $folder = new Folder();
|
$isNew = !$folder;
|
||||||
|
if ($isNew) $folder = new Folder();
|
||||||
$folder->fromPublicArray($this->putParameters());
|
$folder->fromPublicArray($this->putParameters());
|
||||||
$folder->id = Folder::unhex($id);
|
$folder->id = Folder::unhex($id);
|
||||||
$folder->owner_id = $this->user()->id;
|
$folder->owner_id = $this->user()->id;
|
||||||
|
$folder->setIsNew($isNew);
|
||||||
$folder->save();
|
$folder->save();
|
||||||
return static::successResponse($folder);
|
return static::successResponse($folder);
|
||||||
}
|
}
|
||||||
|
@ -37,10 +37,12 @@ class NotesController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isMethod('PUT')) {
|
if ($request->isMethod('PUT')) {
|
||||||
if (!$note) $note = new Note();
|
$isNew = !$note;
|
||||||
|
if ($isNew) $note = new Note();
|
||||||
$note->fromPublicArray($this->putParameters());
|
$note->fromPublicArray($this->putParameters());
|
||||||
$note->id = Note::unhex($id);
|
$note->id = Note::unhex($id);
|
||||||
$note->owner_id = $this->user()->id;
|
$note->owner_id = $this->user()->id;
|
||||||
|
$note->setIsNew($isNew);
|
||||||
$note->save();
|
$note->save();
|
||||||
return static::successResponse($note);
|
return static::successResponse($note);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ class Change extends BaseModel {
|
|||||||
// - If update, update, delete, update => return 'delete' only
|
// - If update, update, delete, update => return 'delete' only
|
||||||
// - If update, update, update => return last
|
// - If update, update, update => return last
|
||||||
|
|
||||||
|
// $limit = 10000;
|
||||||
$limit = 100;
|
$limit = 100;
|
||||||
$changes = self::where('id', '>', $fromChangeId)
|
$changes = self::where('id', '>', $fromChangeId)
|
||||||
->where('user_id', '=', $userId)
|
->where('user_id', '=', $userId)
|
||||||
@ -46,8 +47,12 @@ class Change extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$itemIdToChange[$change->item_id] = $change;
|
$itemIdToChange[$change->item_id] = $change;
|
||||||
|
|
||||||
|
// echo BaseModel::hex($change->item_id) . ' ' . $change->id . ' ' . Change::enumName('type', $change->type) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// die();
|
||||||
|
|
||||||
|
|
||||||
$output = array();
|
$output = array();
|
||||||
foreach ($itemIdToChange as $itemId => $change) {
|
foreach ($itemIdToChange as $itemId => $change) {
|
||||||
@ -68,11 +73,23 @@ class Change extends BaseModel {
|
|||||||
$syncItem['type'] = 'delete';
|
$syncItem['type'] = 'delete';
|
||||||
} else if (in_array($itemId, $createdItems)) {
|
} else if (in_array($itemId, $createdItems)) {
|
||||||
// Item was created then updated - just return one 'create' event with the latest changes
|
// Item was created then updated - just return one 'create' event with the latest changes
|
||||||
$syncItem['type'] = 'create';
|
|
||||||
$syncItem['item'] = self::requireItemById($change->item_type, $change->item_id);
|
// If $item is null it can mean two things:
|
||||||
|
// - The item has been deleted by the client requesting the sync items (which means the "delete" event is not included in the batch)
|
||||||
|
// - The item has been deleted in the next batch - for example, if we're requesting sync items 0 to 100, the "delete" event is in range 101 to 200.
|
||||||
|
// In the both cases we don't need to do anything. In the first case, the client already knows that the item has been deleted.
|
||||||
|
// In the second case, the "delete" event will be sent later on.
|
||||||
|
$item = BaseItem::byTypeAndId($change->item_type, $change->item_id);
|
||||||
|
if ($item) {
|
||||||
|
$syncItem['type'] = 'create';
|
||||||
|
$syncItem['item'] = $item;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$syncItem['item_fields'] = $itemIdToChangedFields[$change->item_id];
|
$item = BaseItem::byTypeAndId($change->item_type, $change->item_id);
|
||||||
$syncItem['item'] = self::requireItemById($change->item_type, $change->item_id);
|
if ($item) {
|
||||||
|
$syncItem['item_fields'] = $itemIdToChangedFields[$change->item_id];
|
||||||
|
$syncItem['item'] = $item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$output[] = $syncItem;
|
$output[] = $syncItem;
|
||||||
@ -108,7 +125,7 @@ class Change extends BaseModel {
|
|||||||
|
|
||||||
static private function requireItemById($itemTypeId, $itemId) {
|
static private function requireItemById($itemTypeId, $itemId) {
|
||||||
$item = BaseItem::byTypeAndId($itemTypeId, $itemId);
|
$item = BaseItem::byTypeAndId($itemTypeId, $itemId);
|
||||||
if (!$item) throw new \Exception('No such item: ' . $itemTypeId . ' ' . $itemId);
|
if (!$item) throw new \Exception('No such item: ' . $itemTypeId . ' ' . BaseModel::hex($itemId));
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user