mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-26 18:58:21 +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:
parent
8a097fb79c
commit
c98644b72f
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user