mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-04 03:34:28 +08:00
[local-sync] Ensure order correct order when running syncback requests
When running syncback requests, if a task is meant to change the UID of a message (e.g. move it to a new folder), that task should be run after other tasks that don't affect the UID. Otherwise, when trying to run the other tasks, they would reference a UID that is no longer valid. This commit will make sure that we run any tasks that will change message uids last, /and/ make sure that we don't run more than 1 task that will affect the uids of the same messages in a row (i.e. without running a sync loop in between)
This commit is contained in:
parent
e785d73bdc
commit
fda7fe18cc
23 changed files with 138 additions and 14 deletions
|
@ -115,14 +115,62 @@ class SyncWorker {
|
|||
return await this._conn.connect();
|
||||
}
|
||||
|
||||
async syncbackMessageActions() {
|
||||
const {SyncbackRequest} = this._db;
|
||||
const where = {where: {status: "NEW"}, limit: 100};
|
||||
async runNewSyncbackRequests() {
|
||||
const {SyncbackRequest, Message} = this._db;
|
||||
const where = {
|
||||
limit: 100,
|
||||
where: {status: "NEW"},
|
||||
order: [['createdAt', 'ASC']],
|
||||
};
|
||||
|
||||
// Make sure we run the tasks that affect IMAP uids last, and that we don't
|
||||
// run 2 tasks that will affect the same set of UIDS together (i.e. without
|
||||
// running a sync loop in between)
|
||||
const tasks = await SyncbackRequest.findAll(where)
|
||||
.map((req) => SyncbackTaskFactory.create(this._account, req));
|
||||
.map((req) => SyncbackTaskFactory.create(this._account, req))
|
||||
|
||||
return PromiseUtils.each(tasks, this.runSyncbackTask.bind(this));
|
||||
if (tasks.length === 0) { return Promise.resolve() }
|
||||
|
||||
const tasksToProcess = tasks.filter(t => !t.affectsImapMessageUIDs())
|
||||
const tasksAffectingUIDs = tasks.filter(t => t.affectsImapMessageUIDs())
|
||||
|
||||
const changeFolderTasks = tasksAffectingUIDs.filter(t =>
|
||||
t.description() === 'RenameFolder' || t.description() === 'DeleteFolder'
|
||||
)
|
||||
if (changeFolderTasks.length > 0) {
|
||||
// If we are renaming or deleting folders, those are the only tasks we
|
||||
// want to process before executing any other tasks that may change UIDs
|
||||
const affectedFolderIds = new Set()
|
||||
changeFolderTasks.forEach((task) => {
|
||||
const {props: {folderId}} = task.syncbackRequestObject()
|
||||
if (folderId && !affectedFolderIds.has(folderId)) {
|
||||
tasksToProcess.push(task)
|
||||
affectedFolderIds.add(folderId)
|
||||
}
|
||||
})
|
||||
return PromiseUtils.each(tasks, (task) => this.runSyncbackTask(task));
|
||||
}
|
||||
|
||||
// Otherwise, make sure that we don't process more than 1 task that will affect
|
||||
// the UID of the same message
|
||||
const affectedMessageIds = new Set()
|
||||
for (const task of tasksAffectingUIDs) {
|
||||
const {props: {messageId, threadId}} = task.syncbackRequestObject()
|
||||
if (messageId) {
|
||||
if (!affectedMessageIds.has(messageId)) {
|
||||
tasksToProcess.push(task)
|
||||
affectedMessageIds.add(messageId)
|
||||
}
|
||||
} else if (threadId) {
|
||||
const messageIds = await Message.findAll({where: {threadId}}).map(m => m.id)
|
||||
const shouldIncludeTask = messageIds.every(id => !affectedMessageIds.has(id))
|
||||
if (shouldIncludeTask) {
|
||||
tasksToProcess.push(task)
|
||||
messageIds.forEach(id => affectedMessageIds.add(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return PromiseUtils.each(tasks, (task) => this.runSyncbackTask(task));
|
||||
}
|
||||
|
||||
async runSyncbackTask(task) {
|
||||
|
@ -176,7 +224,7 @@ class SyncWorker {
|
|||
try {
|
||||
await this._account.update({syncError: null});
|
||||
await this.ensureConnection();
|
||||
await this.syncbackMessageActions();
|
||||
await this.runNewSyncbackRequests();
|
||||
await this._conn.runOperation(new FetchFolderList(this._account.provider, this._logger));
|
||||
await this.syncMessagesInAllFolders();
|
||||
await this.onSyncDidComplete();
|
||||
|
|
|
@ -5,14 +5,6 @@
|
|||
*/
|
||||
class SyncbackTaskFactory {
|
||||
|
||||
static TaskTypesAffectingMessageFolderUIDs = [
|
||||
'MoveThreadToFolder',
|
||||
'MoveMessageToFolder',
|
||||
'SetThreadFolderAndLabels',
|
||||
'RenameFolder',
|
||||
'DeleteFolder',
|
||||
]
|
||||
|
||||
static create(account, syncbackRequest) {
|
||||
let Task = null;
|
||||
switch (syncbackRequest.type) {
|
|
@ -5,6 +5,10 @@ class CreateCategoryIMAP extends SyncbackTask {
|
|||
return `CreateCategory`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const syncbackRequestObject = this.syncbackRequestObject()
|
||||
const displayName = syncbackRequestObject.props.displayName
|
||||
|
|
|
@ -5,6 +5,10 @@ class DeleteFolderIMAP extends SyncbackTask {
|
|||
return `DeleteFolder`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return true
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const folderId = this.syncbackRequestObject().props.folderId
|
||||
const folder = await db.Folder.findById(folderId)
|
||||
|
|
|
@ -5,6 +5,10 @@ class DeleteLabelIMAP extends SyncbackTask {
|
|||
return `DeleteLabel`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const labelId = this.syncbackRequestObject().props.labelId
|
||||
const label = await db.Label.findById(labelId)
|
||||
|
|
|
@ -6,6 +6,10 @@ class DeleteMessageIMAP extends SyncbackTask {
|
|||
return `DeleteMessage`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
const {box, message} = await TaskHelpers.openMessageBox({messageId, db, imap})
|
||||
|
|
|
@ -6,6 +6,10 @@ class MarkMessageAsReadIMAP extends SyncbackTask {
|
|||
return `MarkMessageAsRead`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class MarkMessageAsUnreadIMAP extends SyncbackTask {
|
|||
return `MarkMessageAsUnread`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class MarkThreadAsRead extends SyncbackTask {
|
|||
return `MarkThreadAsRead`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class MarkThreadAsUnread extends SyncbackTask {
|
|||
return `MarkThreadAsUnread`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class MoveMessageToFolderIMAP extends SyncbackTask {
|
|||
return `MoveMessageToFolder`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return true
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
const targetFolderId = this.syncbackRequestObject().props.folderId
|
||||
|
|
|
@ -6,6 +6,10 @@ class MoveThreadToFolderIMAP extends SyncbackTask {
|
|||
return `MoveThreadToFolder`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return true
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
const targetFolderId = this.syncbackRequestObject().props.folderId
|
||||
|
|
|
@ -5,6 +5,10 @@ class RenameFolderIMAP extends SyncbackTask {
|
|||
return `RenameFolder`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return true
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const folderId = this.syncbackRequestObject().props.folderId
|
||||
const newFolderName = this.syncbackRequestObject().props.displayName
|
||||
|
|
|
@ -5,6 +5,10 @@ class RenameLabelIMAP extends SyncbackTask {
|
|||
return `RenameLabel`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const labelId = this.syncbackRequestObject().props.labelId
|
||||
const newLabelName = this.syncbackRequestObject().props.displayName
|
||||
|
|
|
@ -5,6 +5,10 @@ class SaveSentMessageIMAP extends SyncbackTask {
|
|||
return `SaveSentMessage`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const {rawMime, messageId} = this.syncbackRequestObject().props;
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class SetMessageLabelsIMAP extends SyncbackTask {
|
|||
return `SetMessageLabels`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
const labelIds = this.syncbackRequestObject().props.labelIds
|
||||
|
|
|
@ -6,6 +6,10 @@ class SetThreadFolderAndLabelsIMAP extends SyncbackTask {
|
|||
return `SetThreadFolderAndLabels`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return true
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
const labelIds = this.syncbackRequestObject().props.labelIds
|
||||
|
|
|
@ -6,6 +6,10 @@ class SetThreadLabelsIMAP extends SyncbackTask {
|
|||
return `SetThreadLabels`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
async run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
const labelIds = this.syncbackRequestObject().props.labelIds
|
||||
|
|
|
@ -6,6 +6,10 @@ class StarMessageIMAP extends SyncbackTask {
|
|||
return `StarMessage`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class StarThread extends SyncbackTask {
|
|||
return `StarThread`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
|
||||
|
|
|
@ -12,6 +12,10 @@ class SyncbackTask {
|
|||
throw new Error("Must return a description")
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
throw new Error("Must implement `affectsImapMessageUIDs`")
|
||||
}
|
||||
|
||||
run() {
|
||||
throw new Error("Must implement a run method")
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ class UnstarMessageIMAP extends SyncbackTask {
|
|||
return `UnstarMessage`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const messageId = this.syncbackRequestObject().props.messageId
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class UnstarThread extends SyncbackTask {
|
|||
return `UnstarThread`;
|
||||
}
|
||||
|
||||
affectsImapMessageUIDs() {
|
||||
return false
|
||||
}
|
||||
|
||||
run(db, imap) {
|
||||
const threadId = this.syncbackRequestObject().props.threadId
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue