1
0
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:
Laurent Cozic 2020-05-31 17:43:51 +01:00
parent 36776cd615
commit bdbf2fe583
4 changed files with 65 additions and 41 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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,

View File

@ -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)) {
// ------------------------------------------------------------------------------