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:
parent
1e45668c19
commit
9acf41567b
@ -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);
|
||||
});
|
@ -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
|
@ -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');
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { time } from 'lib/time-utils.js';
|
||||
|
||||
class FileApiDriverMemory {
|
||||
|
||||
constructor(baseDir) {
|
||||
constructor() {
|
||||
this.items_ = [];
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,8 @@ class Logger {
|
||||
});
|
||||
|
||||
this.scheduleFileAppendQueueProcessing_();
|
||||
} else if (t.type == 'vorpal') {
|
||||
t.vorpal.log(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user