1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-26 18:58:21 +02:00

Added support for local sync and fixed sync bug

This commit is contained in:
Laurent Cozic 2017-06-27 19:26:29 +00:00
parent 1e45668c19
commit 9acf41567b
11 changed files with 67 additions and 65 deletions

View File

@ -6,6 +6,7 @@ require('babel-plugin-transform-runtime');
import { FileApi } from 'lib/file-api.js';
import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js';
import { FileApiDriverMemory } from 'lib/file-api-driver-memory.js';
import { FileApiDriverLocal } from 'lib/file-api-driver-local.js';
import { Database } from 'lib/database.js';
import { DatabaseDriverNode } from 'lib/database-driver-node.js';
import { BaseModel } from 'lib/base-model.js';
@ -29,11 +30,15 @@ process.on('unhandledRejection', (reason, p) => {
const packageJson = require('./package.json');
let profileDir = os.homedir() + '/.config/' + Setting.value('appName');
let initArgs = {
profileDir: null,
syncTarget: null,
}
let currentFolder = null;
let commands = [];
let database_ = null;
let synchronizer_ = null;
let synchronizers_ = {};
let logger = new Logger();
let dbLogger = new Logger();
let syncLogger = new Logger();
@ -41,11 +46,18 @@ let syncLogger = new Logger();
commands.push({
usage: 'root',
options: [
['-p, --profile <filePath>', 'Sets the profile path directory.'],
['--profile <filePath>', 'Sets the profile path directory.'],
['--sync-target <target>', 'Sets the sync target.'],
],
action: function(args, end) {
let p = args.profile || args.p;
if (p) profileDir = p;
if (args.profile) {
initArgs.profileDir = args.profile;
}
if (args['sync-target']) {
initArgs.syncTarget = args['sync-target'];
}
end();
},
});
@ -312,11 +324,11 @@ commands.push({
usage: 'sync',
description: 'Synchronizes with remote storage.',
action: function(args, end) {
//synchronizer('onedrive').then((s) => {
synchronizer('memory').then((s) => {
this.log(_('Synchronization target: %s', Setting.value('sync.target')));
synchronizer(Setting.value('sync.target')).then((s) => {
return s.start();
}).catch((error) => {
logger.error(error);
this.log(error);
}).then(() => {
end();
});
@ -416,12 +428,12 @@ function execCommand(name, args) {
});
}
async function synchronizer(remoteBackend) {
if (synchronizer_) return synchronizer_;
async function synchronizer(syncTarget) {
if (synchronizers_[syncTarget]) return synchronizers_[syncTarget];
let fileApi = null;
if (remoteBackend == 'onedrive') {
if (syncTarget == 'onedrive') {
const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b';
const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5';
@ -431,7 +443,7 @@ async function synchronizer(remoteBackend) {
if (auth) {
auth = JSON.parse(auth);
} else {
auth = await driver.api().oauthDance();
auth = await driver.api().oauthDance(vorpal);
Setting.setValue('sync.onedrive.auth', JSON.stringify(auth));
}
@ -444,24 +456,25 @@ async function synchronizer(remoteBackend) {
logger.info('App dir: ' + appDir);
fileApi = new FileApi(appDir, driver);
fileApi.setLogger(logger);
} else if (remoteBackend == 'memory') {
let driver = new FileApiDriverMemory();
fileApi = new FileApi('joplin', driver);
} else if (syncTarget == 'memory') {
fileApi = new FileApi('joplin', new FileApiDriverMemory());
fileApi.setLogger(logger);
} else if (syncTarget == 'local') {
let syncDir = Setting.value('profileDir') + '/sync';
vorpal.log(syncDir);
await fs.mkdirp(syncDir, 0o755);
fileApi = new FileApi(syncDir, new FileApiDriverLocal());
fileApi.setLogger(logger);
} else {
throw new Error('Unknown backend: ' + remoteBackend);
throw new Error('Unknown backend: ' + syncTarget);
}
synchronizer_ = new Synchronizer(database_, fileApi);
synchronizer_.setLogger(syncLogger);
synchronizers_[syncTarget] = new Synchronizer(database_, fileApi);
synchronizers_[syncTarget].setLogger(syncLogger);
return synchronizer_;
return synchronizers_[syncTarget];
}
// let s = await synchronizer('onedrive');
// await synchronizer_.start();
// return;
function switchCurrentFolder(folder) {
if (!folder) throw new Error(_('No active folder is defined.'));
@ -571,25 +584,6 @@ process.stdin.on('keypress', (_, key) => {
const vorpal = require('vorpal')();
async function main() {
// console.info('DELETING ALL DATA');
// await db.exec('DELETE FROM notes');
// await db.exec('DELETE FROM changes');
// await db.exec('DELETE FROM folders');
// await db.exec('DELETE FROM resources');
// await db.exec('DELETE FROM deleted_items');
// await db.exec('DELETE FROM tags');
// await db.exec('DELETE FROM note_tags');
// let folder1 = await Folder.save({ title: 'test1' });
// let folder2 = await Folder.save({ title: 'test2' });
// await importEnex(folder1.id, '/mnt/c/Users/Laurent/Desktop/Laurent.enex');
// return;
// let testglob = await Note.glob('title', 'La *', {
// fields: ['title', 'updated_time'],
// });
// console.info(testglob);
for (let commandIndex = 0; commandIndex < commands.length; commandIndex++) {
let c = commands[commandIndex];
if (c.usage == 'root') continue;
@ -618,6 +612,7 @@ async function main() {
await handleStartArgs(process.argv);
const profileDir = initArgs.profileDir ? initArgs.profileDir : os.homedir() + '/.config/' + Setting.value('appName');
const resourceDir = profileDir + '/resources';
Setting.setConstant('profileDir', profileDir);
@ -644,6 +639,8 @@ async function main() {
BaseModel.db_ = database_;
await Setting.load();
if (initArgs.syncTarget) Setting.setValue('sync.target', initArgs.syncTarget);
let activeFolderId = Setting.value('activeFolderId');
let activeFolder = null;
if (activeFolderId) activeFolder = await Folder.load(activeFolderId);
@ -654,5 +651,6 @@ async function main() {
}
main().catch((error) => {
console.error('Fatal error: ', error);
vorpal.log('Fatal error:');
vorpal.log(error);
});

View File

@ -2,4 +2,4 @@
set -e
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
bash $CLIENT_DIR/build.sh
NODE_PATH="$CLIENT_DIR/build/" node build/main.js --profile ~/Temp/TestNotes
NODE_PATH="$CLIENT_DIR/build/" node build/main.js --profile ~/Temp/TestNotes --sync-target local

View File

@ -461,9 +461,10 @@ class Database {
this.logger().info('Database is new - creating the schema...');
let now = time.unixMs();
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 folders (`id`, `title`, `created_time`) VALUES ("' + uuid.create() + '", "' + _('Notebook') + '", ' + (new Date()).getTime() + ')'));
queries.push(this.wrapQuery('INSERT INTO folders (`id`, `title`, `created_time`, `updated_time`) VALUES ("' + uuid.create() + '", "' + _('Notebook') + '", ' + now + ', ' + now + ')'));
return this.transactionExecBatch(queries).then(() => {
this.logger().info('Database schema created successfully');

View File

@ -65,6 +65,7 @@ class FileApiDriverLocal {
chain.push((output) => {
if (!output) output = [];
return this.stat(path + '/' + items[i]).then((stat) => {
stat.path = items[i];
output.push(stat);
return output;
});

View File

@ -2,7 +2,7 @@ import { time } from 'lib/time-utils.js';
class FileApiDriverMemory {
constructor(baseDir) {
constructor() {
this.items_ = [];
}

View File

@ -23,6 +23,7 @@ class FileApi {
return output;
}
// DRIVER MUST RETURN PATHS RELATIVE TO `path`
list(path = '', options = null) {
if (!options) options = {};
if (!('includeHidden' in options)) options.includeHidden = false;
@ -41,17 +42,17 @@ class FileApi {
}
setTimestamp(path, timestamp) {
this.logger().debug('setTimestamp ' + path);
this.logger().debug('setTimestamp ' + this.fullPath_(path));
return this.driver_.setTimestamp(this.fullPath_(path), timestamp);
}
mkdir(path) {
this.logger().debug('mkdir ' + path);
this.logger().debug('mkdir ' + this.fullPath_(path));
return this.driver_.mkdir(this.fullPath_(path));
}
stat(path) {
this.logger().debug('stat ' + path);
this.logger().debug('stat ' + this.fullPath_(path));
return this.driver_.stat(this.fullPath_(path)).then((output) => {
if (!output) return output;
output.path = path;
@ -60,22 +61,22 @@ class FileApi {
}
get(path) {
this.logger().debug('get ' + path);
this.logger().debug('get ' + this.fullPath_(path));
return this.driver_.get(this.fullPath_(path));
}
put(path, content) {
this.logger().debug('put ' + path);
this.logger().debug('put ' + this.fullPath_(path));
return this.driver_.put(this.fullPath_(path), content);
}
delete(path) {
this.logger().debug('delete ' + path);
this.logger().debug('delete ' + this.fullPath_(path));
return this.driver_.delete(this.fullPath_(path));
}
move(oldPath, newPath) {
this.logger().debug('move ' + oldPath + ' => ' + newPath);
this.logger().debug('move ' + this.fullPath_(oldPath) + ' => ' + this.fullPath_(newPath));
return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath));
}

View File

@ -72,6 +72,8 @@ class Logger {
});
this.scheduleFileAppendQueueProcessing_();
} else if (t.type == 'vorpal') {
t.vorpal.log(object);
}
}
}

View File

@ -145,10 +145,9 @@ class BaseItem extends BaseModel {
}
static itemsThatNeedSync(limit = 100) {
let conflictFolderId = Setting.value('sync.conflictFolderId');
return Folder.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time AND id != ? LIMIT ' + limit, [conflictFolderId]).then((items) => {
return Folder.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit).then((items) => {
if (items.length) return { hasMore: true, items: items };
return Note.modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND parent_id != ? LIMIT ' + limit, [conflictFolderId]).then((items) => {
return Note.modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit).then((items) => {
return { hasMore: items.length >= limit, items: items };
});
});

View File

@ -134,12 +134,8 @@ Setting.defaults_ = {
'clientId': { value: '', type: 'string' },
'sessionId': { value: '', type: 'string' },
'activeFolderId': { value: '', type: 'string' },
'user.email': { value: '', type: 'string' },
'user.session': { value: '', type: 'string' },
'sync.lastRevId': { value: 0, type: 'int' }, // DEPRECATED
'sync.lastUpdateTime': { value: 0, type: 'int' },
'sync.conflictFolderId': { value: '', type: 'string' },
'sync.onedrive.auth': { value: '', type: 'string' },
'sync.target': { value: 'onedrive', type: 'string' },
};
// Contains constants that are set by the application and

View File

@ -152,7 +152,9 @@ class OneDriveApi {
this.dispatch('authRefreshed', this.auth_);
}
async oauthDance() {
async oauthDance(targetConsole = null) {
if (targetConsole === null) targetConsole = console;
this.auth_ = null;
let ports = this.possibleOAuthDancePorts();
@ -224,8 +226,9 @@ class OneDriveApi {
enableServerDestroy(server);
console.info('Please open this URL in your browser to authentify the application:');
console.info(authCodeUrl);
targetConsole.log('Please open this URL in your browser to authentify the application:');
targetConsole.log('');
targetConsole.log(authCodeUrl);
});
}

View File

@ -225,6 +225,7 @@ class Synchronizer {
let remoteIds = [];
let remotes = await this.api().list();
for (let i = 0; i < remotes.length; i++) {
let remote = remotes[i];
let path = remote.path;
@ -249,7 +250,7 @@ class Synchronizer {
if (action == 'createLocal' || action == 'updateLocal') {
let content = await this.api().get(path);
if (!content) {
if (content === null) {
this.logger().warn('Remote has been deleted between now and the list() call? In that case it will be handled during the next sync: ' + path);
continue;
}