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

synchronizer

This commit is contained in:
Laurent Cozic 2017-05-21 21:55:01 +02:00
parent 43f2c6c756
commit fe277e0cac
9 changed files with 91 additions and 16 deletions

View File

@ -171,10 +171,12 @@ class BaseModel {
}
}
}
}).then(() => {
}).then((r) => {
o = Object.assign({}, o);
o.id = query.id;
return o;
}).catch((error) => {
Log.error('Cannot save model', error);
});
}

View File

@ -58,6 +58,10 @@ class LoginScreenComponent extends React.Component {
this.props.dispatch({
type: 'Navigation/BACK',
});
Registry.api().setSession(session.id);
Registry.synchronizer().start();
}).catch((error) => {
this.setState({ errorMessage: _('Could not login: %s)', error.message) });
});

View File

@ -117,7 +117,7 @@ class Database {
}
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');
}, (error) => {
Log.error('Cannot open database: ', error);
@ -333,6 +333,29 @@ class Database {
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) => {
if (error && error.code != 0) {
Log.error(error);

View File

@ -33,6 +33,15 @@ class Registry {
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 };

View File

@ -164,7 +164,7 @@ class AppComponent extends React.Component {
componentDidMount() {
let db = new Database();
//db.setDebugEnabled(Registry.debugMode());
db.setDebugEnabled(false);
db.setDebugEnabled(true);
BaseModel.dispatch = this.props.dispatch;
BaseModel.db_ = db;
@ -199,6 +199,7 @@ class AppComponent extends React.Component {
});
}).then(() => {
let synchronizer = new Synchronizer(db, Registry.api());
Registry.setSynchronizer(synchronizer);
synchronizer.start();
}).catch((error) => {
Log.error('Initialization error:', error);

View File

@ -27,12 +27,20 @@ class Synchronizer {
return this.api_;
}
switchState(state) {
Log.info('Sync: switching state to: ' + state);
processState(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') {
let maxRevId = null;
let hasMore = false;
this.api().get('synchronizer', { last_id: Setting.value('sync.lastRevId') }).then((syncOperations) => {
hasMore = syncOperations.has_more;
let chain = [];
for (let i = 0; i < syncOperations.items.length; i++) {
let syncOp = syncOperations.items[i];
@ -57,6 +65,7 @@ class Synchronizer {
if (syncOp.type == 'update') {
chain.push(() => {
return ItemClass.load(syncOp.item_id).then((item) => {
if (!item) return;
item = ItemClass.applyPatch(item, syncOp.item);
return ItemClass.save(item, { trackChanges: false });
});
@ -71,13 +80,17 @@ class Synchronizer {
}
return promiseChain(chain);
}).then(() => {
Log.info('All items synced.');
Log.info('All items synced. has_more = ', hasMore);
if (maxRevId) {
Setting.setValue('sync.lastRevId', maxRevId);
return Setting.saveAll();
}
}).then(() => {
this.switchState('uploadingChanges');
if (hasMore) {
this.processState('downloadChanges');
} else {
this.processState('uploadingChanges');
}
}).catch((error) => {
Log.warn('Sync error', error);
});
@ -112,11 +125,13 @@ class Synchronizer {
return this.api().patch(path + '/' + item.id, null, item);
});
} 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(() => {
processedChangeIds = processedChangeIds.concat(c.ids);
}).catch((error) => {
Log.warn('Failed applying changes', c.ids);
});
});
}
@ -142,7 +157,7 @@ class Synchronizer {
return;
}
this.switchState('downloadChanges');
this.processState('downloadChanges');
}
}

View File

@ -45,10 +45,12 @@ class FoldersController extends ApiController {
}
if ($request->isMethod('PUT')) {
if (!$folder) $folder = new Folder();
$isNew = !$folder;
if ($isNew) $folder = new Folder();
$folder->fromPublicArray($this->putParameters());
$folder->id = Folder::unhex($id);
$folder->owner_id = $this->user()->id;
$folder->setIsNew($isNew);
$folder->save();
return static::successResponse($folder);
}

View File

@ -37,10 +37,12 @@ class NotesController extends ApiController {
}
if ($request->isMethod('PUT')) {
if (!$note) $note = new Note();
$isNew = !$note;
if ($isNew) $note = new Note();
$note->fromPublicArray($this->putParameters());
$note->id = Note::unhex($id);
$note->owner_id = $this->user()->id;
$note->setIsNew($isNew);
$note->save();
return static::successResponse($note);
}

View File

@ -18,6 +18,7 @@ class Change extends BaseModel {
// - If update, update, delete, update => return 'delete' only
// - If update, update, update => return last
// $limit = 10000;
$limit = 100;
$changes = self::where('id', '>', $fromChangeId)
->where('user_id', '=', $userId)
@ -46,8 +47,12 @@ class Change extends BaseModel {
}
$itemIdToChange[$change->item_id] = $change;
// echo BaseModel::hex($change->item_id) . ' ' . $change->id . ' ' . Change::enumName('type', $change->type) . "\n";
}
// die();
$output = array();
foreach ($itemIdToChange as $itemId => $change) {
@ -68,11 +73,23 @@ class Change extends BaseModel {
$syncItem['type'] = 'delete';
} else if (in_array($itemId, $createdItems)) {
// 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 {
$syncItem['item_fields'] = $itemIdToChangedFields[$change->item_id];
$syncItem['item'] = self::requireItemById($change->item_type, $change->item_id);
$item = BaseItem::byTypeAndId($change->item_type, $change->item_id);
if ($item) {
$syncItem['item_fields'] = $itemIdToChangedFields[$change->item_id];
$syncItem['item'] = $item;
}
}
$output[] = $syncItem;
@ -108,7 +125,7 @@ class Change extends BaseModel {
static private function requireItemById($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;
}