1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +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:
Laurent Cozic 2019-10-11 01:03:23 +02:00
parent 8a097fb79c
commit c98644b72f
3 changed files with 1264 additions and 1189 deletions

File diff suppressed because it is too large Load Diff

View File

@ -131,7 +131,7 @@ class FileApi {
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) {
let temp = [];

View File

@ -12,6 +12,7 @@ const { time } = require('lib/time-utils.js');
const { Logger } = require('lib/logger.js');
const { _ } = require('lib/locale.js');
const { shim } = require('lib/shim.js');
const { filename } = require('lib/path-utils');
const JoplinError = require('lib/JoplinError');
const BaseSyncTarget = require('lib/BaseSyncTarget');
const TaskQueue = require('lib/TaskQueue');
@ -22,6 +23,7 @@ class Synchronizer {
this.db_ = db;
this.api_ = api;
this.syncDirName_ = '.sync';
this.lockDirName_ = '.lock';
this.resourceDirName_ = BaseSyncTarget.resourceDirName();
this.logger_ = new Logger();
this.appType_ = appType;
@ -29,6 +31,7 @@ class Synchronizer {
this.autoStartDecryptionWorker_ = true;
this.maxResourceSize_ = null;
this.downloadQueue_ = null;
this.clientId_ = Setting.value('clientId');
// Debug flags are used to test certain hard-to-test conditions
// such as cancelling in the middle of a loop.
@ -52,6 +55,10 @@ class Synchronizer {
return this.api_;
}
clientId() {
return this.clientId_;
}
setLogger(l) {
this.logger_ = l;
}
@ -190,6 +197,66 @@ class Synchronizer {
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:
//
// 1. UPLOAD: Send to the sync target the items that have changed since the last sync.
@ -244,22 +311,12 @@ class Synchronizer {
try {
await this.api().mkdir(this.syncDirName_);
await this.api().mkdir(this.lockDirName_);
this.api().setTempDirName(this.syncDirName_);
await this.api().mkdir(this.resourceDirName_);
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
}
}
await this.checkLock_();
await this.checkSyncTargetVersion_();
// ========================================================================
// 1. UPLOAD