You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	All: Allow a sync client to lock a sync target, so that migration operations can be performed on it
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -131,7 +131,7 @@ class FileApi { | |||||||
|  |  | ||||||
| 		this.logger().debug(`list ${this.baseDir()}`); | 		this.logger().debug(`list ${this.baseDir()}`); | ||||||
|  |  | ||||||
| 		const result = await tryAndRepeat(() => this.driver_.list(this.baseDir(), options), this.requestRepeatCount()); | 		const result = await tryAndRepeat(() => this.driver_.list(this.fullPath_(path), options), this.requestRepeatCount()); | ||||||
|  |  | ||||||
| 		if (!options.includeHidden) { | 		if (!options.includeHidden) { | ||||||
| 			let temp = []; | 			let temp = []; | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ const { time } = require('lib/time-utils.js'); | |||||||
| const { Logger } = require('lib/logger.js'); | const { Logger } = require('lib/logger.js'); | ||||||
| const { _ } = require('lib/locale.js'); | const { _ } = require('lib/locale.js'); | ||||||
| const { shim } = require('lib/shim.js'); | const { shim } = require('lib/shim.js'); | ||||||
|  | const { filename } = require('lib/path-utils'); | ||||||
| const JoplinError = require('lib/JoplinError'); | const JoplinError = require('lib/JoplinError'); | ||||||
| const BaseSyncTarget = require('lib/BaseSyncTarget'); | const BaseSyncTarget = require('lib/BaseSyncTarget'); | ||||||
| const TaskQueue = require('lib/TaskQueue'); | const TaskQueue = require('lib/TaskQueue'); | ||||||
| @@ -22,6 +23,7 @@ class Synchronizer { | |||||||
| 		this.db_ = db; | 		this.db_ = db; | ||||||
| 		this.api_ = api; | 		this.api_ = api; | ||||||
| 		this.syncDirName_ = '.sync'; | 		this.syncDirName_ = '.sync'; | ||||||
|  | 		this.lockDirName_ = '.lock'; | ||||||
| 		this.resourceDirName_ = BaseSyncTarget.resourceDirName(); | 		this.resourceDirName_ = BaseSyncTarget.resourceDirName(); | ||||||
| 		this.logger_ = new Logger(); | 		this.logger_ = new Logger(); | ||||||
| 		this.appType_ = appType; | 		this.appType_ = appType; | ||||||
| @@ -29,6 +31,7 @@ class Synchronizer { | |||||||
| 		this.autoStartDecryptionWorker_ = true; | 		this.autoStartDecryptionWorker_ = true; | ||||||
| 		this.maxResourceSize_ = null; | 		this.maxResourceSize_ = null; | ||||||
| 		this.downloadQueue_ = null; | 		this.downloadQueue_ = null; | ||||||
|  | 		this.clientId_ = Setting.value('clientId'); | ||||||
|  |  | ||||||
| 		// Debug flags are used to test certain hard-to-test conditions | 		// Debug flags are used to test certain hard-to-test conditions | ||||||
| 		// such as cancelling in the middle of a loop. | 		// such as cancelling in the middle of a loop. | ||||||
| @@ -52,6 +55,10 @@ class Synchronizer { | |||||||
| 		return this.api_; | 		return this.api_; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	clientId() { | ||||||
|  | 		return this.clientId_; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	setLogger(l) { | 	setLogger(l) { | ||||||
| 		this.logger_ = l; | 		this.logger_ = l; | ||||||
| 	} | 	} | ||||||
| @@ -190,6 +197,66 @@ class Synchronizer { | |||||||
| 		return state; | 		return state; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	async acquireLock_() { | ||||||
|  | 		await this.checkLock_(); | ||||||
|  | 		await this.api().put(`${this.lockDirName_}/${this.clientId()}_${Date.now()}.lock`, `${Date.now()}`); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	async releaseLock_() { | ||||||
|  | 		const lockFiles = await this.lockFiles_(); | ||||||
|  | 		for (const lockFile of lockFiles) { | ||||||
|  | 			const p = this.parseLockFilePath(lockFile.path); | ||||||
|  | 			if (p.clientId === this.clientId()) { | ||||||
|  | 				await this.api().delete(p.fullPath); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	async lockFiles_() { | ||||||
|  | 		const output = await this.api().list(this.lockDirName_); | ||||||
|  | 		return output.items; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	parseLockFilePath(path) { | ||||||
|  | 		const splitted = filename(path).split('_'); | ||||||
|  | 		const fullPath = `${this.lockDirName_}/${path}`; | ||||||
|  | 		if (splitted.length !== 2) throw new Error(`Sync target appears to be locked but lock filename is invalid: ${fullPath}. Please delete it on the sync target to continue.`); | ||||||
|  | 		return { | ||||||
|  | 			clientId: splitted[0], | ||||||
|  | 			timestamp: Number(splitted[1]), | ||||||
|  | 			fullPath: fullPath, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	async checkLock_() { | ||||||
|  | 		const lockFiles = await this.lockFiles_(); | ||||||
|  | 		if (lockFiles.length) { | ||||||
|  | 			const lock = this.parseLockFilePath(lockFiles[0].path); | ||||||
|  |  | ||||||
|  | 			if (lock.clientId === this.clientId()) { | ||||||
|  | 				await this.releaseLock_(); | ||||||
|  | 			} else { | ||||||
|  | 				throw new Error(`The sync target was locked by client ${lock.clientId} on ${time.unixMsToLocalDateTime(lock.timestamp)} and cannot be accessed. If no app is currently operating on the sync target, you can delete the files in the "${this.lockDirName_}" directory on the sync target to resume.`); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	async checkSyncTargetVersion_() { | ||||||
|  | 		const supportedSyncTargetVersion = Setting.value('syncVersion'); | ||||||
|  | 		const syncTargetVersion = await this.api().get('.sync/version.txt'); | ||||||
|  |  | ||||||
|  | 		if (!syncTargetVersion) { | ||||||
|  | 			await this.api().put('.sync/version.txt', `${supportedSyncTargetVersion}`); | ||||||
|  | 		} else { | ||||||
|  | 			if (Number(syncTargetVersion) > supportedSyncTargetVersion) { | ||||||
|  | 				throw new Error(sprintf('Sync version of the target (%d) does not match sync version supported by client (%d). Please upgrade your client.', Number(syncTargetVersion), supportedSyncTargetVersion)); | ||||||
|  | 			} else { | ||||||
|  | 				await this.api().put('.sync/version.txt', `${supportedSyncTargetVersion}`); | ||||||
|  | 				// TODO: do upgrade job | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Synchronisation is done in three major steps: | 	// Synchronisation is done in three major steps: | ||||||
| 	// | 	// | ||||||
| 	// 1. UPLOAD: Send to the sync target the items that have changed since the last sync. | 	// 1. UPLOAD: Send to the sync target the items that have changed since the last sync. | ||||||
| @@ -244,22 +311,12 @@ class Synchronizer { | |||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			await this.api().mkdir(this.syncDirName_); | 			await this.api().mkdir(this.syncDirName_); | ||||||
|  | 			await this.api().mkdir(this.lockDirName_); | ||||||
| 			this.api().setTempDirName(this.syncDirName_); | 			this.api().setTempDirName(this.syncDirName_); | ||||||
| 			await this.api().mkdir(this.resourceDirName_); | 			await this.api().mkdir(this.resourceDirName_); | ||||||
|  |  | ||||||
| 			const supportedSyncTargetVersion = Setting.value('syncVersion'); | 			await this.checkLock_(); | ||||||
| 			const syncTargetVersion = await this.api().get('.sync/version.txt'); | 			await this.checkSyncTargetVersion_(); | ||||||
|  |  | ||||||
| 			if (!syncTargetVersion) { |  | ||||||
| 				await this.api().put('.sync/version.txt', `${supportedSyncTargetVersion}`); |  | ||||||
| 			} else { |  | ||||||
| 				if (Number(syncTargetVersion) > supportedSyncTargetVersion) { |  | ||||||
| 					throw new Error(sprintf('Sync version of the target (%d) does not match sync version supported by client (%d). Please upgrade your client.', Number(syncTargetVersion), supportedSyncTargetVersion)); |  | ||||||
| 				} else { |  | ||||||
| 					await this.api().put('.sync/version.txt', `${supportedSyncTargetVersion}`); |  | ||||||
| 					// TODO: do upgrade job |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// ======================================================================== | 			// ======================================================================== | ||||||
| 			// 1. UPLOAD | 			// 1. UPLOAD | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user