Desktop: Add Joplin Cloud sync target

electron_12
Laurent Cozic 2021-06-03 17:12:07 +02:00
parent 770af6a53b
commit 21ea3253db
11 changed files with 151 additions and 33 deletions

View File

@ -821,6 +821,9 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map packages/lib/SyncTargetJoplinServer.js.map

3
.gitignore vendored
View File

@ -807,6 +807,9 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map packages/lib/SyncTargetJoplinServer.js.map

View File

@ -25,6 +25,7 @@ import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtil
import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile'; import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile';
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale'; import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer'; import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform } = require('react-native'); const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform } = require('react-native');
@ -90,6 +91,7 @@ SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetFilesystem); SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetAmazonS3); SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
import FsDriverRN from './utils/fs-driver-rn'; import FsDriverRN from './utils/fs-driver-rn';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker'; import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';

View File

@ -1,4 +1,4 @@
import Setting from './models/Setting'; import Setting, { Env } from './models/Setting';
import Logger, { TargetType, LoggerWrapper } from './Logger'; import Logger, { TargetType, LoggerWrapper } from './Logger';
import shim from './shim'; import shim from './shim';
import BaseService from './services/BaseService'; import BaseService from './services/BaseService';
@ -46,6 +46,7 @@ const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
import MigrationService from './services/MigrationService'; import MigrationService from './services/MigrationService';
import ShareService from './services/share/ShareService'; import ShareService from './services/share/ShareService';
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation'; import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
const { toSystemSlashes } = require('./path-utils'); const { toSystemSlashes } = require('./path-utils');
const { setAutoFreeze } = require('immer'); const { setAutoFreeze } = require('immer');
@ -691,6 +692,7 @@ export default class BaseApplication {
SyncTargetRegistry.addClass(SyncTargetDropbox); SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3); SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
try { try {
await shim.fsDriver().remove(tempDir); await shim.fsDriver().remove(tempDir);
@ -763,6 +765,10 @@ export default class BaseApplication {
setLocale(Setting.value('locale')); setLocale(Setting.value('locale'));
} }
if (Setting.value('env') === Env.Dev) {
Setting.setValue('sync.10.path', 'http://api-joplincloud.local:22300');
}
// For now always disable fuzzy search due to performance issues: // For now always disable fuzzy search due to performance issues:
// https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11 // https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11
// https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23 // https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23

View File

@ -58,12 +58,17 @@ export default class JoplinServerApi {
private async session() { private async session() {
if (this.session_) return this.session_; if (this.session_) return this.session_;
this.session_ = await this.exec('POST', 'api/sessions', null, { try {
email: this.options_.username(), this.session_ = await this.exec('POST', 'api/sessions', null, {
password: this.options_.password(), email: this.options_.username(),
}); password: this.options_.password(),
});
return this.session_; return this.session_;
} catch (error) {
logger.error('Could not acquire session:', error);
throw error;
}
} }
private async sessionId() { private async sessionId() {

View File

@ -0,0 +1,57 @@
import Setting from './models/Setting';
import Synchronizer from './Synchronizer';
import { _ } from './locale.js';
import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api';
import SyncTargetJoplinServer, { initFileApi } from './SyncTargetJoplinServer';
interface FileApiOptions {
path(): string;
username(): string;
password(): string;
}
export default class SyncTargetJoplinCloud extends BaseSyncTarget {
public static id() {
return 10;
}
public static supportsConfigCheck() {
return SyncTargetJoplinServer.supportsConfigCheck();
}
public static targetName() {
return 'joplinCloud';
}
public static label() {
return _('Joplin Cloud');
}
public async isAuthenticated() {
return true;
}
public async fileApi(): Promise<FileApi> {
return super.fileApi();
}
public static async checkConfig(options: FileApiOptions) {
return SyncTargetJoplinServer.checkConfig({
...options,
});
}
protected async initFileApi() {
return initFileApi(this.logger(), {
path: () => Setting.value('sync.10.path'),
username: () => Setting.value('sync.10.username'),
password: () => Setting.value('sync.10.password'),
});
}
protected async initSynchronizer() {
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}
}

View File

@ -5,6 +5,7 @@ import { _ } from './locale.js';
import JoplinServerApi from './JoplinServerApi'; import JoplinServerApi from './JoplinServerApi';
import BaseSyncTarget from './BaseSyncTarget'; import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api'; import { FileApi } from './file-api';
import Logger from './Logger';
interface FileApiOptions { interface FileApiOptions {
path(): string; path(): string;
@ -12,6 +13,28 @@ interface FileApiOptions {
password(): string; password(): string;
} }
export async function newFileApi(id: number, options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(id);
await fileApi.initialize();
return fileApi;
}
export async function initFileApi(logger: Logger, options: FileApiOptions) {
const fileApi = await newFileApi(SyncTargetJoplinServer.id(), options);
fileApi.setLogger(logger);
return fileApi;
}
export default class SyncTargetJoplinServer extends BaseSyncTarget { export default class SyncTargetJoplinServer extends BaseSyncTarget {
public static id() { public static id() {
@ -38,22 +61,6 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
return super.fileApi(); return super.fileApi();
} }
private static async newFileApi_(options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(this.id());
await fileApi.initialize();
return fileApi;
}
public static async checkConfig(options: FileApiOptions) { public static async checkConfig(options: FileApiOptions) {
const output = { const output = {
ok: false, ok: false,
@ -61,7 +68,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
}; };
try { try {
const fileApi = await SyncTargetJoplinServer.newFileApi_(options); const fileApi = await newFileApi(SyncTargetJoplinServer.id(), options);
fileApi.requestRepeatCount_ = 0; fileApi.requestRepeatCount_ = 0;
await fileApi.put('testing.txt', 'testing'); await fileApi.put('testing.txt', 'testing');
@ -78,15 +85,11 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
} }
protected async initFileApi() { protected async initFileApi() {
const fileApi = await SyncTargetJoplinServer.newFileApi_({ return initFileApi(this.logger(), {
path: () => Setting.value('sync.9.path'), path: () => Setting.value('sync.9.path'),
username: () => Setting.value('sync.9.username'), username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'), password: () => Setting.value('sync.9.password'),
}); });
fileApi.setLogger(this.logger());
return fileApi;
} }
protected async initSynchronizer() { protected async initSynchronizer() {

View File

@ -24,7 +24,7 @@ class SyncTargetRegistry {
if (!this.reg_.hasOwnProperty(n)) continue; if (!this.reg_.hasOwnProperty(n)) continue;
if (this.reg_[n].name === name) return this.reg_[n].id; if (this.reg_[n].name === name) return this.reg_[n].id;
} }
throw new Error(`Name not found: ${name}`); throw new Error(`Name not found: ${name}. Was the sync target registered?`);
} }
static idToMetadata(id) { static idToMetadata(id) {

View File

@ -499,6 +499,39 @@ class Setting extends BaseModel {
secure: true, secure: true,
}, },
// Although sync.10.path is essentially a constant, we still define
// it here so that both Joplin Server and Joplin Cloud can be
// handled in the same consistent way. Also having it a setting
// means it can be set to something else for development.
'sync.10.path': {
value: 'https://api.joplincloud.com',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
'sync.10.username': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud email'),
storage: SettingStorage.File,
},
'sync.10.password': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud password'),
secure: true,
},
'sync.5.syncTargets': { value: {}, type: SettingItemType.Object, public: false }, 'sync.5.syncTargets': { value: {}, type: SettingItemType.Object, public: false },
'sync.resourceDownloadMode': { 'sync.resourceDownloadMode': {
@ -525,6 +558,7 @@ class Setting extends BaseModel {
'sync.4.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.4.auth': { value: '', type: SettingItemType.String, public: false },
'sync.7.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.7.auth': { value: '', type: SettingItemType.String, public: false },
'sync.9.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.9.auth': { value: '', type: SettingItemType.String, public: false },
'sync.10.auth': { value: '', type: SettingItemType.String, public: false },
'sync.1.context': { value: '', type: SettingItemType.String, public: false }, 'sync.1.context': { value: '', type: SettingItemType.String, public: false },
'sync.2.context': { value: '', type: SettingItemType.String, public: false }, 'sync.2.context': { value: '', type: SettingItemType.String, public: false },
'sync.3.context': { value: '', type: SettingItemType.String, public: false }, 'sync.3.context': { value: '', type: SettingItemType.String, public: false },
@ -534,6 +568,7 @@ class Setting extends BaseModel {
'sync.7.context': { value: '', type: SettingItemType.String, public: false }, 'sync.7.context': { value: '', type: SettingItemType.String, public: false },
'sync.8.context': { value: '', type: SettingItemType.String, public: false }, 'sync.8.context': { value: '', type: SettingItemType.String, public: false },
'sync.9.context': { value: '', type: SettingItemType.String, public: false }, 'sync.9.context': { value: '', type: SettingItemType.String, public: false },
'sync.10.context': { value: '', type: SettingItemType.String, public: false },
'sync.maxConcurrentConnections': { value: 5, type: SettingItemType.Int, storage: SettingStorage.File, public: true, advanced: true, section: 'sync', label: () => _('Max concurrent connections'), minimum: 1, maximum: 20, step: 1 }, 'sync.maxConcurrentConnections': { value: 5, type: SettingItemType.Int, storage: SettingStorage.File, public: true, advanced: true, section: 'sync', label: () => _('Max concurrent connections'), minimum: 1, maximum: 20, step: 1 },

View File

@ -22,7 +22,7 @@ export default class ShareService {
} }
public get enabled(): boolean { public get enabled(): boolean {
return Setting.value('sync.target') === 9; // Joplin Server target return [9, 10].includes(Setting.value('sync.target')); // Joplin Server, Joplin Cloud targets
} }
private get store(): Store<any> { private get store(): Store<any> {
@ -36,10 +36,12 @@ export default class ShareService {
private api(): JoplinServerApi { private api(): JoplinServerApi {
if (this.api_) return this.api_; if (this.api_) return this.api_;
const syncTargetId = Setting.value('sync.target');
this.api_ = new JoplinServerApi({ this.api_ = new JoplinServerApi({
baseUrl: () => Setting.value('sync.9.path'), baseUrl: () => Setting.value(`sync.${syncTargetId}.path`),
username: () => Setting.value('sync.9.username'), username: () => Setting.value(`sync.${syncTargetId}.username`),
password: () => Setting.value('sync.9.password'), password: () => Setting.value(`sync.${syncTargetId}.password`),
}); });
return this.api_; return this.api_;

View File

@ -51,6 +51,7 @@ const DropboxApi = require('../DropboxApi');
import JoplinServerApi from '../JoplinServerApi'; import JoplinServerApi from '../JoplinServerApi';
import { FolderEntity } from '../services/database/types'; import { FolderEntity } from '../services/database/types';
import { credentialFile } from '../utils/credentialFiles'; import { credentialFile } from '../utils/credentialFiles';
import SyncTargetJoplinCloud from '../SyncTargetJoplinCloud';
const { loadKeychainServiceAndSettings } = require('../services/SettingUtils'); const { loadKeychainServiceAndSettings } = require('../services/SettingUtils');
const md5 = require('md5'); const md5 = require('md5');
const S3 = require('aws-sdk/clients/s3'); const S3 = require('aws-sdk/clients/s3');
@ -112,6 +113,7 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetDropbox); SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3); SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
let syncTargetName_ = ''; let syncTargetName_ = '';
let syncTargetId_: number = null; let syncTargetId_: number = null;