mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Refactoring and handle conflicts from edit watcher
This commit is contained in:
parent
36776cd615
commit
bdbf2fe583
@ -1,5 +1,6 @@
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const ItemChange = require('lib/models/ItemChange.js');
|
||||
const NoteResource = require('lib/models/NoteResource.js');
|
||||
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
@ -327,7 +328,7 @@ class Resource extends BaseItem {
|
||||
const fileStat = await this.fsDriver().stat(newBlobFilePath);
|
||||
await this.fsDriver().copy(newBlobFilePath, Resource.fullPath(resource));
|
||||
|
||||
await Resource.save({
|
||||
return await Resource.save({
|
||||
id: resource.id,
|
||||
size: fileStat.size,
|
||||
});
|
||||
@ -339,6 +340,40 @@ class Resource extends BaseItem {
|
||||
return await this.fsDriver().readFile(Resource.fullPath(resource), encoding);
|
||||
}
|
||||
|
||||
static async duplicateResource(resourceId) {
|
||||
const resource = await Resource.load(resourceId);
|
||||
const localState = await Resource.localState(resource);
|
||||
|
||||
let newResource = { ...resource };
|
||||
delete newResource.id;
|
||||
newResource = await Resource.save(newResource);
|
||||
|
||||
const newLocalState = { ...localState };
|
||||
newLocalState.resource_id = newResource.id;
|
||||
delete newLocalState.id;
|
||||
|
||||
await Resource.setLocalState(newResource, newLocalState);
|
||||
|
||||
const sourcePath = Resource.fullPath(resource);
|
||||
if (await this.fsDriver().exists(sourcePath)) {
|
||||
await this.fsDriver().copy(sourcePath, Resource.fullPath(newResource));
|
||||
}
|
||||
|
||||
return newResource;
|
||||
}
|
||||
|
||||
static async createConflictResourceNote(resource) {
|
||||
const Note = this.getClass('Note');
|
||||
|
||||
const conflictResource = await Resource.duplicateResource(resource.id);
|
||||
|
||||
await Note.save({
|
||||
title: _('Attachment conflict: "%s"', resource.title),
|
||||
body: _('There was a [conflict](%s) on the attachment below.\n\n%s', 'https://joplinapp.org/conflict', Resource.markdownTag(conflictResource)),
|
||||
is_conflict: 1,
|
||||
}, { changeSource: ItemChange.SOURCE_SYNC });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Resource.IMAGE_MAX_DIMENSION = 1920;
|
||||
|
@ -10,7 +10,8 @@ import AsyncActionQueue from '../AsyncActionQueue';
|
||||
|
||||
interface WatchedItem {
|
||||
resourceId: string,
|
||||
updatedTime: number,
|
||||
lastFileUpdatedTime: number,
|
||||
lastResourceUpdatedTime: number,
|
||||
path:string,
|
||||
asyncSaveQueue: AsyncActionQueue,
|
||||
}
|
||||
@ -77,7 +78,19 @@ export default class ResourceEditWatcher {
|
||||
const makeSaveAction = (resourceId:string, path:string) => {
|
||||
return async () => {
|
||||
this.logger().info(`ResourceEditWatcher: Saving resource ${resourceId}`);
|
||||
await Resource.updateResourceBlob(resourceId, path);
|
||||
const resource = await Resource.load(resourceId);
|
||||
const watchedItem = this.watchedItemByResourceId(resourceId);
|
||||
|
||||
if (resource.updated_time !== watchedItem.lastResourceUpdatedTime) {
|
||||
this.logger().info(`ResourceEditWatcher: Conflict was detected (resource was modified from somewhere else, possibly via sync). Conflict note will be created: ${resourceId}`);
|
||||
// The resource has been modified from elsewhere, for example via sync
|
||||
// so copy the current version to the Conflict notebook, and overwrite
|
||||
// the resource content.
|
||||
await Resource.createConflictResourceNote(resource);
|
||||
}
|
||||
|
||||
const savedResource = await Resource.updateResourceBlobContent(resourceId, path);
|
||||
watchedItem.lastResourceUpdatedTime = savedResource.updated_time;
|
||||
this.eventEmitter_.emit('resourceChange', { id: resourceId });
|
||||
};
|
||||
};
|
||||
@ -104,9 +117,9 @@ export default class ResourceEditWatcher {
|
||||
}
|
||||
|
||||
const stat = await shim.fsDriver().stat(path);
|
||||
const updatedTime = stat.mtime.getTime();
|
||||
const editedFileUpdatedTime = stat.mtime.getTime();
|
||||
|
||||
if (watchedItem.updatedTime === updatedTime) {
|
||||
if (watchedItem.lastFileUpdatedTime === editedFileUpdatedTime) {
|
||||
// chokidar is buggy and emits "change" events even when nothing has changed
|
||||
// so double-check the modified time and skip processing if there's no change.
|
||||
// In particular it emits two such events just after the file has been copied
|
||||
@ -118,7 +131,7 @@ export default class ResourceEditWatcher {
|
||||
this.logger().debug(`ResourceEditWatcher: Queuing save action: ${resourceId}`);
|
||||
|
||||
watchedItem.asyncSaveQueue.push(makeSaveAction(resourceId, path));
|
||||
watchedItem.updatedTime = updatedTime;
|
||||
watchedItem.lastFileUpdatedTime = editedFileUpdatedTime;
|
||||
} else if (event === 'error') {
|
||||
this.logger().error('ResourceEditWatcher: error');
|
||||
}
|
||||
@ -147,7 +160,8 @@ export default class ResourceEditWatcher {
|
||||
|
||||
watchedItem = {
|
||||
resourceId: resourceId,
|
||||
updatedTime: 0,
|
||||
lastFileUpdatedTime: 0,
|
||||
lastResourceUpdatedTime: 0,
|
||||
asyncSaveQueue: new AsyncActionQueue(1000),
|
||||
path: '',
|
||||
};
|
||||
@ -163,7 +177,8 @@ export default class ResourceEditWatcher {
|
||||
const stat = await shim.fsDriver().stat(editFilePath);
|
||||
|
||||
watchedItem.path = editFilePath;
|
||||
watchedItem.updatedTime = stat.mtime;
|
||||
watchedItem.lastFileUpdatedTime = stat.mtime.getTime();
|
||||
watchedItem.lastResourceUpdatedTime = resource.updated_time;
|
||||
|
||||
this.watch(editFilePath);
|
||||
}
|
||||
|
@ -205,28 +205,6 @@ function shimInit() {
|
||||
return Resource.save(resource, { isNew: true });
|
||||
};
|
||||
|
||||
shim.duplicateResource = async function(resourceId) {
|
||||
const resource = await Resource.load(resourceId);
|
||||
const localState = await Resource.localState(resource);
|
||||
|
||||
let newResource = { ...resource };
|
||||
delete newResource.id;
|
||||
newResource = await Resource.save(newResource);
|
||||
|
||||
const newLocalState = { ...localState };
|
||||
newLocalState.resource_id = newResource.id;
|
||||
delete newLocalState.id;
|
||||
|
||||
await Resource.setLocalState(newResource, newLocalState);
|
||||
|
||||
const sourcePath = Resource.fullPath(resource);
|
||||
if (await shim.fsDriver().exists(sourcePath)) {
|
||||
await shim.fsDriver().copy(sourcePath, Resource.fullPath(newResource));
|
||||
}
|
||||
|
||||
return newResource;
|
||||
};
|
||||
|
||||
shim.attachFileToNoteBody = async function(noteBody, filePath, position = null, options = null) {
|
||||
options = Object.assign({}, {
|
||||
createFileURL: false,
|
||||
|
@ -539,19 +539,15 @@ class Synchronizer {
|
||||
// Unlike notes we always handle the conflict for resources
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
const conflictResource = await shim.duplicateResource(local.id);
|
||||
|
||||
await Note.save({
|
||||
title: _('Attachment conflict: "%s"', local.title),
|
||||
body: _('There was a [conflict](%s) on the attachment below.\n\n%s', 'https://joplinapp.org/conflict', Resource.markdownTag(conflictResource)),
|
||||
is_conflict: 1,
|
||||
}, { changeSource: ItemChange.SOURCE_SYNC });
|
||||
await Resource.createConflictResourceNote(local);
|
||||
|
||||
if (remote) {
|
||||
// The local content we have is no longer valid and should be re-downloaded
|
||||
await Resource.setLocalState(local.id, {
|
||||
fetch_status: Resource.FETCH_STATUS_IDLE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (['noteConflict', 'resourceConflict'].includes(action)) {
|
||||
// ------------------------------------------------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user