VulpesOS/gaia/apps/email/js/ext/activesync/jobs.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

472 lines
14 KiB
JavaScript
Executable file

define(
[
'logic',
'mix',
'../jobmixins',
'../drafts/jobs',
'activesync/codepages/AirSync',
'activesync/codepages/Email',
'activesync/codepages/Move',
'module',
'require',
'exports'
],
function(
logic,
mix,
$jobmixins,
draftsJobs,
$AirSync,
$Email,
$Move,
$module,
require,
exports
) {
'use strict';
var $wbxml;
function lazyConnection(cbIndex, fn, failString) {
return function lazyRun() {
var args = Array.slice(arguments),
errback = args[cbIndex],
self = this;
require(['wbxml'], function (wbxml) {
if (!$wbxml) {
$wbxml = wbxml;
}
self.account.withConnection(errback, function () {
fn.apply(self, args);
}, failString);
});
};
}
function ActiveSyncJobDriver(account, state) {
this.account = account;
// XXX for simplicity for now, let's assume that ActiveSync GUID's are
// maintained across folder moves.
this.resilientServerIds = true;
this._heldMutexReleasers = [];
logic.defineScope(this, 'ActiveSyncJobDriver',
{ accountId: this.account.id });
this._state = state;
// (we only need to use one as a proxy for initialization)
if (!state.hasOwnProperty('suidToServerId')) {
state.suidToServerId = {};
state.moveMap = {};
}
this._stateDelta = {
serverIdMap: null,
moveMap: null,
};
}
exports.ActiveSyncJobDriver = ActiveSyncJobDriver;
ActiveSyncJobDriver.prototype = {
//////////////////////////////////////////////////////////////////////////////
// helpers
postJobCleanup: $jobmixins.postJobCleanup,
allJobsDone: $jobmixins.allJobsDone,
_accessFolderForMutation: function(folderId, needConn, callback, deathback,
label) {
var storage = this.account.getFolderStorageForFolderId(folderId),
self = this;
storage.runMutexed(label, function(releaseMutex) {
self._heldMutexReleasers.push(releaseMutex);
var syncer = storage.folderSyncer;
if (needConn) {
self.account.withConnection(callback, function () {
try {
callback(syncer.folderConn, storage);
}
catch (ex) {
logic(self, 'callbackErr', { ex: ex });
}
});
} else {
try {
callback(syncer.folderConn, storage);
}
catch (ex) {
logic(self, 'callbackErr', { ex: ex });
}
}
});
},
_partitionAndAccessFoldersSequentially:
$jobmixins._partitionAndAccessFoldersSequentially,
//////////////////////////////////////////////////////////////////////////////
// modtags
local_do_modtags: $jobmixins.local_do_modtags,
do_modtags: lazyConnection(1, function(op, jobDoneCallback, undo) {
// Note: this method is derived from the IMAP implementation.
var addTags = undo ? op.removeTags : op.addTags,
removeTags = undo ? op.addTags : op.removeTags;
function getMark(tag) {
if (addTags && addTags.indexOf(tag) !== -1)
return true;
if (removeTags && removeTags.indexOf(tag) !== -1)
return false;
return undefined;
}
var markRead = getMark('\\Seen');
var markFlagged = getMark('\\Flagged');
var as = $AirSync.Tags;
var em = $Email.Tags;
var aggrErr = null;
this._partitionAndAccessFoldersSequentially(
op.messages, true,
function perFolder(folderConn, storage, serverIds, namers, callWhenDone) {
var modsToGo = 0;
function tagsModded(err) {
if (err) {
console.error('failure modifying tags', err);
aggrErr = 'unknown';
return;
}
op.progress += (undo ? -serverIds.length : serverIds.length);
if (--modsToGo === 0)
callWhenDone();
}
// Filter out any offline headers, since the server naturally can't do
// anything for them. If this means we have no headers at all, just bail
// out.
serverIds = serverIds.filter(function(srvid) { return !!srvid; });
if (!serverIds.length) {
callWhenDone();
return;
}
folderConn.performMutation(
function withWriter(w) {
for (var i = 0; i < serverIds.length; i++) {
w.stag(as.Change)
.tag(as.ServerId, serverIds[i])
.stag(as.ApplicationData);
if (markRead !== undefined)
w.tag(em.Read, markRead ? '1' : '0');
if (markFlagged !== undefined)
w.stag(em.Flag)
.tag(em.Status, markFlagged ? '2' : '0')
.etag();
w.etag(as.ApplicationData)
.etag(as.Change);
}
},
function mutationPerformed(err) {
if (err)
aggrErr = err;
callWhenDone();
});
},
function allDone() {
jobDoneCallback(aggrErr);
},
function deadConn() {
aggrErr = 'aborted-retry';
},
/* reverse if we're undoing */ undo,
'modtags');
}),
check_modtags: function(op, callback) {
callback(null, 'idempotent');
},
local_undo_modtags: $jobmixins.local_undo_modtags,
undo_modtags: function(op, callback) {
this.do_modtags(op, callback, true);
},
//////////////////////////////////////////////////////////////////////////////
// move
local_do_move: $jobmixins.local_do_move,
do_move: lazyConnection(1, function(op, jobDoneCallback) {
/*
* The ActiveSync command for this does not produce or consume SyncKeys.
* As such, we don't need to acquire mutexes for the source folders for
* synchronization correctness, although it is helpful for ordering
* purposes and reducing confusion.
*
* For the target folder a similar logic exists as long as the server-issued
* GUID's are resilient against folder moves. However, we do require in
* all cases that before synchronizing the target folder that we make sure
* all move operations to the folder have completed so we message doesn't
* disappear and then show up again. XXX we are not currently enforcing this
* yet.
*/
var aggrErr = null, account = this.account,
targetFolderStorage = this.account.getFolderStorageForFolderId(
op.targetFolder);
var mo = $Move.Tags;
this._partitionAndAccessFoldersSequentially(
op.messages, true,
function perFolder(folderConn, storage, serverIds, namers, callWhenDone) {
// Filter out any offline headers, since the server naturally can't do
// anything for them. If this means we have no headers at all, just bail
// out.
serverIds = serverIds.filter(function(srvid) { return !!srvid; });
if (!serverIds.length) {
callWhenDone();
return;
}
// Filter out any offline headers, since the server naturally can't do
// anything for them. If this means we have no headers at all, just bail
// out.
serverIds = serverIds.filter(function(srvid) { return !!srvid; });
if (!serverIds.length) {
callWhenDone();
return;
}
var w = new $wbxml.Writer('1.3', 1, 'UTF-8');
w.stag(mo.MoveItems);
for (var i = 0; i < serverIds.length; i++) {
w.stag(mo.Move)
.tag(mo.SrcMsgId, serverIds[i])
.tag(mo.SrcFldId, storage.folderMeta.serverId)
.tag(mo.DstFldId, targetFolderStorage.folderMeta.serverId)
.etag(mo.Move);
}
w.etag(mo.MoveItems);
account.conn.postCommand(w, function(err, response) {
if (err) {
aggrErr = err;
console.error('failure moving messages:', err);
}
callWhenDone();
});
},
function allDone() {
jobDoneCallback(aggrErr, null, true);
},
function deadConn() {
aggrErr = 'aborted-retry';
},
false,
'move');
}),
check_move: function(op, jobDoneCallback) {
},
local_undo_move: $jobmixins.local_undo_move,
undo_move: function(op, jobDoneCallback) {
},
//////////////////////////////////////////////////////////////////////////////
// delete
local_do_delete: $jobmixins.local_do_delete,
do_delete: lazyConnection(1, function(op, jobDoneCallback) {
var aggrErr = null;
var as = $AirSync.Tags;
var em = $Email.Tags;
this._partitionAndAccessFoldersSequentially(
op.messages, true,
function perFolder(folderConn, storage, serverIds, namers, callWhenDone) {
// Filter out any offline headers, since the server naturally can't do
// anything for them. If this means we have no headers at all, just bail
// out.
serverIds = serverIds.filter(function(srvid) { return !!srvid; });
if (!serverIds.length) {
callWhenDone();
return;
}
folderConn.performMutation(
function withWriter(w) {
for (var i = 0; i < serverIds.length; i++) {
w.stag(as.Delete)
.tag(as.ServerId, serverIds[i])
.etag(as.Delete);
}
},
function mutationPerformed(err) {
if (err) {
aggrErr = err;
console.error('failure deleting messages:', err);
}
callWhenDone();
});
},
function allDone() {
jobDoneCallback(aggrErr, null, true);
},
function deadConn() {
aggrErr = 'aborted-retry';
},
false,
'delete');
}),
check_delete: function(op, callback) {
callback(null, 'idempotent');
},
local_undo_delete: $jobmixins.local_undo_delete,
// TODO implement
undo_delete: function(op, callback) {
callback('moot');
},
//////////////////////////////////////////////////////////////////////////////
// syncFolderList
//
// Synchronize our folder list. This should always be an idempotent operation
// that makes no sense to undo/redo/etc.
local_do_syncFolderList: function(op, doneCallback) {
doneCallback(null);
},
do_syncFolderList: lazyConnection(1, function(op, doneCallback) {
var account = this.account, self = this;
// The inbox needs to be resynchronized if there was no server id and we
// have active slices displaying the contents of the folder. (No server id
// means the sync will not happen.)
var inboxFolder = account.getFirstFolderWithType('inbox'),
inboxStorage;
if (inboxFolder && inboxFolder.serverId === null)
inboxStorage = account.getFolderStorageForFolderId(inboxFolder.id);
account.syncFolderList(function(err) {
if (!err)
account.meta.lastFolderSyncAt = Date.now();
// save if it worked
doneCallback(err ? 'aborted-retry' : null, null, !err);
if (inboxStorage && inboxStorage.hasActiveSlices) {
if (!err) {
console.log("Refreshing fake inbox");
inboxStorage.resetAndRefreshActiveSlices();
}
// XXX: If we do have an error here, we should probably report
// syncfailed on the slices to let the user retry. However, what needs
// retrying is syncFolderList, not syncing the messages in a folder.
// Since that's complicated to handle, and syncFolderList will retry
// automatically, we can ignore that case for now.
}
});
}, 'aborted-retry'),
check_syncFolderList: function(op, doneCallback) {
doneCallback(null, 'coherent-notyet');
},
local_undo_syncFolderList: function(op, doneCallback) {
doneCallback('moot');
},
undo_syncFolderList: function(op, doneCallback) {
doneCallback('moot');
},
//////////////////////////////////////////////////////////////////////////////
// downloadBodies: Download the bodies from a list of messages
local_do_downloadBodies: $jobmixins.local_do_downloadBodies,
do_downloadBodies: $jobmixins.do_downloadBodies,
check_downloadBodies: $jobmixins.check_downloadBodies,
//////////////////////////////////////////////////////////////////////////////
// downloadBodyReps: Download the bodies from a single message
local_do_downloadBodyReps: $jobmixins.local_do_downloadBodyReps,
do_downloadBodyReps: $jobmixins.do_downloadBodyReps,
check_downloadBodyReps: $jobmixins.check_downloadBodyReps,
//////////////////////////////////////////////////////////////////////////////
// download: Download one or more attachments from a single message
local_do_download: $jobmixins.local_do_download,
do_download: $jobmixins.do_download,
check_download: $jobmixins.check_download,
local_undo_download: $jobmixins.local_undo_download,
undo_download: $jobmixins.undo_download,
//////////////////////////////////////////////////////////////////////////////
local_do_sendOutboxMessages: $jobmixins.local_do_sendOutboxMessages,
do_sendOutboxMessages: $jobmixins.do_sendOutboxMessages,
check_sendOutboxMessages: $jobmixins.check_sendOutboxMessages,
local_undo_sendOutboxMessages: $jobmixins.local_undo_sendOutboxMessages,
undo_sendOutboxMessages: $jobmixins.undo_sendOutboxMessages,
local_do_setOutboxSyncEnabled: $jobmixins.local_do_setOutboxSyncEnabled,
// upgrade: perfom necessary upgrades when the db version changes
local_do_upgradeDB: $jobmixins.local_do_upgradeDB,
//////////////////////////////////////////////////////////////////////////////
// purgeExcessMessages is a NOP for activesync
local_do_purgeExcessMessages: function(op, doneCallback) {
doneCallback(null);
},
do_purgeExcessMessages: function(op, doneCallback) {
doneCallback(null);
},
check_purgeExcessMessages: function(op, doneCallback) {
return 'idempotent';
},
local_undo_purgeExcessMessages: function(op, doneCallback) {
doneCallback(null);
},
undo_purgeExcessMessages: function(op, doneCallback) {
doneCallback(null);
},
//////////////////////////////////////////////////////////////////////////////
};
mix(ActiveSyncJobDriver.prototype, draftsJobs.draftsMixins);
}); // end define