From 1c9f358f96f1f2f101082d8089b69d13160b2fd9 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 12 May 2017 20:17:23 +0000 Subject: [PATCH] Handle settings --- ReactNativeClient/src/base-model.js | 7 +-- ReactNativeClient/src/database.js | 65 ++++++++++++++------ ReactNativeClient/src/main.js | 16 ++--- ReactNativeClient/src/models/setting.js | 82 +++++++++++++++++++++++++ ReactNativeClient/src/registry.js | 45 ++++++++++++++ ReactNativeClient/src/root.js | 41 +++++++++++-- 6 files changed, 215 insertions(+), 41 deletions(-) create mode 100644 ReactNativeClient/src/models/setting.js create mode 100644 ReactNativeClient/src/registry.js diff --git a/ReactNativeClient/src/base-model.js b/ReactNativeClient/src/base-model.js index c09ee0af9..1764199dc 100644 --- a/ReactNativeClient/src/base-model.js +++ b/ReactNativeClient/src/base-model.js @@ -1,5 +1,6 @@ import { Log } from 'src/log.js'; import { Database } from 'src/database.js'; +import { Registry } from 'src/registry.js'; import createUuid from 'uuid/v4'; class BaseModel { @@ -29,12 +30,8 @@ class BaseModel { return this.db().exec(query.sql, query.params).then(() => { return o; }); } - static setDb(database) { - this.db_ = database; - } - static db() { - return this.db_; + return Registry.db(); } } diff --git a/ReactNativeClient/src/database.js b/ReactNativeClient/src/database.js index ba1fc9b64..5842ab033 100644 --- a/ReactNativeClient/src/database.js +++ b/ReactNativeClient/src/database.js @@ -1,5 +1,6 @@ import SQLite from 'react-native-sqlite-storage'; import { Log } from 'src/log.js'; +import createUuid from 'uuid/v4'; const structureSql = ` CREATE TABLE folders ( @@ -84,6 +85,7 @@ class Database { constructor() { this.debugMode_ = false; + this.initialized_ = false; } setDebugEnabled(v) { @@ -95,14 +97,26 @@ class Database { return this.debugMode_; } + initialized() { + return this.initialized_; + } + open() { - this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin.sqlite' }, (db) => { + this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-4.sqlite' }, (db) => { Log.info('Database was open successfully'); }, (error) => { Log.error('Cannot open database: ', error); }); - this.updateSchema(); + return this.updateSchema(); + } + + static enumToId(type, s) { + if (type == 'settings') { + if (s == 'int') return 1; + if (s == 'string') return 2; + } + throw new Error('Unknown enum type or value: ' + type + ', ' + s); } sqlStringToLines(sql) { @@ -202,28 +216,41 @@ class Database { }; } + transaction(readyCallack, errorCallback, successCallback) { + return this.db_.transaction(readyCallack, errorCallback, successCallback); + } + updateSchema() { Log.info('Checking for database schema update...'); - this.selectOne('SELECT * FROM version LIMIT 1').then((row) => { - Log.info('Current database version', row); - // TODO: version update logic - }).catch((error) => { - // 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. + return new Promise((resolve, reject) => { + this.selectOne('SELECT * FROM version LIMIT 1').then((row) => { + Log.info('Current database version', row); + resolve(); + // TODO: version update logic + }).catch((error) => { + // 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. - Log.info('Database is new - creating the schema...'); + Log.info('Database is new - creating the schema...'); - let statements = this.sqlStringToLines(structureSql) - this.db_.transaction((tx) => { - for (let i = 0; i < statements.length; i++) { - tx.executeSql(statements[i]); - } - }, (error) => { - Log.error('Could not create database schema:', error); - }, () => { - Log.info('Database schema created successfully'); + let statements = this.sqlStringToLines(structureSql) + //this.db_.transaction((tx) => { + this.transaction((tx) => { + try { + for (let i = 0; i < statements.length; i++) { + tx.executeSql(statements[i]); + } + tx.executeSql('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + createUuid() + '", "' + Database.enumToId('settings', 'string') + '")'); + } catch (error) { + reject(error); + } + }, (error) => { + reject(error); + }, () => { + resolve('Database schema created successfully'); + }); }); }); } diff --git a/ReactNativeClient/src/main.js b/ReactNativeClient/src/main.js index d60e4d274..30c0baa7a 100644 --- a/ReactNativeClient/src/main.js +++ b/ReactNativeClient/src/main.js @@ -1,20 +1,14 @@ import { AppRegistry } from 'react-native'; -import { Database } from 'src/database.js' import { Log } from 'src/log.js' import { Root } from 'src/root.js'; -import { BaseModel } from 'src/base-model.js'; +import { Registry } from 'src/registry.js'; +import { Database } from 'src/database.js'; function main() { - let debugMode = true; - let clientId = 'A7D301DA7D301DA7D301DA7D301DA7D3'; - + Registry.setDebugMode(true); AppRegistry.registerComponent('AwesomeProject', () => Root); - - let db = new Database(); - db.setDebugEnabled(debugMode); - db.open(); - - BaseModel.setDb(db); + // Note: The final part of the initialization process is in + // AppComponent.componentDidMount(), when the application is ready. } export { main } \ No newline at end of file diff --git a/ReactNativeClient/src/models/setting.js b/ReactNativeClient/src/models/setting.js new file mode 100644 index 000000000..3c317d786 --- /dev/null +++ b/ReactNativeClient/src/models/setting.js @@ -0,0 +1,82 @@ +import { BaseModel } from 'src/base-model.js'; +import { Log } from 'src/log.js'; +import { Database } from 'src/database.js'; + +class Setting extends BaseModel { + + static tableName() { + return 'settings'; + } + + static defaultSetting(key) { + if (!this.defaults_) { + this.defaults_ = { + 'clientId': { value: '', type: 'string' }, + 'sessionId': { value: '', type: 'string' }, + 'lastUpdateTime': { value: '', type: 'int' }, + } + } + if (!(key in this.defaults_)) throw new Error('Unknown key: ' + key); + + let output = Object.assign({}, this.defaults_[key]); + output.key = key; + return output; + } + + static load() { + this.cache_ = []; + return this.db().selectAll('SELECT * FROM settings').then((r) => { + for (let i = 0; i < r.rows.length; i++) { + this.cache_.push(r.rows.item(i)); + } + }); + } + + static setValue(key, value) { + this.scheduleUpdate(); + for (let i = 0; i < this.cache_.length; i++) { + if (this.cache_[i].key == key) { + this.cache_[i].value = value; + return; + } + } + + let s = this.defaultSetting(key); + s.value = value; + this.cache_.push(s); + } + + static value(key) { + for (let i = 0; i < this.cache_.length; i++) { + if (this.cache_[i].key == key) { + return this.cache_[i].value; + } + } + + let s = this.defaultSetting(key); + return s.value; + } + + static scheduleUpdate() { + if (this.updateTimeoutId) clearTimeout(this.updateTimeoutId); + + this.updateTimeoutId = setTimeout(() => { + Log.info('Saving settings...'); + this.updateTimeoutId = null; + BaseModel.db().transaction((tx) => { + tx.executeSql('DELETE FROM settings'); + for (let i = 0; i < this.cache_.length; i++) { + let q = Database.insertQuery(this.tableName(), this.cache_[i]); + tx.executeSql(q.sql, q.params); + } + }, (error) => { + Log.warn('Could not update settings:', error); + }, () => { + Log.info('Settings have been saved.'); + }); + }, 500); + } + +} + +export { Setting }; \ No newline at end of file diff --git a/ReactNativeClient/src/registry.js b/ReactNativeClient/src/registry.js new file mode 100644 index 000000000..43664217b --- /dev/null +++ b/ReactNativeClient/src/registry.js @@ -0,0 +1,45 @@ +// Stores global dynamic objects that are not state but that are required +// throughout the application. Dependency injection would be a better solution +// but more complex and YAGNI at this point. However classes that make use of the +// registry should be designed in such a way that they can be converted to use +// dependency injection later on (eg. `BaseModel.db()`, `Synchroniser.api()`) + +import { Database } from 'src/database.js' + +class Registry { + + static setDebugMode(v) { + this.debugMode_ = v; + } + + static debugMode() { + if (this.debugMode_ === undefined) return false; + return this.debugMode_; + } + + static setApi(v) { + this.api_ = v; + } + + static setDb(v) { + this.db_ = v; + } + + static db() { + if (!this.db_) throw new Error('Accessing database before it has been initialised'); + // if (!this.db_) { + // this.db_ = new Database(); + // this.db_.setDebugEnabled(this.debugMode()); + // this.db_.open(); + // } + return this.db_; + } + + static api() { + if (!this.api_) throw new Error('Accessing web API before it has been initialised'); + return this.api_; + } + +} + +export { Registry }; \ No newline at end of file diff --git a/ReactNativeClient/src/root.js b/ReactNativeClient/src/root.js index 1ad9e4b30..c9cb08e6f 100644 --- a/ReactNativeClient/src/root.js +++ b/ReactNativeClient/src/root.js @@ -8,7 +8,10 @@ import { StackNavigator } from 'react-navigation'; import { addNavigationHelpers } from 'react-navigation'; import { Log } from 'src/log.js' import { Note } from 'src/models/note.js' +import { Database } from 'src/database.js' +import { Registry } from 'src/registry.js' import { ItemList } from 'src/components/item-list.js' +import { Setting } from 'src/models/setting.js' let defaultState = { defaultText: 'bla', @@ -93,12 +96,24 @@ class NotesScreenComponent extends React.Component { }); } + loginButton_press = () => { + + } + + syncButton_press = () => { + Log.info('SYNC'); + } + render() { const { navigate } = this.props.navigation; return ( -