From f5f05e6cc54ffcd4f8d2a8833f03532fe39bb769 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 16 Aug 2021 16:18:32 +0100 Subject: [PATCH] All: Added "None" sync target to allow disabling synchronisation --- .eslintignore | 3 + .gitignore | 3 + .../components/screens/ConfigScreen.tsx | 41 ++++++++++-- packages/lib/SyncTargetNone.ts | 30 +++++++++ packages/lib/SyncTargetRegistry.ts | 62 +++++++++++++++---- packages/lib/commands/synchronize.ts | 9 +++ .../lib/components/shared/side-menu-shared.js | 15 +++++ packages/lib/models/Setting.ts | 2 +- packages/lib/registry.ts | 11 ++++ packages/lib/services/ResourceFetcher.ts | 9 ++- 10 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 packages/lib/SyncTargetNone.ts diff --git a/.eslintignore b/.eslintignore index f1b205b193..828f8d1f5b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -870,6 +870,9 @@ packages/lib/SyncTargetJoplinCloud.js.map packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js.map +packages/lib/SyncTargetNone.d.ts +packages/lib/SyncTargetNone.js +packages/lib/SyncTargetNone.js.map packages/lib/SyncTargetOneDrive.d.ts packages/lib/SyncTargetOneDrive.js packages/lib/SyncTargetOneDrive.js.map diff --git a/.gitignore b/.gitignore index 0e4abde230..148b8c67a0 100644 --- a/.gitignore +++ b/.gitignore @@ -855,6 +855,9 @@ packages/lib/SyncTargetJoplinCloud.js.map packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js.map +packages/lib/SyncTargetNone.d.ts +packages/lib/SyncTargetNone.js +packages/lib/SyncTargetNone.js.map packages/lib/SyncTargetOneDrive.d.ts packages/lib/SyncTargetOneDrive.js packages/lib/SyncTargetOneDrive.js.map diff --git a/packages/app-mobile/components/screens/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen.tsx index f126cc2d3b..d938e03e64 100644 --- a/packages/app-mobile/components/screens/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen.tsx @@ -27,7 +27,9 @@ class ConfigScreenComponent extends BaseScreenComponent { return { header: null }; } - constructor() { + private componentsY_: Record = {}; + + public constructor() { super(); this.styles_ = {}; @@ -37,6 +39,8 @@ class ConfigScreenComponent extends BaseScreenComponent { profileExportPath: '', }; + this.scrollViewRef_ = React.createRef(); + shared.init(this); this.checkSyncConfig_ = async () => { @@ -264,10 +268,39 @@ class ConfigScreenComponent extends BaseScreenComponent { return this.styles_[themeId]; } + private onHeaderLayout(key: string, event: any) { + const layout = event.nativeEvent.layout; + this.componentsY_[`header_${key}`] = layout.y; + } + + private onSectionLayout(key: string, event: any) { + const layout = event.nativeEvent.layout; + this.componentsY_[`section_${key}`] = layout.y; + } + + private componentY(key: string): number { + if ((`section_${key}`) in this.componentsY_) return this.componentsY_[`section_${key}`]; + if ((`header_${key}`) in this.componentsY_) return this.componentsY_[`header_${key}`]; + console.error(`ConfigScreen: Could not find key to scroll to: ${key}`); + return 0; + } + + public componentDidMount() { + if (this.props.navigation.state.sectionName) { + setTimeout(() => { + this.scrollViewRef_.current.scrollTo({ + x: 0, + y: this.componentY(this.props.navigation.state.sectionName), + animated: true, + }); + }, 200); + } + } + renderHeader(key: string, title: string) { const theme = themeStyle(this.props.themeId); return ( - + this.onHeaderLayout(key, event)}> {title} ); @@ -335,7 +368,7 @@ class ConfigScreenComponent extends BaseScreenComponent { if (!settingComps.length) return null; return ( - + this.onSectionLayout(key, event)}> {this.renderHeader(section.name, Setting.sectionNameToLabel(section.name))} {settingComps} @@ -602,7 +635,7 @@ class ConfigScreenComponent extends BaseScreenComponent { return ( - {settingComps} + {settingComps} ); } diff --git a/packages/lib/SyncTargetNone.ts b/packages/lib/SyncTargetNone.ts new file mode 100644 index 0000000000..2ade2c19f3 --- /dev/null +++ b/packages/lib/SyncTargetNone.ts @@ -0,0 +1,30 @@ +import { _ } from './locale.js'; +import BaseSyncTarget from './BaseSyncTarget'; +import { FileApi } from './file-api'; + +export default class SyncTargetNone extends BaseSyncTarget { + + public static id() { + return 0; + } + + public static targetName() { + return 'none'; + } + + public static label() { + return _('(None)'); + } + + public async fileApi(): Promise { + return null; + } + + protected async initFileApi() { + + } + + protected async initSynchronizer() { + return null as any; + } +} diff --git a/packages/lib/SyncTargetRegistry.ts b/packages/lib/SyncTargetRegistry.ts index 8cb41783ac..2e29685a0a 100644 --- a/packages/lib/SyncTargetRegistry.ts +++ b/packages/lib/SyncTargetRegistry.ts @@ -1,3 +1,5 @@ +import SyncTargetNone from './SyncTargetNone'; + export interface SyncTargetInfo { id: number; name: string; @@ -8,25 +10,47 @@ export interface SyncTargetInfo { classRef: any; } +// const syncTargetOrder = [ +// 'joplinCloud', +// 'dropbox', +// 'onedrive', +// ]; + export default class SyncTargetRegistry { private static reg_: Record = {}; + private static get reg() { + if (!this.reg_[0]) { + this.reg_[0] = { + id: 0, + name: SyncTargetNone.targetName(), + label: SyncTargetNone.label(), + classRef: SyncTargetNone, + description: SyncTargetNone.description(), + supportsSelfHosted: false, + supportsConfigCheck: false, + }; + } + + return this.reg_; + } + public static classById(syncTargetId: number) { - const info = SyncTargetRegistry.reg_[syncTargetId]; + const info = SyncTargetRegistry.reg[syncTargetId]; if (!info) throw new Error(`Invalid id: ${syncTargetId}`); return info.classRef; } public static infoByName(name: string): SyncTargetInfo { - for (const [, info] of Object.entries(this.reg_)) { + for (const [, info] of Object.entries(this.reg)) { if (info.name === name) return info; } throw new Error(`Unknown name: ${name}`); } public static addClass(SyncTargetClass: any) { - this.reg_[SyncTargetClass.id()] = { + this.reg[SyncTargetClass.id()] = { id: SyncTargetClass.id(), name: SyncTargetClass.targetName(), label: SyncTargetClass.label(), @@ -38,21 +62,21 @@ export default class SyncTargetRegistry { } public static allIds() { - return Object.keys(this.reg_); + return Object.keys(this.reg); } public static nameToId(name: string) { - for (const n in this.reg_) { - if (!this.reg_.hasOwnProperty(n)) continue; - if (this.reg_[n].name === name) return this.reg_[n].id; + for (const n in this.reg) { + if (!this.reg.hasOwnProperty(n)) continue; + if (this.reg[n].name === name) return this.reg[n].id; } throw new Error(`Name not found: ${name}. Was the sync target registered?`); } public static idToMetadata(id: number) { - for (const n in this.reg_) { - if (!this.reg_.hasOwnProperty(n)) continue; - if (this.reg_[n].id === id) return this.reg_[n]; + for (const n in this.reg) { + if (!this.reg.hasOwnProperty(n)) continue; + if (this.reg[n].id === id) return this.reg[n]; } throw new Error(`ID not found: ${id}`); } @@ -63,14 +87,26 @@ export default class SyncTargetRegistry { public static idAndLabelPlainObject(os: string) { const output: Record = {}; - for (const n in this.reg_) { - if (!this.reg_.hasOwnProperty(n)) continue; - const info = this.reg_[n]; + for (const n in this.reg) { + if (!this.reg.hasOwnProperty(n)) continue; + const info = this.reg[n]; if (info.classRef.unsupportedPlatforms().indexOf(os) >= 0) { continue; } output[n] = info.label; } + return output; + + // const sorted: Record = {}; + // for (const o of syncTargetOrder) { + // sorted[o] = output[o]; + // } + + // for (const [name, value] of Object.entries(output)) { + // if (!sorted[name]) sorted[name] = value; + // } + + // return sorted; } } diff --git a/packages/lib/commands/synchronize.ts b/packages/lib/commands/synchronize.ts index 4509761d7f..3ebcd70e39 100644 --- a/packages/lib/commands/synchronize.ts +++ b/packages/lib/commands/synchronize.ts @@ -1,6 +1,7 @@ import { utils, CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService'; import { _ } from '../locale'; import { reg } from '../registry'; +import Setting from '../models/Setting'; export const declaration: CommandDeclaration = { name: 'synchronize', @@ -17,6 +18,14 @@ export const runtime = (): CommandRuntime => { const action = syncStarted ? 'cancel' : 'start'; + if (!Setting.value('sync.target')) { + context.dispatch({ + type: 'DIALOG_OPEN', + name: 'syncWizard', + }); + return 'init'; + } + if (!(await reg.syncTarget().isAuthenticated())) { if (reg.syncTarget().authRouteName()) { utils.store.dispatch({ diff --git a/packages/lib/components/shared/side-menu-shared.js b/packages/lib/components/shared/side-menu-shared.js index b5481b46dd..94f8cda69f 100644 --- a/packages/lib/components/shared/side-menu-shared.js +++ b/packages/lib/components/shared/side-menu-shared.js @@ -1,4 +1,5 @@ const Folder = require('../../models/Folder').default; +const Setting = require('../../models/Setting').default; const BaseModel = require('../../BaseModel').default; const shared = {}; @@ -81,6 +82,20 @@ shared.synchronize_press = async function(comp) { const action = comp.props.syncStarted ? 'cancel' : 'start'; + if (!Setting.value('sync.target')) { + comp.props.dispatch({ + type: 'SIDE_MENU_CLOSE', + }); + + comp.props.dispatch({ + type: 'NAV_GO', + routeName: 'Config', + sectionName: 'sync', + }); + + return 'init'; + } + if (!(await reg.syncTarget().isAuthenticated())) { if (reg.syncTarget().authRouteName()) { comp.props.dispatch({ diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 5b784c4978..da51ef9c06 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -319,7 +319,7 @@ class Setting extends BaseModel { }, 'sync.target': { - value: SyncTargetRegistry.nameToId('dropbox'), + value: 0, type: SettingItemType.Int, isEnum: true, public: true, diff --git a/packages/lib/registry.ts b/packages/lib/registry.ts index 09b7b6779b..65ef6e9e99 100644 --- a/packages/lib/registry.ts +++ b/packages/lib/registry.ts @@ -71,6 +71,11 @@ class Registry { // sure it gets synced. So we wait for the current sync operation to // finish (if one is running), then we trigger a sync just after. waitForSyncFinishedThenSync = async () => { + if (!Setting.value('sync.target')) { + this.logger().info('waitForSyncFinishedThenSync - cancelling because no sync target is selected.'); + return; + } + this.waitForReSyncCalls_.push(true); try { const synchronizer = await this.syncTarget().synchronizer(); @@ -119,6 +124,12 @@ class Registry { const syncTargetId = Setting.value('sync.target'); + if (!syncTargetId) { + this.logger().info('Sync cancelled - no sync target is selected.'); + promiseResolve(); + return; + } + if (!(await this.syncTarget(syncTargetId).isAuthenticated())) { this.logger().info('Synchroniser is missing credentials - manual sync required to authenticate.'); promiseResolve(); diff --git a/packages/lib/services/ResourceFetcher.ts b/packages/lib/services/ResourceFetcher.ts index bd19a42dc0..9436ef120d 100644 --- a/packages/lib/services/ResourceFetcher.ts +++ b/packages/lib/services/ResourceFetcher.ts @@ -161,6 +161,13 @@ export default class ResourceFetcher extends BaseService { return; } + const fileApi = await this.fileApi(); + + if (!fileApi) { + this.logger().debug('ResourceFetcher: Disabled because fileApi is not set'); + return; + } + this.fetchingItems_[resourceId] = resource; const localResourceContentPath = Resource.fullPath(resource, !!resource.encryption_blob_encrypted); @@ -168,8 +175,6 @@ export default class ResourceFetcher extends BaseService { await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_STARTED }); - const fileApi = await this.fileApi(); - this.logger().debug(`ResourceFetcher: Downloading resource: ${resource.id}`); this.eventEmitter_.emit('downloadStarted', { id: resource.id });