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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
1032
gaia/apps/email/js/ext/activesync/account.js
Executable file
1032
gaia/apps/email/js/ext/activesync/account.js
Executable file
File diff suppressed because it is too large
Load diff
322
gaia/apps/email/js/ext/activesync/configurator.js
Executable file
322
gaia/apps/email/js/ext/activesync/configurator.js
Executable file
|
|
@ -0,0 +1,322 @@
|
|||
/**
|
||||
* Configurator for activesync
|
||||
**/
|
||||
|
||||
define(
|
||||
[
|
||||
'logic',
|
||||
'../accountcommon',
|
||||
'../a64',
|
||||
'./account',
|
||||
'../date',
|
||||
'tcp-socket',
|
||||
'require',
|
||||
'exports'
|
||||
],
|
||||
function(
|
||||
logic,
|
||||
$accountcommon,
|
||||
$a64,
|
||||
$asacct,
|
||||
$date,
|
||||
tcpSocket,
|
||||
require,
|
||||
exports
|
||||
) {
|
||||
|
||||
function checkServerCertificate(url, callback) {
|
||||
var match = /^https:\/\/([^:/]+)(?::(\d+))?/.exec(url);
|
||||
// probably unit test http case?
|
||||
if (!match) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
var port = match[2] ? parseInt(match[2], 10) : 443,
|
||||
host = match[1];
|
||||
|
||||
console.log('checking', host, port, 'for security problem');
|
||||
|
||||
var sock = tcpSocket.open(host, port);
|
||||
function reportAndClose(err) {
|
||||
if (sock) {
|
||||
var wasSock = sock;
|
||||
sock = null;
|
||||
try {
|
||||
wasSock.close();
|
||||
}
|
||||
catch (ex) {
|
||||
}
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
// this is a little dumb, but since we don't actually get an event right now
|
||||
// that tells us when our secure connection is established, and connect always
|
||||
// happens, we write data when we connect to help trigger an error or have us
|
||||
// receive data to indicate we successfully connected.
|
||||
// so, the deal is that connect is going to happen.
|
||||
sock.onopen = function() {
|
||||
sock.send(
|
||||
new TextEncoder('utf-8').encode('GET /images/logo.png HTTP/1.1\n\n'));
|
||||
};
|
||||
sock.onerror = function(err) {
|
||||
var reportErr = null;
|
||||
if (err && typeof(err) === 'object' &&
|
||||
/^Security/.test(err.name))
|
||||
reportErr = 'bad-security';
|
||||
reportAndClose(reportErr);
|
||||
};
|
||||
sock.ondata = function(data) {
|
||||
reportAndClose(null);
|
||||
};
|
||||
}
|
||||
|
||||
var scope = logic.scope('ActivesyncConfigurator');
|
||||
|
||||
exports.account = $asacct;
|
||||
exports.configurator = {
|
||||
timeout: 30 * 1000,
|
||||
_getFullDetailsFromAutodiscover: function($asproto, userDetails, url,
|
||||
callback) {
|
||||
logic(scope, 'autodiscover:begin', { url: url });
|
||||
$asproto.raw_autodiscover(
|
||||
url, userDetails.emailAddress, userDetails.password, self.timeout,
|
||||
/* redirects are okay */ false,
|
||||
function(error, config) {
|
||||
if (error) {
|
||||
var failureType = 'no-config-info',
|
||||
failureDetails = {};
|
||||
|
||||
if (error instanceof $asproto.HttpError) {
|
||||
if (error.status === 401)
|
||||
failureType = 'bad-user-or-pass';
|
||||
else if (error.status === 403)
|
||||
failureType = 'not-authorized';
|
||||
else
|
||||
failureDetails.status = error.status;
|
||||
}
|
||||
else if (error instanceof $asproto.AutodiscoverDomainError) {
|
||||
logic(scope, 'autodiscover.error', { message: error.message });
|
||||
}
|
||||
logic(scope, 'autodiscover:end', { url: url, err: failureType });
|
||||
callback(failureType, null, failureDetails);
|
||||
return;
|
||||
}
|
||||
logic(scope, 'autodiscover:end',
|
||||
{ url: url, server: config.mobileSyncServer.url });
|
||||
|
||||
var autoconfig = {
|
||||
type: 'activesync',
|
||||
displayName: config.user.name,
|
||||
incoming: {
|
||||
server: config.mobileSyncServer.url,
|
||||
username: config.user.email
|
||||
},
|
||||
};
|
||||
callback(null, autoconfig, null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* There are 2 scenarios we can get invoked with:
|
||||
* - Direct creation. We already know the ActiveSync endpoint. This happens
|
||||
* from a hardcoded (for testing) or local (hotmail.com/outlook.com)
|
||||
* autoconfig entry OR from a user typing that stuff in manually.
|
||||
*
|
||||
* - Indirection creation. We just know an AutoDiscover endpoing and need
|
||||
* to run AutoDiscover. If our autoconfig process probed and found some
|
||||
* AutoDiscover looking endpoints, that's how we end up here. It's also
|
||||
* conceivable that in the future the manual config mode could use this
|
||||
* path.
|
||||
*/
|
||||
tryToCreateAccount: function(universe, userDetails, domainInfo, callback) {
|
||||
require(['activesync/protocol'], function ($asproto) {
|
||||
if (domainInfo.incoming.autodiscoverEndpoint) {
|
||||
this._getFullDetailsFromAutodiscover(
|
||||
$asproto, userDetails, domainInfo.incoming.autodiscoverEndpoint,
|
||||
function(err, fullConfigInfo, errDetails) {
|
||||
// If we got an error, pass it directly back.
|
||||
if (err) {
|
||||
callback(err, fullConfigInfo, errDetails);
|
||||
return;
|
||||
}
|
||||
// Otherwise we have a config and should continue the creation
|
||||
// process.
|
||||
this._createAccountUsingFullInfo(
|
||||
universe, userDetails, fullConfigInfo, callback, $asproto);
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
// We should have full config info then. Just call direct in.
|
||||
this._createAccountUsingFullInfo(universe, userDetails, domainInfo,
|
||||
callback, $asproto);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_createAccountUsingFullInfo: function(universe, userDetails, domainInfo,
|
||||
callback, $asproto) {
|
||||
logic(scope, 'create:begin', { server: domainInfo.incoming.server });
|
||||
var credentials = {
|
||||
username: domainInfo.incoming.username,
|
||||
password: userDetails.password,
|
||||
};
|
||||
|
||||
var deviceId = $asacct.makeUniqueDeviceId();
|
||||
|
||||
var self = this;
|
||||
var conn = new $asproto.Connection(deviceId);
|
||||
conn.open(domainInfo.incoming.server, credentials.username,
|
||||
credentials.password);
|
||||
conn.timeout = $asacct.DEFAULT_TIMEOUT_MS;
|
||||
|
||||
conn.connect(function(error, options) {
|
||||
if (error) {
|
||||
// This error is basically an indication of whether we were able to
|
||||
// call getOptions or not. If the XHR request completed, we get an
|
||||
// HttpError. If we timed out or an XHR error occurred, we get a
|
||||
// general Error.
|
||||
var failureType,
|
||||
failureDetails = { server: domainInfo.incoming.server };
|
||||
|
||||
if (error instanceof $asproto.HttpError) {
|
||||
if (error.status === 401) {
|
||||
failureType = 'bad-user-or-pass';
|
||||
}
|
||||
else if (error.status === 403) {
|
||||
failureType = 'not-authorized';
|
||||
}
|
||||
// Treat any other errors where we talked to the server as a problem
|
||||
// with the server.
|
||||
else {
|
||||
failureType = 'server-problem';
|
||||
failureDetails.status = error.status;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We didn't talk to the server, so it's either an unresponsive
|
||||
// server or a server with a bad certificate. (We require https
|
||||
// outside of unit tests so there's no need to branch here.)
|
||||
checkServerCertificate(
|
||||
domainInfo.incoming.server,
|
||||
function(securityError) {
|
||||
var failureType;
|
||||
if (securityError)
|
||||
failureType = 'bad-security';
|
||||
else
|
||||
failureType = 'unresponsive-server';
|
||||
callback(failureType, null, failureDetails);
|
||||
});
|
||||
return;
|
||||
}
|
||||
logic(scope, 'create:end', {
|
||||
server: domainInfo.incoming.server,
|
||||
err: failureType
|
||||
});
|
||||
|
||||
callback(failureType, null, failureDetails);
|
||||
return;
|
||||
}
|
||||
|
||||
var accountId = $a64.encodeInt(universe.config.nextAccountNum++);
|
||||
var accountDef = {
|
||||
id: accountId,
|
||||
name: userDetails.accountName || userDetails.emailAddress,
|
||||
defaultPriority: $date.NOW(),
|
||||
|
||||
type: 'activesync',
|
||||
syncRange: 'auto',
|
||||
|
||||
syncInterval: userDetails.syncInterval || 0,
|
||||
notifyOnNew: userDetails.hasOwnProperty('notifyOnNew') ?
|
||||
userDetails.notifyOnNew : true,
|
||||
playSoundOnSend: userDetails.hasOwnProperty('playSoundOnSend') ?
|
||||
userDetails.playSoundOnSend : true,
|
||||
|
||||
credentials: credentials,
|
||||
connInfo: {
|
||||
server: domainInfo.incoming.server,
|
||||
deviceId: deviceId
|
||||
},
|
||||
|
||||
identities: [
|
||||
{
|
||||
id: accountId + '/' +
|
||||
$a64.encodeInt(universe.config.nextIdentityNum++),
|
||||
name: userDetails.displayName || domainInfo.displayName,
|
||||
address: userDetails.emailAddress,
|
||||
replyTo: null,
|
||||
signature: null
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
logic(scope, 'create:end', {
|
||||
server: domainInfo.incoming.server,
|
||||
id: accountId
|
||||
});
|
||||
|
||||
self._loadAccount(universe, accountDef, conn, function (account) {
|
||||
callback(null, account, null);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
recreateAccount: function cfg_as_ra(universe, oldVersion, oldAccountInfo,
|
||||
callback) {
|
||||
var oldAccountDef = oldAccountInfo.def;
|
||||
var credentials = {
|
||||
username: oldAccountDef.credentials.username,
|
||||
password: oldAccountDef.credentials.password,
|
||||
};
|
||||
var accountId = $a64.encodeInt(universe.config.nextAccountNum++);
|
||||
var accountDef = {
|
||||
id: accountId,
|
||||
name: oldAccountDef.name,
|
||||
|
||||
type: 'activesync',
|
||||
syncRange: oldAccountDef.syncRange,
|
||||
syncInterval: oldAccountDef.syncInterval || 0,
|
||||
notifyOnNew: oldAccountDef.hasOwnProperty('notifyOnNew') ?
|
||||
oldAccountDef.notifyOnNew : true,
|
||||
playSoundOnSend: oldAccountDef.hasOwnProperty('playSoundOnSend') ?
|
||||
oldAccountDef.playSoundOnSend : true,
|
||||
|
||||
credentials: credentials,
|
||||
connInfo: {
|
||||
server: oldAccountDef.connInfo.server,
|
||||
deviceId: oldAccountDef.connInfo.deviceId ||
|
||||
$asacct.makeUniqueDeviceId()
|
||||
},
|
||||
|
||||
identities: $accountcommon.recreateIdentities(universe, accountId,
|
||||
oldAccountDef.identities)
|
||||
};
|
||||
|
||||
this._loadAccount(universe, accountDef, null, function (account) {
|
||||
callback(null, account, null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the account def and folder info for our new (or recreated) account and
|
||||
* then load it.
|
||||
*/
|
||||
_loadAccount: function cfg_as__loadAccount(universe, accountDef,
|
||||
protoConn, callback) {
|
||||
// XXX: Just reload the old folders when applicable instead of syncing the
|
||||
// folder list again, which is slow.
|
||||
var folderInfo = {
|
||||
$meta: {
|
||||
nextFolderNum: 0,
|
||||
nextMutationNum: 0,
|
||||
lastFolderSyncAt: 0,
|
||||
syncKey: '0',
|
||||
},
|
||||
$mutations: [],
|
||||
$mutationState: {},
|
||||
};
|
||||
universe.saveAccountDef(accountDef, folderInfo);
|
||||
universe._loadAccount(accountDef, folderInfo, protoConn, callback);
|
||||
},
|
||||
};
|
||||
|
||||
}); // end define
|
||||
1488
gaia/apps/email/js/ext/activesync/folder.js
Executable file
1488
gaia/apps/email/js/ext/activesync/folder.js
Executable file
File diff suppressed because it is too large
Load diff
472
gaia/apps/email/js/ext/activesync/jobs.js
Executable file
472
gaia/apps/email/js/ext/activesync/jobs.js
Executable file
|
|
@ -0,0 +1,472 @@
|
|||
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
|
||||
Loading…
Reference in a new issue