2017-11-03 02:09:34 +02:00
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' ) ;
2017-11-03 02:09:34 +02:00
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' ) ;
2018-01-25 21:01:14 +02:00
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 ( ) ;
2017-11-24 01:10:55 +02:00
this . syncTargetId _ = null ;
2017-07-17 21:37:59 +02:00
this . releaseLockFn _ = null ;
2017-10-24 20:03:12 +02:00
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 ) ;
} ) ;
} ) ;
}
2018-01-25 21:01:14 +02:00
async doAuth ( ) {
2017-11-24 01:10:55 +02:00
const syncTarget = reg . syncTarget ( this . syncTargetId _ ) ;
2018-01-25 21:01:14 +02:00
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 ) ;
2018-03-27 01:05:39 +02:00
Setting . setValue ( 'sync.' + this . syncTargetId _ + '.auth' , response . access _token ) ;
2018-03-26 19:33:55 +02:00
api . setAuthToken ( response . access _token ) ;
2018-01-25 21:01:14 +02:00
return true ;
}
2018-03-26 19:33:55 +02:00
this . stdout ( _ ( 'Not authentified with %s. Please provide any missing credentials.' , syncTargetMd . label ) ) ;
2018-01-25 21:01:14 +02:00
return false ;
2017-11-24 01:10:55 +02:00
}
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
2017-07-24 20:01:40 +02:00
// Lock is unique per profile/database
2018-03-26 19:33:55 +02:00
// TODO: use SQLite database to do lock?
2018-02-23 21:32:19 +02:00
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
2017-10-24 20:03:12 +02:00
const cleanUp = ( ) => {
cliUtils . redrawDone ( ) ;
if ( this . releaseLockFn _ ) {
this . releaseLockFn _ ( ) ;
this . releaseLockFn _ = null ;
}
} ;
2017-07-17 21:37:59 +02:00
try {
2017-11-24 01:10:55 +02:00
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 ( ) ;
2017-11-24 01:10:55 +02:00
2018-01-25 21:01:14 +02:00
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
2017-11-24 01:10: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 ) => {
2017-07-18 00:43:29 +02:00
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
} ,
} ;
2017-11-24 01:10:55 +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
2017-11-24 01:10:55 +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 ) {
2017-10-24 20:03:12 +02:00
cleanUp ( ) ;
2017-07-17 21:37:59 +02:00
throw error ;
}
2017-07-16 00:47:11 +02:00
2017-10-24 20:03:12 +02:00
cleanUp ( ) ;
2017-07-10 22:03:46 +02:00
}
async cancel ( ) {
2017-11-24 01:10:55 +02:00
if ( this . doingAuth ( ) ) {
this . cancelAuth ( ) ;
2017-10-24 20:03:12 +02:00
return ;
}
2017-11-24 01:10:55 +02:00
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
2017-11-24 01:10:55 +02:00
const syncTarget = reg . syncTarget ( syncTargetId ) ;
2018-03-26 19:33:55 +02:00
if ( await syncTarget . isAuthenticated ( ) ) {
2017-11-24 01:10:55 +02:00
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
2017-11-24 01:10:55 +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 ;