mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Improved logging handling
This commit is contained in:
parent
ec9dacc2c0
commit
45ac9daebe
@ -10,20 +10,34 @@ import { Folder } from 'src/models/folder.js';
|
|||||||
import { Note } from 'src/models/note.js';
|
import { Note } from 'src/models/note.js';
|
||||||
import { Setting } from 'src/models/setting.js';
|
import { Setting } from 'src/models/setting.js';
|
||||||
import { Synchronizer } from 'src/synchronizer.js';
|
import { Synchronizer } from 'src/synchronizer.js';
|
||||||
|
import { Logger } from 'src/logger.js';
|
||||||
import { uuid } from 'src/uuid.js';
|
import { uuid } from 'src/uuid.js';
|
||||||
import { sprintf } from 'sprintf-js';
|
import { sprintf } from 'sprintf-js';
|
||||||
import { _ } from 'src/locale.js';
|
import { _ } from 'src/locale.js';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
const APPNAME = 'joplin';
|
||||||
|
const dataDir = os.homedir() + '/.local/share/' + APPNAME;
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = new Logger();
|
||||||
|
logger.addTarget('file', { path: dataDir + '/log.txt' });
|
||||||
|
logger.setLevel(Logger.LEVEL_DEBUG);
|
||||||
|
|
||||||
|
const syncLogger = new Logger();
|
||||||
|
syncLogger.addTarget('file', { path: dataDir + '/log-sync.txt' });
|
||||||
|
syncLogger.setLevel(Logger.LEVEL_DEBUG);
|
||||||
|
|
||||||
let db = new Database(new DatabaseDriverNode());
|
let db = new Database(new DatabaseDriverNode());
|
||||||
|
db.setLogger(logger);
|
||||||
let synchronizer_ = null;
|
let synchronizer_ = null;
|
||||||
const vorpal = require('vorpal')();
|
const vorpal = require('vorpal')();
|
||||||
const APPNAME = 'joplin';
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let dataDir = os.homedir() + '/.local/share/' + APPNAME;
|
|
||||||
await fs.mkdirp(dataDir, 0o755);
|
await fs.mkdirp(dataDir, 0o755);
|
||||||
|
|
||||||
await db.open({ name: dataDir + '/database.sqlite' });
|
await db.open({ name: dataDir + '/database.sqlite' });
|
||||||
@ -58,17 +72,23 @@ async function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let appDir = await driver.api().appDirectory();
|
let appDir = await driver.api().appDirectory();
|
||||||
console.info('App dir: ' + appDir);
|
logger.info('App dir: ' + appDir);
|
||||||
fileApi = new FileApi(appDir, driver);
|
fileApi = new FileApi(appDir, driver);
|
||||||
|
fileApi.setLogger(logger);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown backend: ' + remoteBackend);
|
throw new Error('Unknown backend: ' + remoteBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronizer_ = new Synchronizer(db, fileApi);
|
synchronizer_ = new Synchronizer(db, fileApi);
|
||||||
|
synchronizer_.setLogger(syncLogger);
|
||||||
|
|
||||||
return synchronizer_;
|
return synchronizer_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let s = await synchronizer('onedrive');
|
||||||
|
// await synchronizer_.start();
|
||||||
|
// return;
|
||||||
|
|
||||||
function switchCurrentFolder(folder) {
|
function switchCurrentFolder(folder) {
|
||||||
currentFolder = folder;
|
currentFolder = folder;
|
||||||
updatePrompt();
|
updatePrompt();
|
||||||
@ -356,7 +376,7 @@ async function main() {
|
|||||||
synchronizer('onedrive').then((s) => {
|
synchronizer('onedrive').then((s) => {
|
||||||
return s.start();
|
return s.start();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
end();
|
end();
|
||||||
});
|
});
|
||||||
|
@ -101,22 +101,18 @@ describe('Synchronizer', function() {
|
|||||||
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();
|
||||||
|
|
||||||
let files = await fileApi().list();
|
|
||||||
|
|
||||||
await switchClient(1);
|
await switchClient(1);
|
||||||
|
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
|
|
||||||
note1 = await Note.load(note1.id);
|
let all = await Folder.all(true);
|
||||||
|
let files = await fileApi().list();
|
||||||
|
|
||||||
expect(!!note1).toBe(true);
|
await localItemsSameAsRemote(all, expect);
|
||||||
expect(note1.title).toBe(note2.title);
|
|
||||||
expect(note1.body).toBe(note2.body);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -90,7 +90,7 @@ function db(id = null) {
|
|||||||
|
|
||||||
function synchronizer(id = null) {
|
function synchronizer(id = null) {
|
||||||
if (id === null) id = currentClient_;
|
if (id === null) id = currentClient_;
|
||||||
console.info('SYNC', id);
|
//console.info('SYNC', id);
|
||||||
return synchronizers_[id];
|
return synchronizers_[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,6 @@ class BaseModel {
|
|||||||
|
|
||||||
static loadByField(fieldName, fieldValue) {
|
static loadByField(fieldName, fieldValue) {
|
||||||
return this.modelSelectOne('SELECT * FROM ' + this.tableName() + ' WHERE `' + fieldName + '` = ?', [fieldValue]);
|
return this.modelSelectOne('SELECT * FROM ' + this.tableName() + ' WHERE `' + fieldName + '` = ?', [fieldValue]);
|
||||||
//return this.db().selectOne('SELECT * FROM ' + this.tableName() + ' WHERE `' + fieldName + '` = ?', [fieldValue]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static applyPatch(model, patch) {
|
static applyPatch(model, patch) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Log } from 'src/log.js';
|
|
||||||
import { uuid } from 'src/uuid.js';
|
import { uuid } from 'src/uuid.js';
|
||||||
import { promiseChain } from 'src/promise-utils.js';
|
import { promiseChain } from 'src/promise-utils.js';
|
||||||
|
import { Logger } from 'src/logger.js'
|
||||||
import { _ } from 'src/locale.js'
|
import { _ } from 'src/locale.js'
|
||||||
|
|
||||||
const structureSql = `
|
const structureSql = `
|
||||||
@ -115,6 +115,30 @@ class Database {
|
|||||||
this.tableFields_ = null;
|
this.tableFields_ = null;
|
||||||
this.driver_ = driver;
|
this.driver_ = driver;
|
||||||
this.inTransaction_ = false;
|
this.inTransaction_ = false;
|
||||||
|
|
||||||
|
this.logger_ = new Logger();
|
||||||
|
this.logger_.addTarget('console');
|
||||||
|
this.logger_.setLevel(Logger.LEVEL_DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the SQLite error to a regular JS error
|
||||||
|
// so that it prints a stacktrace when passed to
|
||||||
|
// console.error()
|
||||||
|
sqliteErrorToJsError(error, sql, params = null) {
|
||||||
|
let msg = sql;
|
||||||
|
if (params) msg += ': ' + JSON.stringify(params);
|
||||||
|
msg += ': ' + error.toString();
|
||||||
|
let output = new Error(msg);
|
||||||
|
if (error.code) output.code = error.code;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLogger(l) {
|
||||||
|
this.logger_ = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger() {
|
||||||
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDebugEnabled(v) {
|
setDebugEnabled(v) {
|
||||||
@ -136,26 +160,32 @@ class Database {
|
|||||||
|
|
||||||
open(options) {
|
open(options) {
|
||||||
return this.driver().open(options).then((db) => {
|
return this.driver().open(options).then((db) => {
|
||||||
Log.info('Database was open successfully');
|
this.logger().info('Database was open successfully');
|
||||||
return this.initialize();
|
return this.initialize();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
Log.error('Cannot open database: ', error);
|
this.logger().error('Cannot open database: ', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOne(sql, params = null) {
|
selectOne(sql, params = null) {
|
||||||
this.logQuery(sql, params);
|
this.logQuery(sql, params);
|
||||||
return this.driver().selectOne(sql, params);
|
return this.driver().selectOne(sql, params).catch((error) => {
|
||||||
|
throw this.sqliteErrorToJsError(error, sql, params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll(sql, params = null) {
|
selectAll(sql, params = null) {
|
||||||
this.logQuery(sql, params);
|
this.logQuery(sql, params);
|
||||||
return this.driver().selectAll(sql, params);
|
return this.driver().selectAll(sql, params).catch((error) => {
|
||||||
|
throw this.sqliteErrorToJsError(error, sql, params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(sql, params = null) {
|
exec(sql, params = null) {
|
||||||
this.logQuery(sql, params);
|
this.logQuery(sql, params);
|
||||||
return this.driver().exec(sql, params);
|
return this.driver().exec(sql, params).catch((error) => {
|
||||||
|
throw this.sqliteErrorToJsError(error, sql, params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionExecBatch(queries) {
|
transactionExecBatch(queries) {
|
||||||
@ -254,9 +284,9 @@ class Database {
|
|||||||
logQuery(sql, params = null) {
|
logQuery(sql, params = null) {
|
||||||
if (!this.debugMode()) return;
|
if (!this.debugMode()) return;
|
||||||
if (params !== null) {
|
if (params !== null) {
|
||||||
Log.debug('DB: ' + sql, JSON.stringify(params));
|
this.logger().debug('DB: ' + sql, JSON.stringify(params));
|
||||||
} else {
|
} else {
|
||||||
Log.debug('DB: ' + sql);
|
this.logger().debug('DB: ' + sql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +359,7 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshTableFields() {
|
refreshTableFields() {
|
||||||
Log.info('Initializing tables...');
|
this.logger().info('Initializing tables...');
|
||||||
let queries = [];
|
let queries = [];
|
||||||
queries.push(this.wrapQuery('DELETE FROM table_fields'));
|
queries.push(this.wrapQuery('DELETE FROM table_fields'));
|
||||||
|
|
||||||
@ -367,10 +397,10 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
Log.info('Checking for database schema update...');
|
this.logger().info('Checking for database schema update...');
|
||||||
|
|
||||||
return this.selectOne('SELECT * FROM version LIMIT 1').then((row) => {
|
return this.selectOne('SELECT * FROM version LIMIT 1').then((row) => {
|
||||||
Log.info('Current database version', row);
|
this.logger().info('Current database version', row);
|
||||||
// TODO: version update logic
|
// TODO: version update logic
|
||||||
|
|
||||||
// TODO: only do this if db has been updated:
|
// TODO: only do this if db has been updated:
|
||||||
@ -409,9 +439,8 @@ class Database {
|
|||||||
// return p;
|
// return p;
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
//console.info(error.code);
|
|
||||||
if (error && error.code != 0 && error.code != 'SQLITE_ERROR') {
|
if (error && error.code != 0 && error.code != 'SQLITE_ERROR') {
|
||||||
Log.error(error);
|
this.logger().error(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,14 +449,14 @@ class Database {
|
|||||||
// which means the database is empty and the tables need to be created.
|
// which means the database is empty and the tables need to be created.
|
||||||
// If it's any other error there's nothing we can do anyway.
|
// If it's any other error there's nothing we can do anyway.
|
||||||
|
|
||||||
Log.info('Database is new - creating the schema...');
|
this.logger().info('Database is new - creating the schema...');
|
||||||
|
|
||||||
let queries = this.wrapQueries(this.sqlStringToLines(structureSql));
|
let queries = this.wrapQueries(this.sqlStringToLines(structureSql));
|
||||||
queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")'));
|
queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")'));
|
||||||
queries.push(this.wrapQuery('INSERT INTO folders (`id`, `title`, `is_default`, `created_time`) VALUES ("' + uuid.create() + '", "' + _('Default list') + '", 1, ' + Math.round((new Date()).getTime() / 1000) + ')'));
|
queries.push(this.wrapQuery('INSERT INTO folders (`id`, `title`, `is_default`, `created_time`) VALUES ("' + uuid.create() + '", "' + _('Default list') + '", 1, ' + Math.round((new Date()).getTime() / 1000) + ')'));
|
||||||
|
|
||||||
return this.transactionExecBatch(queries).then(() => {
|
return this.transactionExecBatch(queries).then(() => {
|
||||||
Log.info('Database schema created successfully');
|
this.logger().info('Database schema created successfully');
|
||||||
// Calling initialize() now that the db has been created will make it go through
|
// Calling initialize() now that the db has been created will make it go through
|
||||||
// the normal db update process (applying any additional patch).
|
// the normal db update process (applying any additional patch).
|
||||||
return this.refreshTableFields();
|
return this.refreshTableFields();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { time } from 'src/time-utils.js';
|
import { time } from 'src/time-utils.js';
|
||||||
|
import { dirname, basename } from 'src/path-utils.js';
|
||||||
import { OneDriveApi } from 'src/onedrive-api.js';
|
import { OneDriveApi } from 'src/onedrive-api.js';
|
||||||
|
|
||||||
class FileApiDriverOneDrive {
|
class FileApiDriverOneDrive {
|
||||||
@ -40,8 +41,9 @@ class FileApiDriverOneDrive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async stat(path) {
|
async stat(path) {
|
||||||
|
let item = null;
|
||||||
try {
|
try {
|
||||||
let item = await this.api_.execJson('GET', this.makePath_(path), this.itemFilter_());
|
item = await this.api_.execJson('GET', this.makePath_(path), this.itemFilter_());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.error.code == 'itemNotFound') return null;
|
if (error.error.code == 'itemNotFound') return null;
|
||||||
throw error;
|
throw error;
|
||||||
@ -64,8 +66,9 @@ class FileApiDriverOneDrive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(path) {
|
async get(path) {
|
||||||
|
let content = null;
|
||||||
try {
|
try {
|
||||||
let content = await this.api_.execText('GET', this.makePath_(path) + ':/content');
|
content = await this.api_.execText('GET', this.makePath_(path) + ':/content');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.error.code == 'itemNotFound') return null;
|
if (error.error.code == 'itemNotFound') return null;
|
||||||
throw error;
|
throw error;
|
||||||
@ -73,8 +76,17 @@ class FileApiDriverOneDrive {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir(path) {
|
async mkdir(path) {
|
||||||
throw new Error('Not implemented');
|
let item = await this.stat(path);
|
||||||
|
if (item) return item;
|
||||||
|
|
||||||
|
let parentPath = dirname(path);
|
||||||
|
item = await this.api_.execJson('POST', this.makePath_(parentPath) + ':/children', this.itemFilter_(), {
|
||||||
|
name: basename(path),
|
||||||
|
folder: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.makeItem_(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(path, content) {
|
put(path, content) {
|
||||||
@ -88,8 +100,16 @@ class FileApiDriverOneDrive {
|
|||||||
return this.api_.exec('DELETE', this.makePath_(path));
|
return this.api_.exec('DELETE', this.makePath_(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
move(oldPath, newPath) {
|
async move(oldPath, newPath) {
|
||||||
throw new Error('Not implemented');
|
let newDir = dirname(newPath);
|
||||||
|
let newName = basename(newPath);
|
||||||
|
|
||||||
|
let item = await this.api_.execJson('PATCH', this.makePath_(oldPath), this.itemFilter_(), {
|
||||||
|
name: newName,
|
||||||
|
parentReference: { path: newDir },
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.makeItem_(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
format() {
|
format() {
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import { isHidden } from 'src/path-utils.js';
|
import { isHidden } from 'src/path-utils.js';
|
||||||
|
import { Logger } from 'src/logger.js';
|
||||||
|
|
||||||
class FileApi {
|
class FileApi {
|
||||||
|
|
||||||
constructor(baseDir, driver) {
|
constructor(baseDir, driver) {
|
||||||
this.baseDir_ = baseDir;
|
this.baseDir_ = baseDir;
|
||||||
this.driver_ = driver;
|
this.driver_ = driver;
|
||||||
|
this.logger_ = new Logger();
|
||||||
}
|
}
|
||||||
|
|
||||||
dlog(s) {
|
setLogger(l) {
|
||||||
console.info('FileApi: ' + s);
|
this.logger_ = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger() {
|
||||||
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath_(path) {
|
fullPath_(path) {
|
||||||
@ -21,7 +27,7 @@ class FileApi {
|
|||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!('includeHidden' in options)) options.includeHidden = false;
|
if (!('includeHidden' in options)) options.includeHidden = false;
|
||||||
|
|
||||||
this.dlog('list');
|
this.logger().debug('list');
|
||||||
return this.driver_.list(this.baseDir_).then((items) => {
|
return this.driver_.list(this.baseDir_).then((items) => {
|
||||||
if (!options.includeHidden) {
|
if (!options.includeHidden) {
|
||||||
let temp = [];
|
let temp = [];
|
||||||
@ -35,17 +41,17 @@ class FileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimestamp(path, timestamp) {
|
setTimestamp(path, timestamp) {
|
||||||
this.dlog('setTimestamp ' + path);
|
this.logger().debug('setTimestamp ' + path);
|
||||||
return this.driver_.setTimestamp(this.fullPath_(path), timestamp);
|
return this.driver_.setTimestamp(this.fullPath_(path), timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir(path) {
|
mkdir(path) {
|
||||||
this.dlog('delete ' + path);
|
this.logger().debug('mkdir ' + path);
|
||||||
return this.driver_.mkdir(this.fullPath_(path));
|
return this.driver_.mkdir(this.fullPath_(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
stat(path) {
|
stat(path) {
|
||||||
this.dlog('stat ' + path);
|
this.logger().debug('stat ' + path);
|
||||||
return this.driver_.stat(this.fullPath_(path)).then((output) => {
|
return this.driver_.stat(this.fullPath_(path)).then((output) => {
|
||||||
if (!output) return output;
|
if (!output) return output;
|
||||||
output.path = path;
|
output.path = path;
|
||||||
@ -54,22 +60,22 @@ class FileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(path) {
|
get(path) {
|
||||||
this.dlog('get ' + path);
|
this.logger().debug('get ' + path);
|
||||||
return this.driver_.get(this.fullPath_(path));
|
return this.driver_.get(this.fullPath_(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
put(path, content) {
|
put(path, content) {
|
||||||
this.dlog('put ' + path);
|
this.logger().debug('put ' + path);
|
||||||
return this.driver_.put(this.fullPath_(path), content);
|
return this.driver_.put(this.fullPath_(path), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(path) {
|
delete(path) {
|
||||||
this.dlog('delete ' + path);
|
this.logger().debug('delete ' + path);
|
||||||
return this.driver_.delete(this.fullPath_(path));
|
return this.driver_.delete(this.fullPath_(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
move(oldPath, newPath) {
|
move(oldPath, newPath) {
|
||||||
this.dlog('move ' + oldPath + ' => ' + newPath);
|
this.logger().debug('move ' + oldPath + ' => ' + newPath);
|
||||||
return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath));
|
return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
76
ReactNativeClient/src/logger.js
Normal file
76
ReactNativeClient/src/logger.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.targets_ = [];
|
||||||
|
this.level_ = Logger.LEVEL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLevel(level) {
|
||||||
|
this.level_ = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
level() {
|
||||||
|
return this.level_;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTargets() {
|
||||||
|
this.targets_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
addTarget(type, options = null) {
|
||||||
|
let target = { type: type };
|
||||||
|
for (let n in options) {
|
||||||
|
if (!options.hasOwnProperty(n)) continue;
|
||||||
|
target[n] = options[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.targets_.push(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(level, object) {
|
||||||
|
if (this.level() < level || !this.targets_.length) return;
|
||||||
|
|
||||||
|
let levelString = '';
|
||||||
|
if (this.level() == Logger.LEVEL_INFO) levelString = '[info] ';
|
||||||
|
if (this.level() == Logger.LEVEL_WARN) levelString = '[warn] ';
|
||||||
|
if (this.level() == Logger.LEVEL_ERROR) levelString = '[error] ';
|
||||||
|
let line = moment().format('YYYY-MM-DD HH:mm:ss') + ': ' + levelString;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.targets_.length; i++) {
|
||||||
|
let t = this.targets_[i];
|
||||||
|
if (t.type == 'console') {
|
||||||
|
let fn = 'debug';
|
||||||
|
if (level = Logger.LEVEL_ERROR) fn = 'error';
|
||||||
|
if (level = Logger.LEVEL_WARN) fn = 'warn';
|
||||||
|
if (level = Logger.LEVEL_INFO) fn = 'info';
|
||||||
|
if (typeof object === 'object') {
|
||||||
|
console[fn](line, object);
|
||||||
|
} else {
|
||||||
|
console[fn](line + object);
|
||||||
|
}
|
||||||
|
} else if (t.type == 'file') {
|
||||||
|
if (typeof object === 'object') object = JSON.stringify(object);
|
||||||
|
fs.appendFile(t.path, line + object + "\n", (error) => {
|
||||||
|
if (error) throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(object) { return this.log(Logger.LEVEL_ERROR, object); }
|
||||||
|
warn(object) { return this.log(Logger.LEVEL_WARN, object); }
|
||||||
|
info(object) { return this.log(Logger.LEVEL_INFO, object); }
|
||||||
|
debug(object) { return this.log(Logger.LEVEL_DEBUG, object); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LEVEL_NONE = 0;
|
||||||
|
Logger.LEVEL_ERROR = 10;
|
||||||
|
Logger.LEVEL_WARN = 20;
|
||||||
|
Logger.LEVEL_INFO = 30;
|
||||||
|
Logger.LEVEL_DEBUG = 40;
|
||||||
|
|
||||||
|
export { Logger };
|
@ -44,11 +44,11 @@ class Note extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static previews(parentId) {
|
static previews(parentId) {
|
||||||
return this.modelSelectAll('SELECT ' + this.previewFieldsSql() + ' FROM is_conflict = 0 AND notes WHERE parent_id = ?', [parentId]);
|
return this.modelSelectAll('SELECT ' + this.previewFieldsSql() + ' FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static preview(noteId) {
|
static preview(noteId) {
|
||||||
return this.modelSelectOne('SELECT ' + this.previewFieldsSql() + ' FROM is_conflict = 0 AND notes WHERE id = ?', [noteId]);
|
return this.modelSelectOne('SELECT ' + this.previewFieldsSql() + ' FROM notes WHERE is_conflict = 0 AND id = ?', [noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static conflictedNotes() {
|
static conflictedNotes() {
|
||||||
|
@ -77,7 +77,7 @@ class OneDriveApi {
|
|||||||
options.method = method;
|
options.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method == 'PATCH') {
|
if (method == 'PATCH' || method == 'POST') {
|
||||||
options.headers['Content-Type'] = 'application/json';
|
options.headers['Content-Type'] = 'application/json';
|
||||||
if (data) data = JSON.stringify(data);
|
if (data) data = JSON.stringify(data);
|
||||||
}
|
}
|
||||||
@ -88,8 +88,8 @@ class OneDriveApi {
|
|||||||
|
|
||||||
if (data) options.body = data;
|
if (data) options.body = data;
|
||||||
|
|
||||||
console.info(method + ' ' + url);
|
// console.info(method + ' ' + url);
|
||||||
console.info(data);
|
// console.info(data);
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
options.headers['Authorization'] = 'bearer ' + this.token();
|
options.headers['Authorization'] = 'bearer ' + this.token();
|
||||||
@ -98,8 +98,6 @@ class OneDriveApi {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let error = await response.json();
|
let error = await response.json();
|
||||||
|
|
||||||
console.info(error);
|
|
||||||
|
|
||||||
if (error && error.error && error.error.code == 'InvalidAuthenticationToken') {
|
if (error && error.error && error.error.code == 'InvalidAuthenticationToken') {
|
||||||
await this.refreshAccessToken();
|
await this.refreshAccessToken();
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
function dirname(path) {
|
||||||
|
if (!path) throw new Error('Path is empty');
|
||||||
|
let s = path.split('/');
|
||||||
|
s.pop();
|
||||||
|
return s.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
function basename(path) {
|
function basename(path) {
|
||||||
if (!path) throw new Error('Path is empty');
|
if (!path) throw new Error('Path is empty');
|
||||||
let s = path.split('/');
|
let s = path.split('/');
|
||||||
@ -10,4 +17,4 @@ function isHidden(path) {
|
|||||||
return b[0] === '.';
|
return b[0] === '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
export { basename, isHidden };
|
export { basename, dirname, isHidden };
|
@ -6,7 +6,8 @@ import { Note } from 'src/models/note.js';
|
|||||||
import { BaseModel } from 'src/base-model.js';
|
import { BaseModel } from 'src/base-model.js';
|
||||||
import { sprintf } from 'sprintf-js';
|
import { sprintf } from 'sprintf-js';
|
||||||
import { time } from 'src/time-utils.js';
|
import { time } from 'src/time-utils.js';
|
||||||
import { Log } from 'src/log.js'
|
import { Logger } from 'src/logger.js'
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
class Synchronizer {
|
class Synchronizer {
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ class Synchronizer {
|
|||||||
this.db_ = db;
|
this.db_ = db;
|
||||||
this.api_ = api;
|
this.api_ = api;
|
||||||
this.syncDirName_ = '.sync';
|
this.syncDirName_ = '.sync';
|
||||||
|
this.logger_ = new Logger();
|
||||||
}
|
}
|
||||||
|
|
||||||
db() {
|
db() {
|
||||||
@ -24,6 +26,14 @@ class Synchronizer {
|
|||||||
return this.api_;
|
return this.api_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLogger(l) {
|
||||||
|
this.logger_ = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger() {
|
||||||
|
return this.logger_;
|
||||||
|
}
|
||||||
|
|
||||||
async createWorkDir() {
|
async createWorkDir() {
|
||||||
if (this.syncWorkDir_) return this.syncWorkDir_;
|
if (this.syncWorkDir_) return this.syncWorkDir_;
|
||||||
let dir = await this.api().mkdir(this.syncDirName_);
|
let dir = await this.api().mkdir(this.syncDirName_);
|
||||||
@ -36,6 +46,8 @@ class Synchronizer {
|
|||||||
// last sync and apply the changes to remote.
|
// last sync and apply the changes to remote.
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
this.logger().info('Starting synchronization...');
|
||||||
|
|
||||||
await this.createWorkDir();
|
await this.createWorkDir();
|
||||||
|
|
||||||
let donePaths = [];
|
let donePaths = [];
|
||||||
@ -74,7 +86,7 @@ class Synchronizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info('Sync action (1): ' + action);
|
this.logger().debug('Sync action (1): ' + action);
|
||||||
|
|
||||||
if (action == 'createRemote' || action == 'updateRemote') {
|
if (action == 'createRemote' || action == 'updateRemote') {
|
||||||
|
|
||||||
@ -132,7 +144,7 @@ class Synchronizer {
|
|||||||
for (let i = 0; i < deletedItems.length; i++) {
|
for (let i = 0; i < deletedItems.length; i++) {
|
||||||
let item = deletedItems[i];
|
let item = deletedItems[i];
|
||||||
let path = BaseItem.systemPath(item.item_id)
|
let path = BaseItem.systemPath(item.item_id)
|
||||||
console.info('Sync action (2): deleteRemote');
|
this.logger().debug('Sync action (2): deleteRemote');
|
||||||
await this.api().delete(path);
|
await this.api().delete(path);
|
||||||
await BaseModel.remoteDeletedItem(item.item_id);
|
await BaseModel.remoteDeletedItem(item.item_id);
|
||||||
}
|
}
|
||||||
@ -155,23 +167,27 @@ class Synchronizer {
|
|||||||
if (donePaths.indexOf(path) > 0) continue;
|
if (donePaths.indexOf(path) > 0) continue;
|
||||||
|
|
||||||
let action = null;
|
let action = null;
|
||||||
|
let reason = '';
|
||||||
let local = await BaseItem.loadItemByPath(path);
|
let local = await BaseItem.loadItemByPath(path);
|
||||||
if (!local) {
|
if (!local) {
|
||||||
action = 'createLocal';
|
action = 'createLocal';
|
||||||
|
reason = 'Local exists but remote does not';
|
||||||
} else {
|
} else {
|
||||||
if (remote.updated_time > local.updated_time) {
|
if (remote.updated_time > local.updated_time) {
|
||||||
action = 'updateLocal';
|
action = 'updateLocal';
|
||||||
|
reason = sprintf('Remote (%s) is more recent than local (%s)', time.unixMsToIso(remote.updated_time), time.unixMsToIso(local.updated_time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!action) continue;
|
if (!action) continue;
|
||||||
|
|
||||||
console.info('Sync action (3): ' + action);
|
this.logger().debug('Sync action (3): ' + action);
|
||||||
|
this.logger().debug('Reason: ' + reason);
|
||||||
|
|
||||||
if (action == 'createLocal' || action == 'updateLocal') {
|
if (action == 'createLocal' || action == 'updateLocal') {
|
||||||
let content = await this.api().get(path);
|
let content = await this.api().get(path);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
Log.warn('Remote item has been deleted between now and the list() call? In that case it will handled during the next sync: ' + path);
|
this.logger().warn('Remote item has been deleted between now and the list() call? In that case it will handled during the next sync: ' + path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
content = BaseItem.unserialize(content);
|
content = BaseItem.unserialize(content);
|
||||||
@ -192,11 +208,14 @@ class Synchronizer {
|
|||||||
let noteIds = await Folder.syncedNoteIds();
|
let noteIds = await Folder.syncedNoteIds();
|
||||||
for (let i = 0; i < noteIds.length; i++) {
|
for (let i = 0; i < noteIds.length; i++) {
|
||||||
if (remoteIds.indexOf(noteIds[i]) < 0) {
|
if (remoteIds.indexOf(noteIds[i]) < 0) {
|
||||||
console.info('Sync action (4): deleteLocal: ' + noteIds[i]);
|
this.logger().debug('Sync action (4): deleteLocal: ' + noteIds[i]);
|
||||||
await Note.delete(noteIds[i], { trackDeleted: false });
|
await Note.delete(noteIds[i], { trackDeleted: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Number of sync items (Created, updated, deleted Local/Remote)
|
||||||
|
// Total number of items
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
let time = {
|
let time = {
|
||||||
|
|
||||||
unix() {
|
unix() {
|
||||||
@ -10,7 +12,11 @@ let time = {
|
|||||||
|
|
||||||
unixMsToS(ms) {
|
unixMsToS(ms) {
|
||||||
return Math.floor(ms / 1000);
|
return Math.floor(ms / 1000);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
unixMsToIso(ms) {
|
||||||
|
return moment.unix(ms / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z';
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user