Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
472 lines
14 KiB
JavaScript
Executable file
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
|