mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Tidy settings and sync creation
This commit is contained in:
parent
9b8376f152
commit
a983a9f108
@ -10,20 +10,30 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.');
|
return _("Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
|
|
||||||
|
const renderKeyValue = (name) => {
|
||||||
|
const value = Setting.value(name);
|
||||||
|
if (Setting.isEnum(name)) {
|
||||||
|
return _('%s = %s (%s)', name, value, Setting.enumOptionLabel(name, value));
|
||||||
|
} else {
|
||||||
|
return _('%s = %s', name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!args.name && !args.value) {
|
if (!args.name && !args.value) {
|
||||||
let keys = Setting.publicKeys();
|
let keys = Setting.publicKeys();
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
this.log(keys[i] + ' = ' + Setting.value(keys[i]));
|
this.log(renderKeyValue(keys[i]));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.name && !args.value) {
|
if (args.name && !args.value) {
|
||||||
this.log(args.name + ' = ' + Setting.value(args.name));
|
this.log(renderKeyValue(args.name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let service = new ReportService();
|
let service = new ReportService();
|
||||||
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
|
let report = await service.status(Setting.value('sync.target'));
|
||||||
|
|
||||||
for (let i = 0; i < report.length; i++) {
|
for (let i = 0; i < report.length; i++) {
|
||||||
let section = report[i];
|
let section = report[i];
|
||||||
|
@ -42,7 +42,7 @@ async function createClients() {
|
|||||||
for (let clientId = 0; clientId < 2; clientId++) {
|
for (let clientId = 0; clientId < 2; clientId++) {
|
||||||
let client = createClient(clientId);
|
let client = createClient(clientId);
|
||||||
promises.push(fs.remove(client.profileDir));
|
promises.push(fs.remove(client.profileDir));
|
||||||
promises.push(execCommand(client, 'config sync.target filesystem').then(() => { return execCommand(client, 'config sync.filesystem.path ' + syncDir); }));
|
promises.push(execCommand(client, 'config sync.target 2').then(() => { return execCommand(client, 'config sync.2.path ' + syncDir); }));
|
||||||
output.push(client);
|
output.push(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,14 @@ msgid ""
|
|||||||
"current configuration."
|
"current configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s = %s (%s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s = %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
||||||
"specified the note is duplicated in the current notebook."
|
"specified the note is duplicated in the current notebook."
|
||||||
@ -276,8 +284,8 @@ msgid "Please open this URL in your browser to authenticate the application:"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please set the \"sync.filesystem.path\" config value to the desired "
|
"Please set the \"sync.2.path\" config value to the desired synchronisation "
|
||||||
"synchronisation destination."
|
"destination."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
@ -338,6 +346,14 @@ msgstr ""
|
|||||||
msgid "Cannot move note to \"%s\" notebook"
|
msgid "Cannot move note to \"%s\" notebook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s (%s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Synchronisation target"
|
msgid "Synchronisation target"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ msgstr "Affiche tous les détails de la note."
|
|||||||
msgid "Cannot find \"%s\"."
|
msgid "Cannot find \"%s\"."
|
||||||
msgstr "Impossible de trouver \"%s\"."
|
msgstr "Impossible de trouver \"%s\"."
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Gets or sets a config value. If [value] is not provided, it will show the "
|
"Gets or sets a config value. If [value] is not provided, it will show the "
|
||||||
"value of [name]. If neither [name] nor [value] is provided, it will list the "
|
"value of [name]. If neither [name] nor [value] is provided, it will list the "
|
||||||
@ -75,6 +76,14 @@ msgstr ""
|
|||||||
"fournie, la valeur de [nom] est affichée. Si ni le [nom] ni la [valeur] ne "
|
"fournie, la valeur de [nom] est affichée. Si ni le [nom] ni la [valeur] ne "
|
||||||
"sont fournies, la configuration complète est affichée."
|
"sont fournies, la configuration complète est affichée."
|
||||||
|
|
||||||
|
#, fuzzy, javascript-format
|
||||||
|
msgid "%s = %s (%s)"
|
||||||
|
msgstr "%s %s (%s)"
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s = %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
||||||
"specified the note is duplicated in the current notebook."
|
"specified the note is duplicated in the current notebook."
|
||||||
@ -311,8 +320,8 @@ msgstr ""
|
|||||||
"logiciel :"
|
"logiciel :"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please set the \"sync.filesystem.path\" config value to the desired "
|
"Please set the \"sync.2.path\" config value to the desired synchronisation "
|
||||||
"synchronisation destination."
|
"destination."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
@ -373,6 +382,14 @@ msgstr "Impossible de copier la note dans le carnet \"%s\""
|
|||||||
msgid "Cannot move note to \"%s\" notebook"
|
msgid "Cannot move note to \"%s\" notebook"
|
||||||
msgstr "Impossible de déplacer la note vers le carnet \"%s\""
|
msgstr "Impossible de déplacer la note vers le carnet \"%s\""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, fuzzy, javascript-format
|
||||||
|
msgid "%s (%s)"
|
||||||
|
msgstr "%s %s (%s)"
|
||||||
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Synchronisation target"
|
msgid "Synchronisation target"
|
||||||
msgstr "Cible de la synchronisation : %s"
|
msgstr "Cible de la synchronisation : %s"
|
||||||
|
@ -70,6 +70,14 @@ msgid ""
|
|||||||
"current configuration."
|
"current configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s = %s (%s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s = %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
"Duplicates the notes matching <pattern> to [notebook]. If no notebook is "
|
||||||
"specified the note is duplicated in the current notebook."
|
"specified the note is duplicated in the current notebook."
|
||||||
@ -276,8 +284,8 @@ msgid "Please open this URL in your browser to authenticate the application:"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please set the \"sync.filesystem.path\" config value to the desired "
|
"Please set the \"sync.2.path\" config value to the desired synchronisation "
|
||||||
"synchronisation destination."
|
"destination."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
@ -338,6 +346,14 @@ msgstr ""
|
|||||||
msgid "Cannot move note to \"%s\" notebook"
|
msgid "Cannot move note to \"%s\" notebook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
|
msgid "%s (%s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Synchronisation target"
|
msgid "Synchronisation target"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ async function localItemsSameAsRemote(locals, expect) {
|
|||||||
expect(!!remote).toBe(true);
|
expect(!!remote).toBe(true);
|
||||||
if (!remote) continue;
|
if (!remote) continue;
|
||||||
|
|
||||||
if (syncTargetId() == Database.enumId('syncTarget', 'filesystem')) {
|
if (syncTargetId() == Setting.SYNC_TARGET_FILESYSTEM) {
|
||||||
expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
|
expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
|
||||||
} else {
|
} else {
|
||||||
expect(remote.updated_time).toBe(dbItem.updated_time);
|
expect(remote.updated_time).toBe(dbItem.updated_time);
|
||||||
@ -142,29 +142,20 @@ describe('Synchronizer', function() {
|
|||||||
await switchClient(2);
|
await switchClient(2);
|
||||||
|
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
|
|
||||||
await sleep(0.1);
|
|
||||||
|
|
||||||
let note2 = await Note.load(note1.id);
|
let note2 = await Note.load(note1.id);
|
||||||
note2.title = "Updated on client 2";
|
note2.title = "Updated on client 2";
|
||||||
await Note.save(note2);
|
await Note.save(note2);
|
||||||
note2 = await Note.load(note2.id);
|
note2 = await Note.load(note2.id);
|
||||||
|
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
|
|
||||||
await switchClient(1);
|
await switchClient(1);
|
||||||
|
|
||||||
await sleep(0.1);
|
|
||||||
|
|
||||||
let note2conf = await Note.load(note1.id);
|
let note2conf = await Note.load(note1.id);
|
||||||
note2conf.title = "Updated on client 1";
|
note2conf.title = "Updated on client 1";
|
||||||
await Note.save(note2conf);
|
await Note.save(note2conf);
|
||||||
note2conf = await Note.load(note1.id);
|
note2conf = await Note.load(note1.id);
|
||||||
|
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
|
|
||||||
let conflictedNotes = await Note.conflictedNotes();
|
let conflictedNotes = await Note.conflictedNotes();
|
||||||
|
|
||||||
expect(conflictedNotes.length).toBe(1);
|
expect(conflictedNotes.length).toBe(1);
|
||||||
|
|
||||||
// Other than the id (since the conflicted note is a duplicate), and the is_conflict property
|
// Other than the id (since the conflicted note is a duplicate), and the is_conflict property
|
||||||
|
@ -29,10 +29,12 @@ Resource.fsDriver_ = fsDriver;
|
|||||||
const logDir = __dirname + '/../tests/logs';
|
const logDir = __dirname + '/../tests/logs';
|
||||||
fs.mkdirpSync(logDir, 0o755);
|
fs.mkdirpSync(logDir, 0o755);
|
||||||
|
|
||||||
//const syncTarget = 'filesystem';
|
//const syncTargetId_ = Setting.SYNC_TARGET_MEMORY;
|
||||||
const syncTarget = 'memory';
|
const syncTargetId_ = Setting.SYNC_TARGET_FILESYSTEM;
|
||||||
const syncDir = __dirname + '/../tests/sync';
|
const syncDir = __dirname + '/../tests/sync';
|
||||||
|
|
||||||
|
const sleepTime = syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM ? 1001 : 200;
|
||||||
|
|
||||||
const logger = new Logger();
|
const logger = new Logger();
|
||||||
logger.addTarget('file', { path: logDir + '/log.txt' });
|
logger.addTarget('file', { path: logDir + '/log.txt' });
|
||||||
logger.setLevel(Logger.LEVEL_DEBUG);
|
logger.setLevel(Logger.LEVEL_DEBUG);
|
||||||
@ -47,7 +49,7 @@ Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
|||||||
Setting.setConstant('appType', 'cli');
|
Setting.setConstant('appType', 'cli');
|
||||||
|
|
||||||
function syncTargetId() {
|
function syncTargetId() {
|
||||||
return JoplinDatabase.enumId('syncTarget', syncTarget);
|
return syncTargetId_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(n) {
|
function sleep(n) {
|
||||||
@ -59,7 +61,7 @@ function sleep(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function switchClient(id) {
|
async function switchClient(id) {
|
||||||
await time.msleep(200); // Always leave a little time so that updated_time properties don't overlap
|
await time.msleep(sleepTime); // Always leave a little time so that updated_time properties don't overlap
|
||||||
await Setting.saveAll();
|
await Setting.saveAll();
|
||||||
|
|
||||||
currentClient_ = id;
|
currentClient_ = id;
|
||||||
@ -120,7 +122,7 @@ async function setupDatabaseAndSynchronizer(id = null) {
|
|||||||
synchronizers_[id].setLogger(logger);
|
synchronizers_[id].setLogger(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncTarget == 'filesystem') {
|
if (syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM) {
|
||||||
fs.removeSync(syncDir)
|
fs.removeSync(syncDir)
|
||||||
fs.mkdirpSync(syncDir, 0o755);
|
fs.mkdirpSync(syncDir, 0o755);
|
||||||
} else {
|
} else {
|
||||||
@ -141,17 +143,19 @@ function synchronizer(id = null) {
|
|||||||
function fileApi() {
|
function fileApi() {
|
||||||
if (fileApi_) return fileApi_;
|
if (fileApi_) return fileApi_;
|
||||||
|
|
||||||
if (syncTarget == 'filesystem') {
|
if (syncTargetId_ == Setting.SYNC_TARGET_FILESYSTEM) {
|
||||||
fs.removeSync(syncDir)
|
fs.removeSync(syncDir)
|
||||||
fs.mkdirpSync(syncDir, 0o755);
|
fs.mkdirpSync(syncDir, 0o755);
|
||||||
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
|
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
|
||||||
fileApi_.setLogger(logger);
|
|
||||||
return fileApi_;
|
|
||||||
} else {
|
} else {
|
||||||
fileApi_ = new FileApi('/root', new FileApiDriverMemory());
|
fileApi_ = new FileApi('/root', new FileApiDriverMemory());
|
||||||
fileApi_.setLogger(logger);
|
fileApi_.setLogger(logger);
|
||||||
return fileApi_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileApi_.setLogger(logger);
|
||||||
|
fileApi_.setSyncTargetId(syncTargetId_);
|
||||||
|
return fileApi_;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId };
|
export { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId };
|
@ -41,7 +41,7 @@ class StatusScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
async resfreshScreen() {
|
async resfreshScreen() {
|
||||||
let service = new ReportService();
|
let service = new ReportService();
|
||||||
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
|
let report = await service.status(Setting.value('sync.target'));
|
||||||
this.setState({ report: report });
|
this.setState({ report: report });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,20 +157,6 @@ class Database {
|
|||||||
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
|
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static enumName(type, id) {
|
|
||||||
if (type == 'syncTarget') {
|
|
||||||
if (id === 1) return 'memory';
|
|
||||||
if (id === 2) return 'filesystem';
|
|
||||||
if (id === 3) return 'onedrive';
|
|
||||||
}
|
|
||||||
throw new Error('Unknown enum type or id: ' + type + ', ' + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static enumIds(type) {
|
|
||||||
if (type == 'syncTarget') return [1,2,3];
|
|
||||||
throw new Error('Unknown enum type: ' + type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static formatValue(type, value) {
|
static formatValue(type, value) {
|
||||||
if (value === null || value === undefined) return null;
|
if (value === null || value === undefined) return null;
|
||||||
if (type == this.TYPE_INT) return Number(value);
|
if (type == this.TYPE_INT) return Number(value);
|
||||||
|
@ -4,16 +4,21 @@ import moment from 'moment';
|
|||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
|
|
||||||
|
// NOTE: when synchronising with the file system the time resolution is the second (unlike milliseconds for OneDrive for instance).
|
||||||
|
// What it means is that if, for example, client 1 changes a note at time t, and client 2 changes the same note within the same second,
|
||||||
|
// both clients will not know about each others updates during the next sync. They will simply both sync their note and whoever
|
||||||
|
// comes last will overwrite (on the remote storage) the note of the other client. Both client will then have a different note at
|
||||||
|
// that point and that will only be resolved if one of them changes the note and sync (if they don't change it, it will never get resolved).
|
||||||
|
//
|
||||||
|
// This is compound with the fact that we can't have a reliable delta API on the file system so we need to check all the timestamps
|
||||||
|
// every time and rely on this exclusively to know about changes.
|
||||||
|
//
|
||||||
|
// This explains occasional failures of the fuzzing program (it finds that the clients end up with two different notes after sync). To
|
||||||
|
// check that it is indeed the problem, check log-database.txt of both clients, search for the note ID, and most likely both notes
|
||||||
|
// will have been modified at the same exact second at some point. If not, it's another bug that needs to be investigated.
|
||||||
|
|
||||||
class FileApiDriverLocal {
|
class FileApiDriverLocal {
|
||||||
|
|
||||||
syncTargetId() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncTargetName() {
|
|
||||||
return 'filesystem';
|
|
||||||
}
|
|
||||||
|
|
||||||
fsErrorToJsError_(error) {
|
fsErrorToJsError_(error) {
|
||||||
let msg = error.toString();
|
let msg = error.toString();
|
||||||
let output = new Error(msg);
|
let output = new Error(msg);
|
||||||
|
@ -2,14 +2,6 @@ import { time } from 'lib/time-utils.js';
|
|||||||
|
|
||||||
class FileApiDriverMemory {
|
class FileApiDriverMemory {
|
||||||
|
|
||||||
syncTargetId() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncTargetName() {
|
|
||||||
return 'memory';
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.items_ = [];
|
this.items_ = [];
|
||||||
this.deletedItems_ = [];
|
this.deletedItems_ = [];
|
||||||
|
@ -9,14 +9,6 @@ class FileApiDriverOneDrive {
|
|||||||
this.api_ = api;
|
this.api_ = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
syncTargetId() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncTargetName() {
|
|
||||||
return 'onedrive';
|
|
||||||
}
|
|
||||||
|
|
||||||
api() {
|
api() {
|
||||||
return this.api_;
|
return this.api_;
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,22 @@ class FileApi {
|
|||||||
this.baseDir_ = baseDir;
|
this.baseDir_ = baseDir;
|
||||||
this.driver_ = driver;
|
this.driver_ = driver;
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
|
this.syncTargetId_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
driver() {
|
driver() {
|
||||||
return this.driver_;
|
return this.driver_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSyncTargetId(v) {
|
||||||
|
this.syncTargetId_ = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTargetId() {
|
||||||
|
if (this.syncTargetId_ === null) throw new Error('syncTargetId has not been set!!');
|
||||||
|
return this.syncTargetId_;
|
||||||
|
}
|
||||||
|
|
||||||
supportsDelta() {
|
supportsDelta() {
|
||||||
return this.driver_.supportsDelta();
|
return this.driver_.supportsDelta();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Database } from 'lib/database.js';
|
import { Database } from 'lib/database.js';
|
||||||
|
import { Setting } from 'lib/models/setting.js';
|
||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
import { sprintf } from 'sprintf-js';
|
import { sprintf } from 'sprintf-js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -136,7 +137,7 @@ class BaseItem extends BaseModel {
|
|||||||
await super.batchDelete(ids, options);
|
await super.batchDelete(ids, options);
|
||||||
|
|
||||||
if (trackDeleted) {
|
if (trackDeleted) {
|
||||||
const syncTargetIds = Database.enumIds('syncTarget');
|
const syncTargetIds = Setting.enumOptionValues('sync.target');
|
||||||
let queries = [];
|
let queries = [];
|
||||||
let now = time.unixMs();
|
let now = time.unixMs();
|
||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
@ -313,10 +314,6 @@ class BaseItem extends BaseModel {
|
|||||||
limit);
|
limit);
|
||||||
|
|
||||||
let neverSyncedItem = await ItemClass.modelSelectAll(sql);
|
let neverSyncedItem = await ItemClass.modelSelectAll(sql);
|
||||||
//for (let i = 0; i < neverSyncedItem.length; i++) neverSyncedItem[i].sync_time = 0;
|
|
||||||
|
|
||||||
// console.info(sql);
|
|
||||||
// console.info('NEVER', neverSyncedItem);
|
|
||||||
|
|
||||||
// Secondly get the items that have been synced under this sync target but that have been changed since then
|
// Secondly get the items that have been synced under this sync target but that have been changed since then
|
||||||
|
|
||||||
@ -344,8 +341,6 @@ class BaseItem extends BaseModel {
|
|||||||
changedItems = await ItemClass.modelSelectAll(sql);
|
changedItems = await ItemClass.modelSelectAll(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.info('CHANGED', changedItems);
|
|
||||||
|
|
||||||
const items = neverSyncedItem.concat(changedItems);
|
const items = neverSyncedItem.concat(changedItems);
|
||||||
|
|
||||||
if (i >= classNames.length - 1) {
|
if (i >= classNames.length - 1) {
|
||||||
@ -353,63 +348,6 @@ class BaseItem extends BaseModel {
|
|||||||
} else {
|
} else {
|
||||||
if (items.length) return { hasMore: true, items: items };
|
if (items.length) return { hasMore: true, items: items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//let extraWhere = className == 'Note' ? 'AND is_conflict = 0' : '';
|
|
||||||
|
|
||||||
// First get all the items that have never been synced under this sync target
|
|
||||||
|
|
||||||
// let sql = sprintf(`
|
|
||||||
// SELECT %s FROM %s items
|
|
||||||
// LEFT JOIN sync_items t ON t.item_id = items.id
|
|
||||||
// WHERE (t.id IS NULL OR t.sync_target != %d) %s
|
|
||||||
// LIMIT %d
|
|
||||||
// `,
|
|
||||||
// this.db().escapeFields(fieldNames),
|
|
||||||
// this.db().escapeField(ItemClass.tableName()),
|
|
||||||
// Number(syncTarget),
|
|
||||||
// extraWhere,
|
|
||||||
// limit);
|
|
||||||
|
|
||||||
// let neverSyncedItem = await ItemClass.modelSelectAll(sql);
|
|
||||||
// for (let i = 0; i < neverSyncedItem.length; i++) neverSyncedItem[i].sync_time = 0;
|
|
||||||
|
|
||||||
// console.info(sql);
|
|
||||||
// console.info('NEVER', neverSyncedItem);
|
|
||||||
|
|
||||||
// // Secondly get the items that have been synced under this sync target but that have been changed since then
|
|
||||||
|
|
||||||
// const newLimit = limit - neverSyncedItem.length;
|
|
||||||
|
|
||||||
// let changedItems = [];
|
|
||||||
|
|
||||||
// if (newLimit > 0) {
|
|
||||||
// let sql = sprintf(`
|
|
||||||
// SELECT %s FROM %s items
|
|
||||||
// LEFT JOIN sync_items t ON t.item_id = items.id
|
|
||||||
// WHERE (t.sync_time < items.updated_time AND t.sync_target = %d) %s
|
|
||||||
// LIMIT %d
|
|
||||||
// `,
|
|
||||||
// this.db().escapeFields(fieldNames),
|
|
||||||
// this.db().escapeField(ItemClass.tableName()),
|
|
||||||
// Number(syncTarget),
|
|
||||||
// extraWhere,
|
|
||||||
// newLimit);
|
|
||||||
|
|
||||||
// changedItems = await ItemClass.modelSelectAll(sql);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.info('CHANGED', changedItems);
|
|
||||||
|
|
||||||
// const items = neverSyncedItem.concat(changedItems);
|
|
||||||
|
|
||||||
// if (i >= classNames.length - 1) {
|
|
||||||
// return { hasMore: items.length >= limit, items: items };
|
|
||||||
// } else {
|
|
||||||
// if (items.length) return { hasMore: true, items: items };
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Unreachable');
|
throw new Error('Unreachable');
|
||||||
|
@ -12,9 +12,9 @@ class Setting extends BaseModel {
|
|||||||
return BaseModel.TYPE_SETTING;
|
return BaseModel.TYPE_SETTING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultSetting(key) {
|
static settingMetadata(key) {
|
||||||
if (!(key in this.defaults_)) throw new Error('Unknown key: ' + key);
|
if (!(key in this.metadata_)) throw new Error('Unknown key: ' + key);
|
||||||
let output = Object.assign({}, this.defaults_[key]);
|
let output = Object.assign({}, this.metadata_[key]);
|
||||||
output.key = key;
|
output.key = key;
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@ -22,8 +22,8 @@ class Setting extends BaseModel {
|
|||||||
static keys() {
|
static keys() {
|
||||||
if (this.keys_) return this.keys_;
|
if (this.keys_) return this.keys_;
|
||||||
this.keys_ = [];
|
this.keys_ = [];
|
||||||
for (let n in this.defaults_) {
|
for (let n in this.metadata_) {
|
||||||
if (!this.defaults_.hasOwnProperty(n)) continue;
|
if (!this.metadata_.hasOwnProperty(n)) continue;
|
||||||
this.keys_.push(n);
|
this.keys_.push(n);
|
||||||
}
|
}
|
||||||
return this.keys_;
|
return this.keys_;
|
||||||
@ -31,9 +31,9 @@ class Setting extends BaseModel {
|
|||||||
|
|
||||||
static publicKeys() {
|
static publicKeys() {
|
||||||
let output = [];
|
let output = [];
|
||||||
for (let n in this.defaults_) {
|
for (let n in this.metadata_) {
|
||||||
if (!this.defaults_.hasOwnProperty(n)) continue;
|
if (!this.metadata_.hasOwnProperty(n)) continue;
|
||||||
if (this.defaults_[n].public) output.push(n);
|
if (this.metadata_[n].public) output.push(n);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@ -55,15 +55,24 @@ class Setting extends BaseModel {
|
|||||||
if (!this.cache_) throw new Error('Settings have not been initialized!');
|
if (!this.cache_) throw new Error('Settings have not been initialized!');
|
||||||
|
|
||||||
for (let i = 0; i < this.cache_.length; i++) {
|
for (let i = 0; i < this.cache_.length; i++) {
|
||||||
if (this.cache_[i].key == key) {
|
let c = this.cache_[i];
|
||||||
if (this.cache_[i].value === value) return;
|
if (c.key == key) {
|
||||||
this.cache_[i].value = value;
|
const md = this.settingMetadata(key);
|
||||||
|
|
||||||
|
if (md.type == 'enum') {
|
||||||
|
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;
|
||||||
|
c.value = value;
|
||||||
this.scheduleUpdate();
|
this.scheduleUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = this.defaultSetting(key);
|
let s = this.settingMetadata(key);
|
||||||
s.value = value;
|
s.value = value;
|
||||||
this.cache_.push(s);
|
this.cache_.push(s);
|
||||||
this.scheduleUpdate();
|
this.scheduleUpdate();
|
||||||
@ -84,10 +93,54 @@ class Setting extends BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = this.defaultSetting(key);
|
let s = this.settingMetadata(key);
|
||||||
return s.value;
|
return s.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isEnum(key) {
|
||||||
|
const md = this.settingMetadata(key);
|
||||||
|
return md.type == 'enum';
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
output.push(_('%s (%s)', n, options[n]));
|
||||||
|
}
|
||||||
|
return output.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static isAllowedEnumOption(key, value) {
|
||||||
|
const options = this.enumOptions(key);
|
||||||
|
return !!options[value];
|
||||||
|
}
|
||||||
|
|
||||||
// Currently only supports objects with properties one level deep
|
// Currently only supports objects with properties one level deep
|
||||||
static object(key) {
|
static object(key) {
|
||||||
let output = {};
|
let output = {};
|
||||||
@ -149,9 +202,9 @@ class Setting extends BaseModel {
|
|||||||
if (!appType) throw new Error('appType is required');
|
if (!appType) throw new Error('appType is required');
|
||||||
|
|
||||||
let output = {};
|
let output = {};
|
||||||
for (let key in Setting.defaults_) {
|
for (let key in Setting.metadata_) {
|
||||||
if (!Setting.defaults_.hasOwnProperty(key)) continue;
|
if (!Setting.metadata_.hasOwnProperty(key)) continue;
|
||||||
let s = Object.assign({}, Setting.defaults_[key]);
|
let s = Object.assign({}, Setting.metadata_[key]);
|
||||||
if (!s.public) continue;
|
if (!s.public) continue;
|
||||||
if (s.appTypes && s.appTypes.indexOf(appType) < 0) continue;
|
if (s.appTypes && s.appTypes.indexOf(appType) < 0) continue;
|
||||||
s.value = this.value(key);
|
s.value = this.value(key);
|
||||||
@ -162,19 +215,24 @@ class Setting extends BaseModel {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Setting.defaults_ = {
|
Setting.SYNC_TARGET_MEMORY = 1;
|
||||||
|
Setting.SYNC_TARGET_FILESYSTEM = 2;
|
||||||
|
Setting.SYNC_TARGET_ONEDRIVE = 3;
|
||||||
|
|
||||||
|
Setting.metadata_ = {
|
||||||
'activeFolderId': { value: '', type: 'string', public: false },
|
'activeFolderId': { value: '', type: 'string', public: false },
|
||||||
'sync.onedrive.auth': { value: '', type: 'string', public: false },
|
'sync.2.path': { value: '', type: 'string', public: true, appTypes: ['cli'] },
|
||||||
'sync.filesystem.path': { value: '', type: 'string', public: true, appTypes: ['cli'] },
|
'sync.3.auth': { value: '', type: 'string', public: false },
|
||||||
'sync.target': { value: 'onedrive', type: 'enum', public: true, label: () => _('Synchronisation target'), options: () => ({
|
'sync.target': { value: 'onedrive', type: 'enum', public: true, label: () => _('Synchronisation target'), options: () => {
|
||||||
1: 'Memory',
|
let output = {};
|
||||||
2: _('File system'),
|
output[Setting.SYNC_TARGET_MEMORY] = 'Memory';
|
||||||
3: _('OneDrive'),
|
output[Setting.SYNC_TARGET_FILESYSTEM] = _('File system');
|
||||||
})},
|
output[Setting.SYNC_TARGET_ONEDRIVE] = _('OneDrive');
|
||||||
|
return output;
|
||||||
|
}},
|
||||||
'sync.context': { value: '', type: 'string', public: false },
|
'sync.context': { value: '', type: 'string', public: false },
|
||||||
'editor': { value: '', type: 'string', public: true, appTypes: ['cli'] },
|
'editor': { value: '', type: 'string', public: true, appTypes: ['cli'] },
|
||||||
'locale': { value: 'en_GB', type: 'string', public: true },
|
'locale': { value: 'en_GB', type: 'string', public: true },
|
||||||
//'aliases': { value: '', type: 'string', public: true },
|
|
||||||
'todoFilter': { value: 'all', type: 'enum', public: true, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({
|
'todoFilter': { value: 'all', type: 'enum', public: true, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({
|
||||||
all: _('Show all'),
|
all: _('Show all'),
|
||||||
recent: _('Non-completed and recently completed ones'),
|
recent: _('Non-completed and recently completed ones'),
|
||||||
|
@ -34,10 +34,10 @@ reg.oneDriveApi = () => {
|
|||||||
|
|
||||||
reg.oneDriveApi_.on('authRefreshed', (a) => {
|
reg.oneDriveApi_.on('authRefreshed', (a) => {
|
||||||
reg.logger().info('Saving updated OneDrive auth.');
|
reg.logger().info('Saving updated OneDrive auth.');
|
||||||
Setting.setValue('sync.onedrive.auth', a ? JSON.stringify(a) : null);
|
Setting.setValue('sync.3.auth', a ? JSON.stringify(a) : null);
|
||||||
});
|
});
|
||||||
|
|
||||||
let auth = Setting.value('sync.onedrive.auth');
|
let auth = Setting.value('sync.3.auth');
|
||||||
if (auth) {
|
if (auth) {
|
||||||
try {
|
try {
|
||||||
auth = JSON.parse(auth);
|
auth = JSON.parse(auth);
|
||||||
@ -61,20 +61,20 @@ reg.synchronizer = async (syncTargetId) => {
|
|||||||
|
|
||||||
let fileApi = null;
|
let fileApi = null;
|
||||||
|
|
||||||
if (syncTargetId == 'onedrive') {
|
if (syncTargetId == Setting.SYNC_TARGET_ONEDRIVE) {
|
||||||
|
|
||||||
if (!reg.oneDriveApi().auth()) throw new Error('User is not authentified');
|
if (!reg.oneDriveApi().auth()) throw new Error('User is not authentified');
|
||||||
let appDir = await reg.oneDriveApi().appDirectory();
|
let appDir = await reg.oneDriveApi().appDirectory();
|
||||||
fileApi = new FileApi(appDir, new FileApiDriverOneDrive(reg.oneDriveApi()));
|
fileApi = new FileApi(appDir, new FileApiDriverOneDrive(reg.oneDriveApi()));
|
||||||
|
|
||||||
} else if (syncTargetId == 'memory') {
|
} else if (syncTargetId == Setting.SYNC_TARGET_MEMORY) {
|
||||||
|
|
||||||
fileApi = new FileApi('joplin', new FileApiDriverMemory());
|
fileApi = new FileApi('joplin', new FileApiDriverMemory());
|
||||||
|
|
||||||
} else if (syncTargetId == 'filesystem') {
|
} else if (syncTargetId == Setting.SYNC_TARGET_FILESYSTEM) {
|
||||||
|
|
||||||
let syncDir = Setting.value('sync.filesystem.path');
|
let syncDir = Setting.value('sync.2.path');
|
||||||
if (!syncDir) throw new Error(_('Please set the "sync.filesystem.path" config value to the desired synchronisation destination.'));
|
if (!syncDir) throw new Error(_('Please set the "sync.2.path" config value to the desired synchronisation destination.'));
|
||||||
await shim.fs.mkdirp(syncDir, 0o755);
|
await shim.fs.mkdirp(syncDir, 0o755);
|
||||||
fileApi = new FileApi(syncDir, new shim.FileApiDriverLocal());
|
fileApi = new FileApi(syncDir, new shim.FileApiDriverLocal());
|
||||||
|
|
||||||
@ -84,6 +84,7 @@ reg.synchronizer = async (syncTargetId) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileApi.setSyncTargetId(syncTargetId);
|
||||||
fileApi.setLogger(reg.logger());
|
fileApi.setLogger(reg.logger());
|
||||||
|
|
||||||
let sync = new Synchronizer(reg.db(), fileApi, Setting.value('appType'));
|
let sync = new Synchronizer(reg.db(), fileApi, Setting.value('appType'));
|
||||||
|
@ -153,7 +153,7 @@ class Synchronizer {
|
|||||||
|
|
||||||
const lastContext = options.context ? options.context : {};
|
const lastContext = options.context ? options.context : {};
|
||||||
|
|
||||||
const syncTargetId = this.api().driver().syncTargetId();
|
const syncTargetId = this.api().syncTargetId();
|
||||||
|
|
||||||
if (this.state() != 'idle') {
|
if (this.state() != 'idle') {
|
||||||
this.logger().info('Synchronization is already in progress. State: ' + this.state());
|
this.logger().info('Synchronization is already in progress. State: ' + this.state());
|
||||||
@ -176,7 +176,7 @@ class Synchronizer {
|
|||||||
|
|
||||||
this.dispatch({ type: 'SYNC_STARTED' });
|
this.dispatch({ type: 'SYNC_STARTED' });
|
||||||
|
|
||||||
this.logSyncOperation('starting', null, null, 'Starting synchronization to ' + this.api().driver().syncTargetName() + ' (' + syncTargetId + ')... [' + synchronizationId + ']');
|
this.logSyncOperation('starting', null, null, 'Starting synchronization to target ' + syncTargetId + '... [' + synchronizationId + ']');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api().mkdir(this.syncDirName_);
|
await this.api().mkdir(this.syncDirName_);
|
||||||
|
Loading…
Reference in New Issue
Block a user