1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Desktop: Add Joplin Cloud sync target

This commit is contained in:
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.js
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.js
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.js
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.js
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 { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
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(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
import FsDriverRN from './utils/fs-driver-rn';
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 shim from './shim';
import BaseService from './services/BaseService';
@ -46,6 +46,7 @@ const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
import MigrationService from './services/MigrationService';
import ShareService from './services/share/ShareService';
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
const { toSystemSlashes } = require('./path-utils');
const { setAutoFreeze } = require('immer');
@ -691,6 +692,7 @@ export default class BaseApplication {
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
try {
await shim.fsDriver().remove(tempDir);
@ -763,6 +765,10 @@ export default class BaseApplication {
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:
// 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

View File

@ -58,12 +58,17 @@ export default class JoplinServerApi {
private async session() {
if (this.session_) return this.session_;
this.session_ = await this.exec('POST', 'api/sessions', null, {
email: this.options_.username(),
password: this.options_.password(),
});
try {
this.session_ = await this.exec('POST', 'api/sessions', null, {
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() {

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 BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api';
import Logger from './Logger';
interface FileApiOptions {
path(): string;
@ -12,6 +13,28 @@ interface FileApiOptions {
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 {
public static id() {
@ -38,22 +61,6 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
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) {
const output = {
ok: false,
@ -61,7 +68,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
};
try {
const fileApi = await SyncTargetJoplinServer.newFileApi_(options);
const fileApi = await newFileApi(SyncTargetJoplinServer.id(), options);
fileApi.requestRepeatCount_ = 0;
await fileApi.put('testing.txt', 'testing');
@ -78,15 +85,11 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
}
protected async initFileApi() {
const fileApi = await SyncTargetJoplinServer.newFileApi_({
return initFileApi(this.logger(), {
path: () => Setting.value('sync.9.path'),
username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'),
});
fileApi.setLogger(this.logger());
return fileApi;
}
protected async initSynchronizer() {

View File

@ -24,7 +24,7 @@ class SyncTargetRegistry {
if (!this.reg_.hasOwnProperty(n)) continue;
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) {

View File

@ -499,6 +499,39 @@ class Setting extends BaseModel {
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.resourceDownloadMode': {
@ -525,6 +558,7 @@ class Setting extends BaseModel {
'sync.4.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.10.auth': { 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.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.8.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 },

View File

@ -22,7 +22,7 @@ export default class ShareService {
}
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> {
@ -36,10 +36,12 @@ export default class ShareService {
private api(): JoplinServerApi {
if (this.api_) return this.api_;
const syncTargetId = Setting.value('sync.target');
this.api_ = new JoplinServerApi({
baseUrl: () => Setting.value('sync.9.path'),
username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'),
baseUrl: () => Setting.value(`sync.${syncTargetId}.path`),
username: () => Setting.value(`sync.${syncTargetId}.username`),
password: () => Setting.value(`sync.${syncTargetId}.password`),
});
return this.api_;

View File

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