1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/CliClient/app/command-sync.js

244 lines
6.7 KiB
JavaScript
Raw Normal View History

const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
2017-12-14 20:12:14 +02:00
const Setting = require('lib/models/Setting.js');
const BaseItem = require('lib/models/BaseItem.js');
2018-10-08 20:11:53 +02:00
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { Synchronizer } = require('lib/synchronizer.js');
const { reg } = require('lib/registry.js');
const { cliUtils } = require('./cli-utils.js');
const md5 = require('md5');
2017-07-17 21:37:59 +02:00
const locker = require('proper-lockfile');
const fs = require('fs-extra');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
2017-07-10 22:03:46 +02:00
class Command extends BaseCommand {
2017-07-17 20:46:09 +02:00
constructor() {
super();
this.syncTargetId_ = null;
2017-07-17 21:37:59 +02:00
this.releaseLockFn_ = null;
this.oneDriveApiUtils_ = null;
2017-07-17 20:46:09 +02:00
}
2017-07-10 22:03:46 +02:00
usage() {
return 'sync';
}
description() {
2017-07-26 23:27:03 +02:00
return _('Synchronises with remote storage.');
2017-07-10 22:03:46 +02:00
}
options() {
return [
2017-07-28 20:13:07 +02:00
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
2017-07-10 22:03:46 +02:00
];
}
2017-07-17 21:37:59 +02:00
static lockFile(filePath) {
return new Promise((resolve, reject) => {
2017-07-19 21:15:55 +02:00
locker.lock(filePath, { stale: 1000 * 60 * 5 }, (error, release) => {
2017-07-17 21:37:59 +02:00
if (error) {
reject(error);
return;
}
resolve(release);
});
});
}
static isLocked(filePath) {
return new Promise((resolve, reject) => {
locker.check(filePath, (error, isLocked) => {
if (error) {
reject(error);
return;
}
resolve(isLocked);
});
});
}
async doAuth() {
const syncTarget = reg.syncTarget(this.syncTargetId_);
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
const auth = await this.oneDriveApiUtils_.oauthDance({
log: (...s) => { return this.stdout(...s); }
});
this.oneDriveApiUtils_ = null;
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
if (!auth) {
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
return false;
}
2018-03-26 19:33:55 +02:00
return true;
} else if (syncTargetMd.name === 'dropbox') { // Dropbox
const api = await syncTarget.api();
const loginUrl = api.loginUrl();
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
this.stdout(_('Step 1: Open this URL in your browser to authorise the application:'));
this.stdout(loginUrl);
const authCode = await this.prompt(_('Step 2: Enter the code provided by Dropbox:'), { type: 'string' });
if (!authCode) {
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
return false;
}
const response = await api.execAuthToken(authCode);
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', response.access_token);
2018-03-26 19:33:55 +02:00
api.setAuthToken(response.access_token);
return true;
}
2018-03-26 19:33:55 +02:00
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTargetMd.label));
return false;
}
cancelAuth() {
if (this.oneDriveApiUtils_) {
this.oneDriveApiUtils_.cancelOAuthDance();
return;
}
}
doingAuth() {
return !!this.oneDriveApiUtils_;
}
2017-07-10 22:03:46 +02:00
async action(args) {
2017-07-17 21:37:59 +02:00
this.releaseLockFn_ = null;
2017-07-17 20:46:09 +02:00
// Lock is unique per profile/database
2018-03-26 19:33:55 +02:00
// TODO: use SQLite database to do lock?
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
2017-07-17 21:37:59 +02:00
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
2017-07-14 21:06:01 +02:00
2017-08-04 18:50:12 +02:00
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
2017-07-13 20:09:47 +02:00
2017-08-04 18:50:12 +02:00
this.releaseLockFn_ = await Command.lockFile(lockFilePath);
} catch (error) {
if (error.code == 'ELOCKED') {
const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file);
2017-10-07 18:30:27 +02:00
this.stdout(msg);
2017-08-04 18:50:12 +02:00
return;
}
throw error;
}
2017-07-10 22:03:46 +02:00
const cleanUp = () => {
cliUtils.redrawDone();
if (this.releaseLockFn_) {
this.releaseLockFn_();
this.releaseLockFn_ = null;
}
};
2017-07-17 21:37:59 +02:00
try {
this.syncTargetId_ = Setting.value('sync.target');
if (args.options.target) this.syncTargetId_ = args.options.target;
const syncTarget = reg.syncTarget(this.syncTargetId_);
2017-07-24 21:47:01 +02:00
2018-03-26 19:33:55 +02:00
if (!await syncTarget.isAuthenticated()) {
2017-10-19 00:13:53 +02:00
app().gui().showConsole();
app().gui().maximizeConsole();
const authDone = await this.doAuth();
if (!authDone) return cleanUp();
2017-07-24 21:47:01 +02:00
}
2017-07-19 21:15:55 +02:00
const sync = await syncTarget.synchronizer();
2017-07-10 22:03:46 +02:00
2017-07-17 21:37:59 +02:00
let options = {
onProgress: (report) => {
let lines = Synchronizer.reportToLines(report);
2017-08-04 18:50:12 +02:00
if (lines.length) cliUtils.redraw(lines.join(' '));
2017-07-17 21:37:59 +02:00
},
onMessage: (msg) => {
2017-08-04 18:50:12 +02:00
cliUtils.redrawDone();
2017-10-07 18:30:27 +02:00
this.stdout(msg);
2017-07-17 21:37:59 +02:00
},
};
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_));
2017-07-17 21:37:59 +02:00
2017-07-26 23:27:03 +02:00
if (!sync) throw new Error(_('Cannot initialize synchroniser.'));
2017-07-17 21:37:59 +02:00
2017-10-07 18:30:27 +02:00
this.stdout(_('Starting synchronisation...'));
2017-07-17 21:37:59 +02:00
const contextKey = 'sync.' + this.syncTargetId_ + '.context';
2017-08-19 22:56:28 +02:00
let context = Setting.value(contextKey);
2017-07-18 21:57:49 +02:00
context = context ? JSON.parse(context) : {};
options.context = context;
2017-07-24 21:47:01 +02:00
try {
let newContext = await sync.start(options);
2017-08-19 22:56:28 +02:00
Setting.setValue(contextKey, JSON.stringify(newContext));
2017-07-24 21:47:01 +02:00
} catch (error) {
if (error.code == 'alreadyStarted') {
2017-10-07 18:30:27 +02:00
this.stdout(error.message);
2017-07-24 21:47:01 +02:00
} else {
throw error;
}
}
2017-07-17 21:37:59 +02:00
2018-10-08 20:11:53 +02:00
// When using the tool in command line mode, the ResourceFetcher service is
// not going to be running in the background, so the resources need to be
// explicitely downloaded below.
if (!app().hasGui()) {
2018-10-30 02:17:50 +02:00
this.stdout(_('Downloading resources...'));
2018-10-08 20:11:53 +02:00
await ResourceFetcher.instance().fetchAll();
await ResourceFetcher.instance().waitForAllFinished();
}
2017-07-17 21:37:59 +02:00
await app().refreshCurrentFolder();
} catch (error) {
cleanUp();
2017-07-17 21:37:59 +02:00
throw error;
}
2017-07-16 00:47:11 +02:00
cleanUp();
2017-07-10 22:03:46 +02:00
}
async cancel() {
if (this.doingAuth()) {
this.cancelAuth();
return;
}
const syncTargetId = this.syncTargetId_ ? this.syncTargetId_ : Setting.value('sync.target');
2017-07-17 20:46:09 +02:00
2017-08-04 18:50:12 +02:00
cliUtils.redrawDone();
2017-10-07 18:30:27 +02:00
this.stdout(_('Cancelling... Please wait.'));
2017-07-26 22:09:33 +02:00
const syncTarget = reg.syncTarget(syncTargetId);
2018-03-26 19:33:55 +02:00
if (await syncTarget.isAuthenticated()) {
const sync = await syncTarget.synchronizer();
2017-10-14 20:03:23 +02:00
if (sync) await sync.cancel();
2017-07-26 22:09:33 +02:00
} else {
if (this.releaseLockFn_) this.releaseLockFn_();
this.releaseLockFn_ = null;
}
2017-07-17 20:46:09 +02:00
this.syncTargetId_ = null;
2017-07-10 22:03:46 +02:00
}
2017-08-20 16:29:18 +02:00
cancellable() {
return true;
}
2017-07-10 22:03:46 +02:00
}
module.exports = Command;