2017-06-24 20:06:28 +02:00
|
|
|
import { BaseModel } from 'lib/base-model.js';
|
|
|
|
import { Database } from 'lib/database.js';
|
2017-08-20 22:11:32 +02:00
|
|
|
import { Logger } from 'lib/logger.js';
|
2017-07-24 23:29:40 +02:00
|
|
|
import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale.js';
|
2017-05-12 22:17:23 +02:00
|
|
|
|
|
|
|
class Setting extends BaseModel {
|
|
|
|
|
|
|
|
static tableName() {
|
|
|
|
return 'settings';
|
|
|
|
}
|
|
|
|
|
2017-07-03 21:50:45 +02:00
|
|
|
static modelType() {
|
|
|
|
return BaseModel.TYPE_SETTING;
|
2017-06-19 21:26:27 +02:00
|
|
|
}
|
|
|
|
|
2017-07-24 20:58:11 +02:00
|
|
|
static settingMetadata(key) {
|
|
|
|
if (!(key in this.metadata_)) throw new Error('Unknown key: ' + key);
|
|
|
|
let output = Object.assign({}, this.metadata_[key]);
|
2017-05-12 22:17:23 +02:00
|
|
|
output.key = key;
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2017-08-22 19:57:35 +02:00
|
|
|
static keys(publicOnly = false, appType = null) {
|
|
|
|
if (!this.keys_) {
|
|
|
|
this.keys_ = [];
|
|
|
|
for (let n in this.metadata_) {
|
|
|
|
if (!this.metadata_.hasOwnProperty(n)) continue;
|
|
|
|
this.keys_.push(n);
|
|
|
|
}
|
|
|
|
this.keys_.sort();
|
2017-05-16 23:46:21 +02:00
|
|
|
}
|
|
|
|
|
2017-08-22 19:57:35 +02:00
|
|
|
if (appType || publicOnly) {
|
|
|
|
let output = [];
|
|
|
|
for (let i = 0; i < this.keys_.length; i++) {
|
|
|
|
const md = this.settingMetadata(this.keys_[i]);
|
|
|
|
if (publicOnly && !md.public) continue;
|
|
|
|
if (appType && md.appTypes && md.appTypes.indexOf(appType) < 0) continue;
|
|
|
|
output.push(md.key);
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
} else {
|
|
|
|
return this.keys_;
|
2017-06-27 22:16:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-31 22:51:24 +02:00
|
|
|
static isPublic(key) {
|
2017-08-22 19:57:35 +02:00
|
|
|
return this.keys(true).indexOf(key) >= 0;
|
2017-07-31 22:51:24 +02:00
|
|
|
}
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
static load() {
|
2017-07-26 19:49:01 +02:00
|
|
|
this.cancelScheduleSave();
|
2017-05-12 22:17:23 +02:00
|
|
|
this.cache_ = [];
|
2017-06-19 21:26:27 +02:00
|
|
|
return this.modelSelectAll('SELECT * FROM settings').then((rows) => {
|
2017-07-26 20:36:16 +02:00
|
|
|
this.cache_ = [];
|
2017-07-24 22:36:49 +02:00
|
|
|
|
2017-07-26 20:36:16 +02:00
|
|
|
// Old keys - can be removed later
|
|
|
|
const ignore = ['clientId', 'sync.onedrive.auth', 'syncInterval', 'todoOnTop', 'todosOnTop'];
|
2017-07-25 23:55:26 +02:00
|
|
|
|
2017-07-26 20:36:16 +02:00
|
|
|
for (let i = 0; i < rows.length; i++) {
|
|
|
|
let c = rows[i];
|
|
|
|
|
|
|
|
if (ignore.indexOf(c.key) >= 0) continue;
|
2017-07-26 19:49:01 +02:00
|
|
|
|
|
|
|
// console.info(c.key + ' = ' + c.value);
|
2017-07-25 23:55:26 +02:00
|
|
|
|
|
|
|
c.value = this.formatValue(c.key, c.value);
|
|
|
|
|
2017-07-26 20:36:16 +02:00
|
|
|
this.cache_.push(c);
|
2017-07-24 22:36:49 +02:00
|
|
|
}
|
2017-07-26 19:49:01 +02:00
|
|
|
|
|
|
|
const keys = this.keys();
|
|
|
|
let keyToValues = {};
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
keyToValues[keys[i]] = this.value(keys[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.dispatch({
|
|
|
|
type: 'SETTINGS_UPDATE_ALL',
|
|
|
|
settings: keyToValues,
|
|
|
|
});
|
2017-05-12 22:17:23 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-24 20:51:43 +02:00
|
|
|
static setConstant(key, value) {
|
2017-07-06 23:30:45 +02:00
|
|
|
if (!(key in this.constants_)) throw new Error('Unknown constant key: ' + key);
|
2017-06-24 20:51:43 +02:00
|
|
|
this.constants_[key] = value;
|
|
|
|
}
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
static setValue(key, value) {
|
2017-06-19 20:58:49 +02:00
|
|
|
if (!this.cache_) throw new Error('Settings have not been initialized!');
|
2017-07-31 22:51:24 +02:00
|
|
|
|
|
|
|
value = this.formatValue(key, value);
|
2017-06-19 20:58:49 +02:00
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
for (let i = 0; i < this.cache_.length; i++) {
|
2017-07-24 20:58:11 +02:00
|
|
|
let c = this.cache_[i];
|
|
|
|
if (c.key == key) {
|
|
|
|
const md = this.settingMetadata(key);
|
|
|
|
|
2017-07-25 23:55:26 +02:00
|
|
|
if (md.isEnum === true) {
|
2017-07-24 20:58:11 +02:00
|
|
|
if (!this.isAllowedEnumOption(key, value)) {
|
|
|
|
throw new Error(_('Invalid option value: "%s". Possible values are: %s.', value, this.enumOptionsDoc(key)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c.value === value) return;
|
2017-07-25 23:55:26 +02:00
|
|
|
|
2017-07-31 22:51:24 +02:00
|
|
|
this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value);
|
2017-07-25 23:55:26 +02:00
|
|
|
|
|
|
|
c.value = this.formatValue(key, value);
|
2017-07-26 19:49:01 +02:00
|
|
|
|
|
|
|
this.dispatch({
|
|
|
|
type: 'SETTINGS_UPDATE_ONE',
|
|
|
|
key: key,
|
|
|
|
value: c.value,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scheduleSave();
|
2017-05-12 22:17:23 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 23:55:26 +02:00
|
|
|
this.cache_.push({
|
|
|
|
key: key,
|
|
|
|
value: this.formatValue(key, value),
|
|
|
|
});
|
|
|
|
|
2017-07-26 19:49:01 +02:00
|
|
|
this.dispatch({
|
|
|
|
type: 'SETTINGS_UPDATE_ONE',
|
|
|
|
key: key,
|
|
|
|
value: this.formatValue(key, value),
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scheduleSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
static valueToString(key, value) {
|
|
|
|
const md = this.settingMetadata(key);
|
|
|
|
value = this.formatValue(key, value);
|
|
|
|
if (md.type == Setting.TYPE_INT) return value.toFixed(0);
|
|
|
|
if (md.type == Setting.TYPE_BOOL) return value ? '1' : '0';
|
|
|
|
return value;
|
2017-05-12 22:17:23 +02:00
|
|
|
}
|
|
|
|
|
2017-07-25 23:55:26 +02:00
|
|
|
static formatValue(key, value) {
|
|
|
|
const md = this.settingMetadata(key);
|
|
|
|
if (md.type == Setting.TYPE_INT) return Math.floor(Number(value));
|
2017-07-26 19:49:01 +02:00
|
|
|
if (md.type == Setting.TYPE_BOOL) {
|
2017-07-26 23:07:27 +02:00
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = value.toLowerCase();
|
|
|
|
if (value === 'true') return true;
|
|
|
|
if (value === 'false') return false;
|
|
|
|
value = Number(value);
|
|
|
|
}
|
2017-07-26 19:49:01 +02:00
|
|
|
return !!value;
|
|
|
|
}
|
2017-07-25 20:57:06 +02:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
static value(key) {
|
2017-07-07 00:15:31 +02:00
|
|
|
if (key in this.constants_) {
|
|
|
|
let output = this.constants_[key];
|
|
|
|
if (output == 'SET_ME') throw new Error('Setting constant has not been set: ' + key);
|
|
|
|
return output;
|
|
|
|
}
|
2017-06-24 20:51:43 +02:00
|
|
|
|
2017-06-19 20:58:49 +02:00
|
|
|
if (!this.cache_) throw new Error('Settings have not been initialized!');
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
for (let i = 0; i < this.cache_.length; i++) {
|
|
|
|
if (this.cache_[i].key == key) {
|
2017-07-25 23:55:26 +02:00
|
|
|
return this.cache_[i].value;
|
2017-05-12 22:17:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 23:55:26 +02:00
|
|
|
const md = this.settingMetadata(key);
|
|
|
|
return md.value;
|
2017-05-12 22:17:23 +02:00
|
|
|
}
|
|
|
|
|
2017-07-24 20:58:11 +02:00
|
|
|
static isEnum(key) {
|
|
|
|
const md = this.settingMetadata(key);
|
2017-07-25 23:55:26 +02:00
|
|
|
return md.isEnum === true;
|
2017-07-24 20:58:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static enumOptionValues(key) {
|
|
|
|
const options = this.enumOptions(key);
|
|
|
|
let output = [];
|
|
|
|
for (let n in options) {
|
|
|
|
if (!options.hasOwnProperty(n)) continue;
|
|
|
|
output.push(n);
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enumOptionLabel(key, value) {
|
|
|
|
const options = this.enumOptions(key);
|
|
|
|
for (let n in options) {
|
|
|
|
if (n == value) return options[n];
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
static enumOptions(key) {
|
|
|
|
if (!this.metadata_[key]) throw new Error('Unknown key: ' + key);
|
|
|
|
if (!this.metadata_[key].options) throw new Error('No options for: ' + key);
|
|
|
|
return this.metadata_[key].options();
|
|
|
|
}
|
|
|
|
|
|
|
|
static enumOptionsDoc(key) {
|
|
|
|
const options = this.enumOptions(key);
|
|
|
|
let output = [];
|
|
|
|
for (let n in options) {
|
|
|
|
if (!options.hasOwnProperty(n)) continue;
|
2017-08-22 19:57:35 +02:00
|
|
|
output.push(_('%s: %s', n, options[n]));
|
2017-07-24 20:58:11 +02:00
|
|
|
}
|
|
|
|
return output.join(', ');
|
|
|
|
}
|
|
|
|
|
|
|
|
static isAllowedEnumOption(key, value) {
|
|
|
|
const options = this.enumOptions(key);
|
|
|
|
return !!options[value];
|
|
|
|
}
|
|
|
|
|
2017-05-16 23:46:21 +02:00
|
|
|
// Currently only supports objects with properties one level deep
|
|
|
|
static object(key) {
|
|
|
|
let output = {};
|
|
|
|
let keys = this.keys();
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
let k = keys[i].split('.');
|
|
|
|
if (k[0] == key) {
|
|
|
|
output[k[1]] = this.value(keys[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Currently only supports objects with properties one level deep
|
|
|
|
static setObject(key, object) {
|
|
|
|
for (let n in object) {
|
|
|
|
if (!object.hasOwnProperty(n)) continue;
|
|
|
|
this.setValue(key + '.' + n, object[n]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-19 21:12:09 +02:00
|
|
|
static saveAll() {
|
2017-07-26 19:49:01 +02:00
|
|
|
if (!this.saveTimeoutId_) return Promise.resolve();
|
2017-05-19 21:12:09 +02:00
|
|
|
|
2017-06-25 12:41:03 +02:00
|
|
|
this.logger().info('Saving settings...');
|
2017-07-26 19:49:01 +02:00
|
|
|
clearTimeout(this.saveTimeoutId_);
|
|
|
|
this.saveTimeoutId_ = null;
|
2017-05-19 21:12:09 +02:00
|
|
|
|
2017-06-11 23:11:14 +02:00
|
|
|
let queries = [];
|
|
|
|
queries.push('DELETE FROM settings');
|
|
|
|
for (let i = 0; i < this.cache_.length; i++) {
|
2017-07-26 19:49:01 +02:00
|
|
|
let s = Object.assign({}, this.cache_[i]);
|
|
|
|
s.value = this.valueToString(s.key, s.value);
|
|
|
|
queries.push(Database.insertQuery(this.tableName(), s));
|
2017-06-11 23:11:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return BaseModel.db().transactionExecBatch(queries).then(() => {
|
2017-06-25 12:41:03 +02:00
|
|
|
this.logger().info('Settings have been saved.');
|
2017-05-19 21:12:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-26 19:49:01 +02:00
|
|
|
static scheduleSave() {
|
|
|
|
if (this.saveTimeoutId_) clearTimeout(this.saveTimeoutId_);
|
2017-05-19 21:12:09 +02:00
|
|
|
|
2017-07-26 19:49:01 +02:00
|
|
|
this.saveTimeoutId_ = setTimeout(() => {
|
2017-05-19 21:12:09 +02:00
|
|
|
this.saveAll();
|
2017-05-12 22:17:23 +02:00
|
|
|
}, 500);
|
|
|
|
}
|
|
|
|
|
2017-07-26 19:49:01 +02:00
|
|
|
static cancelScheduleSave() {
|
|
|
|
if (this.saveTimeoutId_) clearTimeout(this.saveTimeoutId_);
|
|
|
|
this.saveTimeoutId_ = null;
|
2017-06-19 21:26:27 +02:00
|
|
|
}
|
|
|
|
|
2017-07-23 20:26:50 +02:00
|
|
|
static publicSettings(appType) {
|
|
|
|
if (!appType) throw new Error('appType is required');
|
|
|
|
|
|
|
|
let output = {};
|
2017-07-24 20:58:11 +02:00
|
|
|
for (let key in Setting.metadata_) {
|
|
|
|
if (!Setting.metadata_.hasOwnProperty(key)) continue;
|
|
|
|
let s = Object.assign({}, Setting.metadata_[key]);
|
2017-07-23 20:26:50 +02:00
|
|
|
if (!s.public) continue;
|
|
|
|
if (s.appTypes && s.appTypes.indexOf(appType) < 0) continue;
|
|
|
|
s.value = this.value(key);
|
|
|
|
output[key] = s;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
}
|
|
|
|
|
2017-07-24 20:58:11 +02:00
|
|
|
Setting.SYNC_TARGET_MEMORY = 1;
|
|
|
|
Setting.SYNC_TARGET_FILESYSTEM = 2;
|
|
|
|
Setting.SYNC_TARGET_ONEDRIVE = 3;
|
|
|
|
|
2017-07-25 23:55:26 +02:00
|
|
|
Setting.TYPE_INT = 1;
|
|
|
|
Setting.TYPE_STRING = 2;
|
|
|
|
Setting.TYPE_BOOL = 3;
|
|
|
|
|
2017-07-31 22:51:24 +02:00
|
|
|
Setting.THEME_LIGHT = 1;
|
|
|
|
Setting.THEME_DARK = 2;
|
|
|
|
|
2017-07-24 20:58:11 +02:00
|
|
|
Setting.metadata_ = {
|
2017-07-25 23:55:26 +02:00
|
|
|
'activeFolderId': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'firstStart': { value: true, type: Setting.TYPE_BOOL, public: false },
|
|
|
|
'sync.2.path': { value: '', type: Setting.TYPE_STRING, public: true, appTypes: ['cli'] },
|
|
|
|
'sync.3.auth': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.target': { value: Setting.SYNC_TARGET_ONEDRIVE, type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), options: () => {
|
2017-07-24 20:58:11 +02:00
|
|
|
let output = {};
|
|
|
|
output[Setting.SYNC_TARGET_MEMORY] = 'Memory';
|
|
|
|
output[Setting.SYNC_TARGET_FILESYSTEM] = _('File system');
|
|
|
|
output[Setting.SYNC_TARGET_ONEDRIVE] = _('OneDrive');
|
|
|
|
return output;
|
|
|
|
}},
|
2017-08-19 22:56:28 +02:00
|
|
|
'sync.1.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.2.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.3.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.4.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.5.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
|
|
|
'sync.6.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
2017-07-25 23:55:26 +02:00
|
|
|
'editor': { value: '', type: Setting.TYPE_STRING, public: true, appTypes: ['cli'] },
|
|
|
|
'locale': { value: defaultLocale(), type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Language'), options: () => {
|
2017-07-24 23:29:40 +02:00
|
|
|
return supportedLocalesToLanguages();
|
|
|
|
}},
|
2017-08-20 22:11:32 +02:00
|
|
|
// 'logLevel': { value: Logger.LEVEL_INFO, type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Log level'), options: () => {
|
|
|
|
// return Logger.levelEnum();
|
|
|
|
// }},
|
2017-07-28 19:57:01 +02:00
|
|
|
// Not used for now:
|
|
|
|
'todoFilter': { value: 'all', type: Setting.TYPE_STRING, isEnum: true, public: false, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({
|
2017-07-23 20:26:50 +02:00
|
|
|
all: _('Show all'),
|
|
|
|
recent: _('Non-completed and recently completed ones'),
|
|
|
|
nonCompleted: _('Non-completed ones only'),
|
|
|
|
})},
|
2017-07-26 20:36:16 +02:00
|
|
|
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted todos on top of the lists') },
|
2017-09-24 16:48:23 +02:00
|
|
|
'showAdvancedOptions': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['mobile'], label: () => _('Show advanced options') },
|
2017-07-25 23:55:26 +02:00
|
|
|
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save location with notes') },
|
2017-08-22 19:57:35 +02:00
|
|
|
'sync.interval': { value: 300, type: Setting.TYPE_INT, isEnum: true, public: true, appTypes: ['mobile'], label: () => _('Synchronisation interval'), options: () => {
|
2017-07-26 19:49:01 +02:00
|
|
|
return {
|
2017-08-20 16:29:18 +02:00
|
|
|
0: _('Disabled'),
|
2017-07-26 19:49:01 +02:00
|
|
|
300: _('%d minutes', 5),
|
|
|
|
600: _('%d minutes', 10),
|
|
|
|
1800: _('%d minutes', 30),
|
|
|
|
3600: _('%d hour', 1),
|
2017-07-31 21:02:21 +02:00
|
|
|
43200: _('%d hours', 12),
|
2017-07-26 19:49:01 +02:00
|
|
|
86400: _('%d hours', 24),
|
|
|
|
};
|
|
|
|
}},
|
2017-07-31 22:51:24 +02:00
|
|
|
'theme': { value: Setting.THEME_LIGHT, type: Setting.TYPE_INT, public: true, appTypes: ['mobile'], isEnum: true, label: () => _('Theme'), options: () => {
|
|
|
|
let output = {};
|
|
|
|
output[Setting.THEME_LIGHT] = _('Light');
|
|
|
|
output[Setting.THEME_DARK] = _('Dark');
|
|
|
|
return output;
|
|
|
|
}},
|
2017-06-06 22:01:43 +02:00
|
|
|
};
|
|
|
|
|
2017-06-24 20:51:43 +02:00
|
|
|
// Contains constants that are set by the application and
|
|
|
|
// cannot be modified by the user:
|
|
|
|
Setting.constants_ = {
|
2017-07-08 00:25:03 +02:00
|
|
|
'env': 'SET_ME',
|
2017-06-24 20:51:43 +02:00
|
|
|
'appName': 'joplin',
|
2017-06-29 22:52:52 +02:00
|
|
|
'appId': 'SET_ME', // Each app should set this identifier
|
2017-07-07 00:15:31 +02:00
|
|
|
'appType': 'SET_ME', // 'cli' or 'mobile'
|
2017-07-06 23:30:45 +02:00
|
|
|
'resourceDir': '',
|
|
|
|
'profileDir': '',
|
2017-07-10 22:03:46 +02:00
|
|
|
'tempDir': '',
|
2017-06-24 20:51:43 +02:00
|
|
|
}
|
|
|
|
|
2017-05-12 22:17:23 +02:00
|
|
|
export { Setting };
|