You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	All: Added "None" sync target to allow disabling synchronisation
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -27,7 +27,9 @@ class ConfigScreenComponent extends BaseScreenComponent { | ||||
| 		return { header: null }; | ||||
| 	} | ||||
|  | ||||
| 	constructor() { | ||||
| 	private componentsY_: Record<string, number> = {}; | ||||
|  | ||||
| 	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 ( | ||||
| 			<View key={key} style={this.styles().headerWrapperStyle}> | ||||
| 			<View key={key} style={this.styles().headerWrapperStyle} onLayout={(event: any) => this.onHeaderLayout(key, event)}> | ||||
| 				<Text style={theme.headerStyle}>{title}</Text> | ||||
| 			</View> | ||||
| 		); | ||||
| @@ -335,7 +368,7 @@ class ConfigScreenComponent extends BaseScreenComponent { | ||||
| 		if (!settingComps.length) return null; | ||||
|  | ||||
| 		return ( | ||||
| 			<View key={key}> | ||||
| 			<View key={key} onLayout={(event: any) => this.onSectionLayout(key, event)}> | ||||
| 				{this.renderHeader(section.name, Setting.sectionNameToLabel(section.name))} | ||||
| 				<View>{settingComps}</View> | ||||
| 			</View> | ||||
| @@ -602,7 +635,7 @@ class ConfigScreenComponent extends BaseScreenComponent { | ||||
| 		return ( | ||||
| 			<View style={this.rootStyle(this.props.themeId).root}> | ||||
| 				<ScreenHeader title={_('Configuration')} showSaveButton={true} showSearchButton={false} showSideMenuButton={false} saveButtonDisabled={!this.state.changedSettingKeys.length} onSaveButtonPress={this.saveButton_press} /> | ||||
| 				<ScrollView>{settingComps}</ScrollView> | ||||
| 				<ScrollView ref={this.scrollViewRef_}>{settingComps}</ScrollView> | ||||
| 			</View> | ||||
| 		); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										30
									
								
								packages/lib/SyncTargetNone.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/lib/SyncTargetNone.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<FileApi> { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	protected async initFileApi() { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	protected async initSynchronizer() { | ||||
| 		return null as any; | ||||
| 	} | ||||
| } | ||||
| @@ -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<number, SyncTargetInfo> = {}; | ||||
|  | ||||
| 	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<string, string> = {}; | ||||
| 		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<string, string> = {}; | ||||
| 		// 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; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -319,7 +319,7 @@ class Setting extends BaseModel { | ||||
| 			}, | ||||
|  | ||||
| 			'sync.target': { | ||||
| 				value: SyncTargetRegistry.nameToId('dropbox'), | ||||
| 				value: 0, | ||||
| 				type: SettingItemType.Int, | ||||
| 				isEnum: true, | ||||
| 				public: true, | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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 }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user