1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-10-31 00:07:48 +02:00

Better error handling during sync setup

This commit is contained in:
Laurent Cozic
2017-07-06 23:15:31 +01:00
parent 8ee0c38f86
commit 8751aa1a34
10 changed files with 81 additions and 8285 deletions

View File

@@ -10,4 +10,5 @@ tests/fuzzing/client1
tests/fuzzing/client2 tests/fuzzing/client2
tests/fuzzing/sync tests/fuzzing/sync
tests/fuzzing.* tests/fuzzing.*
tests/fuzzing -* tests/fuzzing -*
tests/logs/*

View File

@@ -46,6 +46,7 @@ Logger.fsDriver_ = fsDriver;
Resource.fsDriver_ = fsDriver; Resource.fsDriver_ = fsDriver;
Setting.setConstant('appId', 'net.cozic.joplin-cli'); Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appType', 'cli');
let currentFolder = null; let currentFolder = null;
let commands = []; let commands = [];
@@ -509,7 +510,7 @@ commands.push({
options: [ options: [
['--random-failures', 'For debugging purposes. Do not use.'], ['--random-failures', 'For debugging purposes. Do not use.'],
], ],
action: function(args, end) { action: async function(args, end) {
let options = { let options = {
onProgress: (report) => { onProgress: (report) => {
@@ -528,16 +529,23 @@ commands.push({
}; };
this.log(_('Synchronization target: %s', Setting.value('sync.target'))); this.log(_('Synchronization target: %s', Setting.value('sync.target')));
synchronizer(Setting.value('sync.target')).then((s) => {
this.log(_('Starting synchronization...')); let sync = await synchronizer(Setting.value('sync.target'));
return s.start(options); if (!sync) {
}).catch((error) => {
this.log(error);
}).then(() => {
vorpalUtils.redrawDone();
this.log(_('Done.'));
end(); end();
}); return;
}
try {
this.log(_('Starting synchronization...'));
await sync.start(options);
} catch (error) {
this.log(error);
}
vorpalUtils.redrawDone();
this.log(_('Done.'));
end();
}, },
}); });
@@ -682,13 +690,12 @@ async function synchronizer(syncTarget) {
const oneDriveApi = reg.oneDriveApi(); const oneDriveApi = reg.oneDriveApi();
let driver = new FileApiDriverOneDrive(oneDriveApi); let driver = new FileApiDriverOneDrive(oneDriveApi);
let auth = Setting.value('sync.onedrive.auth'); let auth = Setting.value('sync.onedrive.auth');
if (auth) { if (!oneDriveApi.auth()) {
auth = JSON.parse(auth);
} else {
const oneDriveApiUtils = new OneDriveApiNodeUtils(oneDriveApi); const oneDriveApiUtils = new OneDriveApiNodeUtils(oneDriveApi);
auth = await oneDriveApiUtils.oauthDance(vorpal); auth = await oneDriveApiUtils.oauthDance(vorpal);
Setting.setValue('sync.onedrive.auth', JSON.stringify(auth)); Setting.setValue('sync.onedrive.auth', auth ? JSON.stringify(auth) : auth);
if (!auth) return;
} }
let appDir = await oneDriveApi.appDirectory(); let appDir = await oneDriveApi.appDirectory();
@@ -709,7 +716,7 @@ async function synchronizer(syncTarget) {
throw new Error('Unknown backend: ' + syncTarget); throw new Error('Unknown backend: ' + syncTarget);
} }
synchronizers_[syncTarget] = new Synchronizer(database_, fileApi); synchronizers_[syncTarget] = new Synchronizer(database_, fileApi, Setting.value('appType'));
synchronizers_[syncTarget].setLogger(syncLogger); synchronizers_[syncTarget].setLogger(syncLogger);
return synchronizers_[syncTarget]; return synchronizers_[syncTarget];

View File

@@ -53,39 +53,6 @@ class OneDriveApiNodeUtils {
if (!query.code) return writeResponse(400, '"code" query parameter is missing'); if (!query.code) return writeResponse(400, '"code" query parameter is missing');
// let body = new FormData();
// body.append('client_id', this.api().clientId());
// body.append('client_secret', this.api().clientSecret());
// body.append('code', query.code ? query.code : '');
// body.append('redirect_uri', 'http://localhost:' + port.toString());
// body.append('grant_type', 'authorization_code');
// let options = {
// method: 'POST',
// body: body,
// };
// fetch(this.api().tokenBaseUrl(), options).then((r) => {
// this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then((r) => {
// if (!r.ok) {
// errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText;
// writeResponse(400, errorMessage);
// targetConsole.log('');
// targetConsole.log(errorMessage);
// server.destroy();
// return;
// }
// return r.json().then((json) => {
// this.api().setAuth(json);
// writeResponse(200, 'The application has been authorised - you may now close this browser tab.');
// targetConsole.log('');
// targetConsole.log('The application has been successfully authorised.');
// server.destroy();
// });
// });
this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => { this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => {
writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); writeResponse(200, 'The application has been authorised - you may now close this browser tab.');
targetConsole.log(''); targetConsole.log('');

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,9 @@ BaseItem.loadClass('Resource', Resource);
BaseItem.loadClass('Tag', Tag); BaseItem.loadClass('Tag', Tag);
BaseItem.loadClass('NoteTag', NoteTag); BaseItem.loadClass('NoteTag', NoteTag);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appType', 'cli');
function sleep(n) { function sleep(n) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
@@ -99,7 +102,7 @@ async function setupDatabaseAndSynchronizer(id = null) {
await setupDatabase(id); await setupDatabase(id);
if (!synchronizers_[id]) { if (!synchronizers_[id]) {
synchronizers_[id] = new Synchronizer(db(id), fileApi()); synchronizers_[id] = new Synchronizer(db(id), fileApi(), Setting.value('appType'));
synchronizers_[id].setLogger(logger); synchronizers_[id].setLogger(logger);
} }

View File

@@ -69,7 +69,11 @@ class Setting extends BaseModel {
} }
static value(key) { static value(key) {
if (key in this.constants_) return this.constants_[key]; if (key in this.constants_) {
let output = this.constants_[key];
if (output == 'SET_ME') throw new Error('Setting constant has not been set: ' + key);
return output;
}
if (!this.cache_) throw new Error('Settings have not been initialized!'); if (!this.cache_) throw new Error('Settings have not been initialized!');
@@ -153,6 +157,7 @@ Setting.defaults_ = {
Setting.constants_ = { Setting.constants_ = {
'appName': 'joplin', 'appName': 'joplin',
'appId': 'SET_ME', // Each app should set this identifier 'appId': 'SET_ME', // Each app should set this identifier
'appType': 'SET_ME', // 'cli' or 'mobile'
'resourceDir': '', 'resourceDir': '',
'profileDir': '', 'profileDir': '',
'tempDir': '', 'tempDir': '',

View File

@@ -4,15 +4,24 @@ import { time } from 'lib/time-utils.js';
class OneDriveApi { class OneDriveApi {
constructor(clientId, clientSecret) { // `isPublic` is to tell OneDrive whether the application is a "public" one (Mobile and desktop
// apps are considered "public"), in which case the secret should not be sent to the API.
// In practice the React Native app is public, and the Node one is not because we
// use a local server for the OAuth dance.
constructor(clientId, clientSecret, isPublic) {
this.clientId_ = clientId; this.clientId_ = clientId;
this.clientSecret_ = clientSecret; this.clientSecret_ = clientSecret;
this.auth_ = null; this.auth_ = null;
this.isPublic_ = isPublic;
this.listeners_ = { this.listeners_ = {
'authRefreshed': [], 'authRefreshed': [],
}; };
} }
isPublic() {
return this.isPublic_;
}
dispatch(eventName, param) { dispatch(eventName, param) {
let ls = this.listeners_[eventName]; let ls = this.listeners_[eventName];
for (let i = 0; i < ls.length; i++) { for (let i = 0; i < ls.length; i++) {
@@ -34,6 +43,7 @@ class OneDriveApi {
setAuth(auth) { setAuth(auth) {
this.auth_ = auth; this.auth_ = auth;
this.dispatch('authRefreshed', this.auth());
} }
token() { token() {
@@ -63,10 +73,10 @@ class OneDriveApi {
return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + stringify(query); return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + stringify(query);
} }
async execTokenRequest(code, redirectUri, isPublic = false) { async execTokenRequest(code, redirectUri) {
let body = new shim.FormData(); let body = new shim.FormData();
body.append('client_id', this.clientId()); body.append('client_id', this.clientId());
if (!isPublic) body.append('client_secret', this.clientSecret()); if (!this.isPublic()) body.append('client_secret', this.clientSecret());
body.append('code', code); body.append('code', code);
body.append('redirect_uri', redirectUri); body.append('redirect_uri', redirectUri);
body.append('grant_type', 'authorization_code'); body.append('grant_type', 'authorization_code');
@@ -84,8 +94,8 @@ class OneDriveApi {
try { try {
const json = await r.json(); const json = await r.json();
this.setAuth(json); this.setAuth(json);
this.dispatch('authRefreshed', this.auth());
} catch (error) { } catch (error) {
this.setAuth(null);
const text = await r.text(); const text = await r.text();
error.message += ': ' + text; error.message += ': ' + text;
throw error; throw error;
@@ -202,7 +212,7 @@ class OneDriveApi {
let body = new shim.FormData(); let body = new shim.FormData();
body.append('client_id', this.clientId()); body.append('client_id', this.clientId());
// body.append('client_secret', this.clientSecret()); // TODO: NEEDED FOR NODE if (!this.isPublic()) body.append('client_secret', this.clientSecret());
body.append('refresh_token', this.auth_.refresh_token); body.append('refresh_token', this.auth_.refresh_token);
body.append('redirect_uri', 'http://localhost:1917'); body.append('redirect_uri', 'http://localhost:1917');
body.append('grant_type', 'refresh_token'); body.append('grant_type', 'refresh_token');
@@ -212,17 +222,15 @@ class OneDriveApi {
body: body, body: body,
}; };
this.auth_ = null;
let response = await shim.fetch(this.tokenBaseUrl(), options); let response = await shim.fetch(this.tokenBaseUrl(), options);
if (!response.ok) { if (!response.ok) {
this.setAuth(null);
let msg = await response.text(); let msg = await response.text();
throw new Error(msg); throw new Error(msg);
} }
this.auth_ = await response.json(); let auth = await response.json();
this.setAuth(auth);
this.dispatch('authRefreshed', this.auth_);
} }
} }

View File

@@ -20,26 +20,31 @@ reg.oneDriveApi = () => {
const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b';
const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5';
reg.oneDriveApi_ = new OneDriveApi(CLIENT_ID, CLIENT_SECRET); const isPublic = Setting.value('appType') != 'cli';
let auth = Setting.value('sync.onedrive.auth'); reg.oneDriveApi_ = new OneDriveApi(CLIENT_ID, CLIENT_SECRET, isPublic);
if (auth) {
auth = JSON.parse(auth);
reg.oneDriveApi_.setAuth(auth);
}
reg.oneDriveApi_.on('authRefreshed', (a) => { reg.oneDriveApi_.on('authRefreshed', (a) => {
reg.logger().info('Saving updated OneDrive auth.'); reg.logger().info('Saving updated OneDrive auth.');
Setting.setValue('sync.onedrive.auth', JSON.stringify(a)); Setting.setValue('sync.onedrive.auth', a ? JSON.stringify(a) : null);
}); });
let auth = Setting.value('sync.onedrive.auth');
if (auth) {
try {
auth = JSON.parse(auth);
} catch (error) {
reg.logger().warn('Could not parse OneDrive auth token');
reg.logger().warn(error);
auth = null;
}
reg.oneDriveApi_.setAuth(auth);
}
return reg.oneDriveApi_; return reg.oneDriveApi_;
} }
reg.setFileApi = (v) => {
reg.fileApi_ = v;
}
reg.fileApi = async () => { reg.fileApi = async () => {
if (reg.fileApi_) return reg.fileApi_; if (reg.fileApi_) return reg.fileApi_;
@@ -58,7 +63,7 @@ reg.synchronizer = async () => {
if (!reg.db()) throw new Error('Cannot initialize synchronizer: db not initialized'); if (!reg.db()) throw new Error('Cannot initialize synchronizer: db not initialized');
let fileApi = await reg.fileApi(); let fileApi = await reg.fileApi();
reg.synchronizer_ = new Synchronizer(reg.db(), fileApi); reg.synchronizer_ = new Synchronizer(reg.db(), fileApi, Setting.value('appType'));
reg.synchronizer_.setLogger(reg.logger()); reg.synchronizer_.setLogger(reg.logger());
return reg.synchronizer_; return reg.synchronizer_;
} }

View File

@@ -10,13 +10,14 @@ import moment from 'moment';
class Synchronizer { class Synchronizer {
constructor(db, api) { constructor(db, api, appType) {
this.state_ = 'idle'; this.state_ = 'idle';
this.db_ = db; this.db_ = db;
this.api_ = api; this.api_ = api;
this.syncDirName_ = '.sync'; this.syncDirName_ = '.sync';
this.resourceDirName_ = '.resource'; this.resourceDirName_ = '.resource';
this.logger_ = new Logger(); this.logger_ = new Logger();
this.appType_ = appType;
} }
state() { state() {
@@ -313,17 +314,17 @@ class Synchronizer {
}; };
if (action == 'createLocal') options.isNew = true; if (action == 'createLocal') options.isNew = true;
// if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') {
// let localResourceContentPath = Resource.fullPath(newContent);
// let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id;
// let remoteResourceContent = await this.api().get(remoteResourceContentPath, { encoding: 'binary' });
// await Resource.setContent(newContent, remoteResourceContent);
// }
if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') {
let localResourceContentPath = Resource.fullPath(newContent); let localResourceContentPath = Resource.fullPath(newContent);
let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id; let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id;
await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' }); if (this.appType_ == 'cli') {
let remoteResourceContent = await this.api().get(remoteResourceContentPath, { encoding: 'binary' });
await Resource.setContent(newContent, remoteResourceContent);
} else if (this.appType_ == 'mobile') {
await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' });
} else {
throw new Error('Unknown appType: ' + this.appType_);
}
} }
await ItemClass.save(newContent, options); await ItemClass.save(newContent, options);

View File

@@ -263,6 +263,7 @@ class AppComponent extends React.Component {
await Setting.load(); await Setting.load();
Setting.setConstant('appId', 'net.cozic.joplin-android'); Setting.setConstant('appId', 'net.cozic.joplin-android');
Setting.setConstant('appType', 'mobile');
Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir); Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir);
Log.info('Loading folders...'); Log.info('Loading folders...');