mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
sycnhronizer
This commit is contained in:
parent
c030791f07
commit
43f2c6c756
@ -1,6 +1,5 @@
|
||||
import { Log } from 'src/log.js';
|
||||
import { Database } from 'src/database.js';
|
||||
import { Registry } from 'src/registry.js';
|
||||
import { uuid } from 'src/uuid.js';
|
||||
|
||||
class BaseModel {
|
||||
@ -9,6 +8,7 @@ class BaseModel {
|
||||
static ITEM_TYPE_FOLDER = 2;
|
||||
static tableInfo_ = null;
|
||||
static tableKeys_ = null;
|
||||
static db_ = null;
|
||||
|
||||
static tableName() {
|
||||
throw new Error('Must be overriden');
|
||||
@ -42,6 +42,20 @@ class BaseModel {
|
||||
return this.db().tableFieldNames(this.tableName());
|
||||
}
|
||||
|
||||
static fields() {
|
||||
return this.db().tableFields(this.tableName());
|
||||
}
|
||||
|
||||
static new() {
|
||||
let fields = this.fields();
|
||||
let output = {};
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
let f = fields[i];
|
||||
output[f.name] = f.default;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static fromApiResult(apiResult) {
|
||||
let fieldNames = this.fieldNames();
|
||||
let output = {};
|
||||
@ -78,6 +92,15 @@ class BaseModel {
|
||||
|
||||
static saveQuery(o, isNew = 'auto') {
|
||||
if (isNew == 'auto') isNew = !o.id;
|
||||
|
||||
let temp = {}
|
||||
let fieldNames = this.fieldNames();
|
||||
for (let i = 0; i < fieldNames.length; i++) {
|
||||
let n = fieldNames[i];
|
||||
if (n in o) temp[n] = o[n];
|
||||
}
|
||||
o = temp;
|
||||
|
||||
let query = '';
|
||||
let itemId = o.id;
|
||||
|
||||
@ -106,6 +129,8 @@ class BaseModel {
|
||||
|
||||
query.id = itemId;
|
||||
|
||||
Log.info('Saving', o);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@ -176,7 +201,8 @@ class BaseModel {
|
||||
}
|
||||
|
||||
static db() {
|
||||
return Registry.db();
|
||||
if (!this.db_) throw new Error('Accessing database before it has been initialised');
|
||||
return this.db_;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class NoteScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { note: Note.newNote() }
|
||||
this.state = { note: Note.new() }
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
@ -48,8 +48,6 @@ class NoteScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
Log.info(this.state.note);
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<ScreenHeader navState={this.props.navigation.state} />
|
||||
@ -65,7 +63,7 @@ class NoteScreenComponent extends React.Component {
|
||||
const NoteScreen = connect(
|
||||
(state) => {
|
||||
return {
|
||||
note: state.selectedNoteId ? Note.byId(state.notes, state.selectedNoteId) : Note.newNote(state.selectedFolderId),
|
||||
note: state.selectedNoteId ? Note.byId(state.notes, state.selectedNoteId) : Note.new(state.selectedFolderId),
|
||||
};
|
||||
}
|
||||
)(NoteScreenComponent)
|
||||
|
@ -26,8 +26,8 @@ CREATE TABLE notes (
|
||||
author TEXT NOT NULL DEFAULT "",
|
||||
source_url TEXT NOT NULL DEFAULT "",
|
||||
is_todo BOOLEAN NOT NULL DEFAULT 0,
|
||||
todo_due INT NOT NULL DEFAULT "",
|
||||
todo_completed INT NOT NULL DEFAULT "",
|
||||
todo_due INT NOT NULL DEFAULT 0,
|
||||
todo_completed BOOLEAN NOT NULL DEFAULT 0,
|
||||
source_application TEXT NOT NULL DEFAULT "",
|
||||
application_data TEXT NOT NULL DEFAULT "",
|
||||
\`order\` INT NOT NULL DEFAULT 0
|
||||
@ -82,7 +82,9 @@ CREATE TABLE settings (
|
||||
CREATE TABLE table_fields (
|
||||
id INTEGER PRIMARY KEY,
|
||||
table_name TEXT,
|
||||
field_name TEXT
|
||||
field_name TEXT,
|
||||
field_type INT,
|
||||
field_default TEXT
|
||||
);
|
||||
|
||||
INSERT INTO version (version) VALUES (1);
|
||||
@ -90,6 +92,11 @@ INSERT INTO version (version) VALUES (1);
|
||||
|
||||
class Database {
|
||||
|
||||
static TYPE_INT = 1;
|
||||
static TYPE_TEXT = 2;
|
||||
static TYPE_BOOLEAN = 3;
|
||||
static TYPE_NUMERIC = 4;
|
||||
|
||||
constructor() {
|
||||
this.debugMode_ = false;
|
||||
this.initialized_ = false;
|
||||
@ -110,7 +117,7 @@ class Database {
|
||||
}
|
||||
|
||||
open() {
|
||||
this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-15.sqlite' }, (db) => {
|
||||
this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-21.sqlite' }, (db) => {
|
||||
Log.info('Database was open successfully');
|
||||
}, (error) => {
|
||||
Log.error('Cannot open database: ', error);
|
||||
@ -119,20 +126,41 @@ class Database {
|
||||
return this.initialize();
|
||||
}
|
||||
|
||||
static enumToId(type, s) {
|
||||
static enumId(type, s) {
|
||||
if (type == 'settings') {
|
||||
if (s == 'int') return 1;
|
||||
if (s == 'string') return 2;
|
||||
}
|
||||
if (type == 'fieldType') {
|
||||
return this['TYPE_' + s];
|
||||
}
|
||||
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
|
||||
}
|
||||
|
||||
tableFieldNames(tableName) {
|
||||
let tf = this.tableFields(tableName);
|
||||
let output = [];
|
||||
for (let i = 0; i < tf.length; i++) {
|
||||
output.push(tf[i].name);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
tableFields(tableName) {
|
||||
if (!this.tableFields_) throw new Error('Fields have not been loaded yet');
|
||||
if (!this.tableFields_[tableName]) throw new Error('Unknown table: ' + tableName);
|
||||
return this.tableFields_[tableName];
|
||||
}
|
||||
|
||||
static formatValue(type, value) {
|
||||
if (value === null || value === undefined) return null;
|
||||
if (type == this.TYPE_INT) return Number(value);
|
||||
if (type == this.TYPE_TEXT) return value;
|
||||
if (type == this.TYPE_BOOLEAN) return !!Number(value);
|
||||
if (type == this.TYPE_NUMERIC) return Number(value);
|
||||
throw new Error('Unknown type: ' + type);
|
||||
}
|
||||
|
||||
sqlStringToLines(sql) {
|
||||
let output = [];
|
||||
let lines = sql.split("\n");
|
||||
@ -213,7 +241,7 @@ class Database {
|
||||
for (let key in data) {
|
||||
if (!data.hasOwnProperty(key)) continue;
|
||||
if (sql != '') sql += ', ';
|
||||
sql += key + '=?';
|
||||
sql += '`' + key + '`=?';
|
||||
params.push(data[key]);
|
||||
}
|
||||
|
||||
@ -251,9 +279,17 @@ class Database {
|
||||
if (!queries) queries = [];
|
||||
return this.exec('PRAGMA table_info("' + tableName + '")').then((pragmaResult) => {
|
||||
for (let i = 0; i < pragmaResult.rows.length; i++) {
|
||||
let item = pragmaResult.rows.item(i);
|
||||
// In SQLite, if the default value is a string it has double quotes around it, so remove them here
|
||||
let defaultValue = item.dflt_value;
|
||||
if (typeof defaultValue == 'string' && defaultValue.length >= 2 && defaultValue[0] == '"' && defaultValue[defaultValue.length - 1] == '"') {
|
||||
defaultValue = defaultValue.substr(1, defaultValue.length - 2);
|
||||
}
|
||||
let q = Database.insertQuery('table_fields', {
|
||||
table_name: tableName,
|
||||
field_name: pragmaResult.rows.item(i).name,
|
||||
field_name: item.name,
|
||||
field_type: Database.enumId('fieldType', item.type),
|
||||
field_default: defaultValue,
|
||||
});
|
||||
queries.push(q);
|
||||
}
|
||||
@ -288,10 +324,21 @@ class Database {
|
||||
for (let i = 0; i < r.rows.length; i++) {
|
||||
let row = r.rows.item(i);
|
||||
if (!this.tableFields_[row.table_name]) this.tableFields_[row.table_name] = [];
|
||||
this.tableFields_[row.table_name].push(row.field_name);
|
||||
this.tableFields_[row.table_name].push({
|
||||
name: row.field_name,
|
||||
type: row.field_type,
|
||||
default: Database.formatValue(row.field_type, row.field_default),
|
||||
});
|
||||
}
|
||||
|
||||
Log.info(this.tableFields_);
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error && error.code != 0) {
|
||||
Log.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume that error was:
|
||||
// { message: 'no such table: version (code 1): , while compiling: SELECT * FROM version', code: 0 }
|
||||
// which means the database is empty and the tables need to be created.
|
||||
@ -304,7 +351,7 @@ class Database {
|
||||
for (let i = 0; i < statements.length; i++) {
|
||||
tx.executeSql(statements[i]);
|
||||
}
|
||||
tx.executeSql('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumToId('settings', 'string') + '")');
|
||||
tx.executeSql('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")');
|
||||
}).then(() => {
|
||||
Log.info('Database schema created successfully');
|
||||
// Calling initialize() now that the db has been created will make it go through
|
||||
|
@ -19,13 +19,10 @@ class Note extends BaseModel {
|
||||
return true;
|
||||
}
|
||||
|
||||
static newNote(parentId = null) {
|
||||
return {
|
||||
id: null,
|
||||
title: '',
|
||||
body: '',
|
||||
parent_id: parentId,
|
||||
}
|
||||
static new(parentId = '') {
|
||||
let output = super.new();
|
||||
output.parent_id = parentId;
|
||||
return output;
|
||||
}
|
||||
|
||||
static previews(parentId) {
|
||||
|
@ -165,7 +165,9 @@ class AppComponent extends React.Component {
|
||||
let db = new Database();
|
||||
//db.setDebugEnabled(Registry.debugMode());
|
||||
db.setDebugEnabled(false);
|
||||
|
||||
BaseModel.dispatch = this.props.dispatch;
|
||||
BaseModel.db_ = db;
|
||||
|
||||
db.open().then(() => {
|
||||
Log.info('Database is ready.');
|
||||
@ -187,10 +189,6 @@ class AppComponent extends React.Component {
|
||||
|
||||
Log.info('Loading folders...');
|
||||
|
||||
// Folder.noteIds('80a90393377b440bbf6edfe849bb87c5').then((ids) => {
|
||||
// Log.info(ids);
|
||||
// });
|
||||
|
||||
Folder.all().then((folders) => {
|
||||
this.props.dispatch({
|
||||
type: 'FOLDERS_UPDATE_ALL',
|
||||
|
@ -3,6 +3,8 @@ import { Log } from 'src/log.js';
|
||||
import { Setting } from 'src/models/setting.js';
|
||||
import { Change } from 'src/models/change.js';
|
||||
import { Folder } from 'src/models/folder.js';
|
||||
import { Note } from 'src/models/note.js';
|
||||
import { BaseModel } from 'src/base-model.js';
|
||||
import { promiseChain } from 'src/promise-chain.js';
|
||||
|
||||
class Synchronizer {
|
||||
@ -35,31 +37,36 @@ class Synchronizer {
|
||||
for (let i = 0; i < syncOperations.items.length; i++) {
|
||||
let syncOp = syncOperations.items[i];
|
||||
if (syncOp.id > maxRevId) maxRevId = syncOp.id;
|
||||
|
||||
let ItemClass = null;
|
||||
if (syncOp.item_type == 'folder') {
|
||||
ItemClass = Folder;
|
||||
} else if (syncOp.item_type == 'note') {
|
||||
ItemClass = Note;
|
||||
}
|
||||
|
||||
if (syncOp.type == 'create') {
|
||||
chain.push(() => {
|
||||
let folder = Folder.fromApiResult(syncOp.item);
|
||||
// TODO: automatically handle NULL fields by checking type and default value of field
|
||||
if (!folder.parent_id) folder.parent_id = '';
|
||||
return Folder.save(folder, { isNew: true, trackChanges: false });
|
||||
});
|
||||
}
|
||||
if (syncOp.type == 'create') {
|
||||
chain.push(() => {
|
||||
let item = ItemClass.fromApiResult(syncOp.item);
|
||||
// TODO: automatically handle NULL fields by checking type and default value of field
|
||||
if ('parent_id' in item && !item.parent_id) item.parent_id = '';
|
||||
return ItemClass.save(item, { isNew: true, trackChanges: false });
|
||||
});
|
||||
}
|
||||
|
||||
if (syncOp.type == 'update') {
|
||||
chain.push(() => {
|
||||
return Folder.load(syncOp.item_id).then((folder) => {
|
||||
folder = Folder.applyPatch(folder, syncOp.item);
|
||||
return Folder.save(folder, { trackChanges: false });
|
||||
});
|
||||
if (syncOp.type == 'update') {
|
||||
chain.push(() => {
|
||||
return ItemClass.load(syncOp.item_id).then((item) => {
|
||||
item = ItemClass.applyPatch(item, syncOp.item);
|
||||
return ItemClass.save(item, { trackChanges: false });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (syncOp.type == 'delete') {
|
||||
chain.push(() => {
|
||||
return Folder.delete(syncOp.item_id, { trackChanges: false });
|
||||
});
|
||||
}
|
||||
if (syncOp.type == 'delete') {
|
||||
chain.push(() => {
|
||||
return ItemClass.delete(syncOp.item_id, { trackChanges: false });
|
||||
});
|
||||
}
|
||||
}
|
||||
return promiseChain(chain);
|
||||
@ -84,18 +91,28 @@ class Synchronizer {
|
||||
chain.push(() => {
|
||||
let p = null;
|
||||
|
||||
let ItemClass = null;
|
||||
let path = null;
|
||||
if (c.item_type == BaseModel.ITEM_TYPE_FOLDER) {
|
||||
ItemClass = Folder;
|
||||
path = 'folders';
|
||||
} else if (c.item_type == BaseModel.ITEM_TYPE_NOTE) {
|
||||
ItemClass = Note;
|
||||
path = 'notes';
|
||||
}
|
||||
|
||||
if (c.type == Change.TYPE_NOOP) {
|
||||
p = Promise.resolve();
|
||||
} else if (c.type == Change.TYPE_CREATE) {
|
||||
p = Folder.load(c.item_id).then((folder) => {
|
||||
return this.api().put('folders/' + folder.id, null, folder);
|
||||
p = ItemClass.load(c.item_id).then((item) => {
|
||||
return this.api().put(path + '/' + item.id, null, item);
|
||||
});
|
||||
} else if (c.type == Change.TYPE_UPDATE) {
|
||||
p = Folder.load(c.item_id).then((folder) => {
|
||||
return this.api().patch('folders/' + folder.id, null, folder);
|
||||
p = ItemClass.load(c.item_id).then((item) => {
|
||||
return this.api().patch(path + '/' + item.id, null, item);
|
||||
});
|
||||
} else if (c.type == Change.TYPE_DELETE) {
|
||||
return this.api().delete('folders/' + c.item_id);
|
||||
return this.api().delete(path + '/' + c.item_id);
|
||||
}
|
||||
|
||||
return p.then(() => {
|
||||
|
@ -26,6 +26,11 @@ CREATE TABLE `notes` (
|
||||
`is_todo` tinyint(1) NOT NULL default '0',
|
||||
`todo_due` int(11) NOT NULL default '0',
|
||||
`todo_completed` int(11) NOT NULL default '0',
|
||||
`application_data` varchar(1024) NOT NULL DEFAULT "",
|
||||
`author` varchar(512) NOT NULL DEFAULT "",
|
||||
`source` varchar(512) NOT NULL DEFAULT "",
|
||||
`source_application` varchar(512) NOT NULL DEFAULT "",
|
||||
`source_url` varchar(1024) NOT NULL DEFAULT "",
|
||||
PRIMARY KEY (`id`)
|
||||
) CHARACTER SET=utf8;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user