Bug 466227 - gloda should index IMAP messages that are not offline too (headers only). Implement indexing of messages in non-offline IMAP folders, and add tests for both offline and non-offline IMAP messages. r=asuth+bienvenu, sr=bienvenu.

This patch includes a fix for a bug in maild.js where the input stream gets GCed early, and also has asuth's "v1 make test_query_core.js xpcshell test work on linux" fix.

--HG--
extra : rebase_source : 36a930adcbef55175915f655394a2df44d1acd5f
This commit is contained in:
Siddharth Agarwal 2009-09-04 23:26:52 +05:30
Родитель 372875b505
Коммит 8ad2e059b6
26 изменённых файлов: 887 добавлений и 319 удалений

Просмотреть файл

@ -449,7 +449,8 @@ GlodaMessage.prototype = {
_clone: function gloda_message_clone() {
return new GlodaMessage(this._datastore, this._id, this._folderID,
this._messageKey, this._conversationID, this._conversation, this._date,
this._headerMessageID, this._deleted);
this._headerMessageID, this._deleted, this._jsonText, this._notability,
this._subject, this._indexedBodyText, this._attachmentNames);
},
_ghost: function gloda_message_ghost() {

Просмотреть файл

@ -20,6 +20,7 @@
*
* Contributor(s):
* Andrew Sutherland <asutherland@asutherland.org>
* Siddharth Agarwal <sid.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -53,52 +54,6 @@ Cu.import("resource://app/modules/gloda/datamodel.js");
Cu.import("resource://app/modules/gloda/databind.js");
Cu.import("resource://app/modules/gloda/collection.js");
let MBM_LOG = Log4Moz.repository.getLogger("gloda.ds.mbm");
/**
* @class This callback handles processing the asynchronous query results of
* GlodaDatastore.getMessagesByMessageID. Because that method is only
* called as part of the indexing process, we are guaranteed that there will
* be no real caching ramifications. Accordingly, we can also defer our cache
* processing (via GlodaCollectionManager) until the query completes.
*
* @param aMsgIDToIndex Map from message-id to the desired
*
* @constructor
*/
function MessagesByMessageIdCallback(aMsgIDToIndex, aResults,
aCallback, aCallbackThis) {
this.msgIDToIndex = aMsgIDToIndex;
this.results = aResults;
this.callback = aCallback;
this.callbackThis = aCallbackThis;
}
MessagesByMessageIdCallback.prototype = {
onItemsAdded: function gloda_ds_mbmi_onItemsAdded(aItems, aCollection) {
// just outright bail if we are shutdown
if (GlodaDatastore.datastoreIsShutdown)
return;
MBM_LOG.debug("getting results...");
for each (let [, message] in Iterator(aItems)) {
this.results[this.msgIDToIndex[message.headerMessageID]].push(message);
}
},
onItemsModified: function () {},
onItemsRemoved: function () {},
onQueryCompleted: function gloda_ds_mbmi_onQueryCompleted(aCollection) {
// just outright bail if we are shutdown
if (GlodaDatastore.datastoreIsShutdown)
return;
MBM_LOG.debug("query completed, notifying... " + this.results);
// we no longer need to unify; it is done for us.
this.callback.call(this.callbackThis, this.results);
}
};
let PCH_LOG = Log4Moz.repository.getLogger("gloda.ds.pch");
function PostCommitHandler(aCallbacks) {
@ -1888,34 +1843,46 @@ var GlodaDatastore = {
this.asyncConnection.lastErrorString + " - " + ex);
}
// we only create the full-text row if the body is non-null.
// so, even though body might be null, we still want to create the
// full-text search row
if (aMessage._bodyLines) {
if (aMessage._content && aMessage._content.hasContent())
aMessage._indexedBodyText = aMessage._content.getContentString(true);
else
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
// we create the full-text row for any message that isn't a ghost,
// whether we have the body or not
if (aMessage.folderID !== null)
this._insertMessageText(aMessage);
},
let imts = this._insertMessageTextStatement;
imts.bindInt64Parameter(0, aMessage.id);
imts.bindStringParameter(1, aMessage._subject);
/**
* Inserts a full-text row. This should only be called if you're sure you want
* to insert a row into the table.
*/
_insertMessageText: function gloda_ds__insertMessageText(aMessage) {
if (aMessage._content && aMessage._content.hasContent())
aMessage._indexedBodyText = aMessage._content.getContentString(true);
else if (aMessage._bodyLines)
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
else
aMessage._indexedBodyText = null;
let imts = this._insertMessageTextStatement;
imts.bindInt64Parameter(0, aMessage.id);
imts.bindStringParameter(1, aMessage._subject);
if (aMessage._indexedBodyText == null)
imts.bindNullParameter(2);
else
imts.bindStringParameter(2, aMessage._indexedBodyText);
if (aMessage._attachmentNames === null)
imts.bindNullParameter(3);
else
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
imts.bindStringParameter(4, aMessage._indexAuthor);
imts.bindStringParameter(5, aMessage._indexRecipients);
if (aMessage._attachmentNames === null)
imts.bindNullParameter(3);
else
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
try {
imts.executeAsync(this.trackAsync());
}
catch(ex) {
throw("error executing fulltext statement... " +
this.asyncConnection.lastError + ": " +
this.asyncConnection.lastErrorString + " - " + ex);
}
imts.bindStringParameter(4, aMessage._indexAuthor);
imts.bindStringParameter(5, aMessage._indexRecipients);
try {
imts.executeAsync(this.trackAsync());
}
catch(ex) {
throw("error executing fulltext statement... " +
this.asyncConnection.lastError + ": " +
this.asyncConnection.lastErrorString + " - " + ex);
}
},
@ -1933,10 +1900,24 @@ var GlodaDatastore = {
return this._updateMessageStatement;
},
get _updateMessageTextStatement() {
let statement = this._createAsyncStatement(
"UPDATE messagesText SET body = ?1, \
attachmentNames = ?2 \
WHERE docid = ?3");
this.__defineGetter__("_updateMessageTextStatement", function() statement);
return this._updateMessageTextStatement;
},
/**
* Update the database row associated with the message. If aBody is supplied,
* the associated full-text row is created; it is assumed that it did not
* previously exist.
* Update the database row associated with the message. If the message is
* not a ghost and has _isNew defined, messagesText is affected.
*
* aMessage._isNew is currently equivalent to the fact that there is no
* full-text row associated with this message, and we work with this
* assumption here. Note that if aMessage._isNew is not defined, then
* we don't do anything.
*/
updateMessage: function gloda_ds_updateMessage(aMessage) {
let ums = this._updateMessageStatement;
@ -1963,31 +1944,11 @@ var GlodaDatastore = {
ums.executeAsync(this.trackAsync());
if (aMessage._isNew && aMessage._bodyLines) {
if (aMessage._content && aMessage._content.hasContent())
aMessage._indexedBodyText = aMessage._content.getContentString(true);
if (aMessage.folderID !== null) {
if (aMessage._isNew === true)
this._insertMessageText(aMessage);
else
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
let imts = this._insertMessageTextStatement;
imts.bindInt64Parameter(0, aMessage.id);
imts.bindStringParameter(1, aMessage._subject);
imts.bindStringParameter(2, aMessage._indexedBodyText);
if (aMessage._attachmentNames === null)
imts.bindNullParameter(3);
else
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
imts.bindStringParameter(4, aMessage._indexAuthor);
imts.bindStringParameter(5, aMessage._indexRecipients);
try {
imts.executeAsync(this.trackAsync());
}
catch(ex) {
throw("error executing fulltext statement... " +
this.asyncConnection.lastError + ": " +
this.asyncConnection.lastErrorString + " - " + ex);
}
this._updateMessageText(aMessage);
}
// In completely abstract theory, this is where we would call
@ -1996,6 +1957,51 @@ var GlodaDatastore = {
// handles it.)
},
/**
* Updates the full-text row associated with this message. This only performs
* the UPDATE query if the indexed body text has changed, which means that if
* the body hasn't changed but the attachments have, we don't update.
*/
_updateMessageText: function gloda_ds__updateMessageText(aMessage) {
let newIndexedBodyText;
if (aMessage._content && aMessage._content.hasContent())
newIndexedBodyText = aMessage._content.getContentString(true);
else if (aMessage._bodyLines)
newIndexedBodyText = aMessage._bodyLines.join("\n");
else
newIndexedBodyText = null;
// If the body text matches, don't perform an update
if (newIndexedBodyText == aMessage._indexedBodyText) {
this._log.debug("in _updateMessageText, skipping update because body matches");
return;
}
aMessage._indexedBodyText = newIndexedBodyText;
let umts = this._updateMessageTextStatement;
umts.bindInt64Parameter(2, aMessage.id);
if (aMessage._indexedBodyText == null)
umts.bindNullParameter(0);
else
umts.bindStringParameter(0, aMessage._indexedBodyText);
if (aMessage._attachmentNames == null)
umts.bindNullParameter(1);
else
umts.bindStringParameter(1, aMessage._attachmentNames.join("\n"));
try {
umts.executeAsync(this.trackAsync());
}
catch(ex) {
throw("error executing fulltext statement... " +
this.asyncConnection.lastError + ": " +
this.asyncConnection.lastErrorString + " - " + ex);
}
},
get _updateMessageLocationStatement() {
let statement = this._createAsyncStatement(
"UPDATE messages SET folderID = ?1, messageKey = ?2 WHERE id = ?3");
@ -2212,49 +2218,6 @@ var GlodaDatastore = {
return messageIDs;
},
/**
* Given a list of Message-ID's, return a matching list of lists of messages
* matching those Message-ID's. So if you pass an array with three
* Message-ID's ["a", "b", "c"], you would get back an array containing
* 3 lists, where the first list contains all the messages with a message-id
* of "a", and so forth. The reason a list is returned rather than null/a
* message is that we accept the reality that we have multiple copies of
* messages with the same ID.
* This call is asynchronous because it depends on previously created messages
* to be reflected in our results, which requires us to execute on the async
* thread where all our writes happen. This also turns out to be a
* reasonable thing because we could imagine pathological cases where there
* could be a lot of message-id's and/or a lot of messages with those
* message-id's.
*/
getMessagesByMessageID: function gloda_ds_getMessagesByMessageID(aMessageIDs,
aCallback, aCallbackThis) {
let msgIDToIndex = {};
let results = [];
for (let iID = 0; iID < aMessageIDs.length; ++iID) {
let msgID = aMessageIDs[iID];
results.push([]);
msgIDToIndex[msgID] = iID;
}
// Unfortunately, IN doesn't work with statement binding mechanisms, and
// a chain of ORed tests really can't be bound unless we create one per
// value of N (seems silly).
let quotedIDs = ["'" + msgID.replace("'", "''", "g") + "'" for each
([i, msgID] in Iterator(aMessageIDs))]
let sqlString = "SELECT * FROM messages WHERE headerMessageID IN (" +
quotedIDs + ")";
let nounDef = GlodaMessage.prototype.NOUN_DEF;
let listener = new MessagesByMessageIdCallback(msgIDToIndex, results,
aCallback, aCallbackThis);
// Use a null query because we don't want any update notifications about our
// collection. They would just confuse and anger the listener.
let query = new nounDef.nullQueryClass();
return this._queryFromSQLString(sqlString, [], nounDef,
query, listener);
},
get _updateMessagesMarkDeletedByFolderID() {
let statement = this._createAsyncStatement(
"UPDATE messages SET folderID = NULL, messageKey = NULL, \
@ -2763,8 +2726,12 @@ var GlodaDatastore = {
// return. For example, in the case of messages, deleted or ghost
// messages should not be returned by this query layer. We require
// hand-rolled SQL to do that for now.
let validityConstraintSuffix =
nounDef.dbQueryValidityConstraintSuffix || "";
let validityConstraintSuffix;
if (nounDef.dbQueryValidityConstraintSuffix &&
!aQuery.options.noDbQueryValidityConstraints)
validityConstraintSuffix = nounDef.dbQueryValidityConstraintSuffix;
else
validityConstraintSuffix = "";
for (let iUnion = 0; iUnion < unionQueries.length; iUnion++) {
let curQuery = unionQueries[iUnion];
@ -2912,8 +2879,14 @@ var GlodaDatastore = {
}
let sqlString = "SELECT * FROM " + nounDef.tableName;
if (nounDef.dbQueryJoinMagic && !aQuery.options.noMagic)
sqlString += nounDef.dbQueryJoinMagic;
if (!aQuery.options.noMagic) {
if (aQuery.options.noDbQueryValidityConstraints &&
nounDef.dbQueryJoinMagicWithNoValidityConstraints)
sqlString += nounDef.dbQueryJoinMagicWithNoValidityConstraints;
else if (nounDef.dbQueryJoinMagic)
sqlString += nounDef.dbQueryJoinMagic;
}
if (whereClauses.length)
sqlString += " WHERE (" + whereClauses.join(") OR (") + ")";

Просмотреть файл

@ -49,7 +49,7 @@ Cu.import("resource://app/modules/gloda/gloda.js");
Cu.import("resource://app/modules/gloda/datastore.js");
Cu.import("resource://app/modules/gloda/noun_mimetype.js");
Cu.import("resource://app/modules/gloda/connotent.js");
/**
* @namespace The Gloda Fundamental Attribute provider is a special attribute
@ -100,6 +100,7 @@ var GlodaFundAttr = {
_attrCc: null,
_attrCcMe: null,
_attrDate: null,
_attrHeaderMessageID: null,
defineAttributes: function() {
/* ***** Conversations ***** */
@ -286,6 +287,19 @@ var GlodaFundAttr = {
objectNoun: Gloda.NOUN_DATE,
}); // tested-by: test_attributes_fundamental
// Header message ID.
this._attrHeaderMessageID = Gloda.defineAttribute({
provider: this,
extensionName: Gloda.BUILT_IN,
attributeType: Gloda.kAttrFundamental,
attributeName: "headerMessageID",
singular: true,
special: Gloda.kSpecialColumn,
specialColumnName: "headerMessageID",
subjectNouns: [Gloda.NOUN_MESSAGE],
objectNoun: Gloda.NOUN_STRING,
}); // tested-by: test_attributes_fundamental
// Attachment MIME Types
this._attrAttachmentTypes = Gloda.defineAttribute({
provider: this,
@ -432,19 +446,21 @@ var GlodaFundAttr = {
aGlodaMessage.cc = ccIdentities;
// -- Attachments
let attachmentTypes = [];
for each (let [, attachment] in Iterator(aMimeMsg.allAttachments)) {
// We don't care about would-be attachments that are not user-intended
// attachments but rather artifacts of the message content.
// We also want to avoid dealing with obviously bogus mime types.
// (If you don't have a "/", you are probably bogus.)
if (attachment.isRealAttachment &&
(attachment.contentType.indexOf("/") != -1)) {
attachmentTypes.push(MimeTypeNoun.getMimeType(attachment.contentType));
if (aMimeMsg) {
let attachmentTypes = [];
for each (let [, attachment] in Iterator(aMimeMsg.allAttachments)) {
// We don't care about would-be attachments that are not user-intended
// attachments but rather artifacts of the message content.
// We also want to avoid dealing with obviously bogus mime types.
// (If you don't have a "/", you are probably bogus.)
if (attachment.isRealAttachment &&
(attachment.contentType.indexOf("/") != -1)) {
attachmentTypes.push(MimeTypeNoun.getMimeType(attachment.contentType));
}
}
if (attachmentTypes.length) {
aGlodaMessage.attachmentTypes = attachmentTypes;
}
}
if (attachmentTypes.length) {
aGlodaMessage.attachmentTypes = attachmentTypes;
}
// TODO: deal with mailing lists, including implicit-to. this will require
@ -565,9 +581,12 @@ var GlodaFundAttr = {
if (fromMeCc.length)
aGlodaMessage.fromMeCc = fromMeCc;
if (aRawReps.bodyLines &&
this.contentWhittle({}, aRawReps.bodyLines, aRawReps.content)) {
// we were going to do something here?
// Content
if (aRawReps.bodyLines) {
aGlodaMessage._content = new GlodaContent();
if (this.contentWhittle({}, aRawReps.bodyLines, aGlodaMessage._content)) {
// we were going to do something here?
}
}
yield Gloda.kWorkDone;

Просмотреть файл

@ -54,6 +54,49 @@ Cu.import("resource://app/modules/gloda/utils.js");
Cu.import("resource://app/modules/iteratorUtils.jsm");
let MBM_LOG = Log4Moz.repository.getLogger("gloda.NS.mbm");
/**
* @class This callback handles processing the asynchronous query results of
* Gloda.getMessagesByMessageID.
*
* @param aMsgIDToIndex Map from message-id to the desired
*
* @constructor
*/
function MessagesByMessageIdCallback(aMsgIDToIndex, aResults,
aCallback, aCallbackThis) {
this.msgIDToIndex = aMsgIDToIndex;
this.results = aResults;
this.callback = aCallback;
this.callbackThis = aCallbackThis;
}
MessagesByMessageIdCallback.prototype = {
onItemsAdded: function gloda_ds_mbmi_onItemsAdded(aItems, aCollection) {
// just outright bail if we are shutdown
if (GlodaDatastore.datastoreIsShutdown)
return;
MBM_LOG.debug("getting results...");
for each (let [, message] in Iterator(aItems)) {
this.results[this.msgIDToIndex[message.headerMessageID]].push(message);
}
},
onItemsModified: function () {},
onItemsRemoved: function () {},
onQueryCompleted: function gloda_ds_mbmi_onQueryCompleted(aCollection) {
// just outright bail if we are shutdown
if (GlodaDatastore.datastoreIsShutdown)
return;
MBM_LOG.debug("query completed, notifying... " + this.results);
// we no longer need to unify; it is done for us.
this.callback.call(this.callbackThis, this.results);
}
};
/**
* Provides the user-visible (and extension visible) global database
* functionality. There is currently a dependency/ordering
@ -326,6 +369,45 @@ var Gloda = {
return query.getCollection(aListener, aData);
},
/**
* Given a list of Message-ID's, return a matching list of lists of messages
* matching those Message-ID's. So if you pass an array with three
* Message-ID's ["a", "b", "c"], you would get back an array containing
* 3 lists, where the first list contains all the messages with a message-id
* of "a", and so forth. The reason a list is returned rather than null/a
* message is that we accept the reality that we have multiple copies of
* messages with the same ID.
* This call is asynchronous because it depends on previously created messages
* to be reflected in our results, which requires us to execute on the async
* thread where all our writes happen. This also turns out to be a
* reasonable thing because we could imagine pathological cases where there
* could be a lot of message-id's and/or a lot of messages with those
* message-id's.
*/
getMessagesByMessageID: function gloda_ns_getMessagesByMessageID(aMessageIDs,
aCallback, aCallbackThis) {
let msgIDToIndex = {};
let results = [];
for (let iID = 0; iID < aMessageIDs.length; ++iID) {
let msgID = aMessageIDs[iID];
results.push([]);
msgIDToIndex[msgID] = iID;
}
let quotedIDs = ["'" + msgID.replace("'", "''", "g") + "'" for each
([i, msgID] in Iterator(aMessageIDs))];
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
noDbQueryValidityConstraints: true,
});
query.headerMessageID.apply(query, quotedIDs);
query.frozen = true;
let listener = new MessagesByMessageIdCallback(msgIDToIndex, results,
aCallback, aCallbackThis);
return query.getCollection(listener);
},
/**
* @testpoint gloda.ns.getMessageContent
*/
@ -1077,6 +1159,10 @@ var Gloda = {
dbAttribAdjuster: GlodaDatastore.adjustMessageAttributes,
dbQueryValidityConstraintSuffix:
" AND +deleted = 0 AND +folderID IS NOT NULL AND +messageKey IS NOT NULL",
// This is what's used when we have no validity constraints, i.e. we allow
// for ghost messages, which do not have a row in the messagesText table.
dbQueryJoinMagicWithNoValidityConstraints:
" LEFT JOIN messagesText ON messages.id = messagesText.rowid",
objInsert: GlodaDatastore.insertMessage,
objUpdate: GlodaDatastore.updateMessage,
toParamAndValue: function(aMessage) {

Просмотреть файл

@ -21,6 +21,7 @@
* Contributor(s):
* Andrew Sutherland <asutherland@asutherland.org>
* Kent James <kent@caspia.com>
* Siddharth Agarwal <sid.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -243,7 +244,8 @@ IndexingJob.prototype = {
* meta-data.
*
* Indexing Message Control
* - We index IMAP messages that are offline. We index all local messages.
* - We index the headers of all IMAP messages. We index the bodies of all IMAP
* messages that are offline. We index all local messages.
* We plan to avoid indexing news messages.
* - We would like a way to express desires about indexing that either don't
* confound offline storage with indexing, or actually allow some choice.
@ -1061,8 +1063,8 @@ var GlodaIndexer = {
// The basic search expression is:
// ((GLODA_MESSAGE_ID_PROPERTY Is 0) || (GLODA_DIRTY_PROPERTY Isnt 0))
// If the folder !isLocal we add the terms:
// && (Status Is nsMsgMessageFlags.Offline)
// && (Status Isnt nsMsgMessageFlags.Expunged)
// - if the folder is offline -- && (Status Is nsMsgMessageFlags.Offline)
// - && (Status Isnt nsMsgMessageFlags.Expunged)
let searchSession = Cc["@mozilla.org/messenger/searchSession;1"]
.createInstance(Ci.nsIMsgSearchSession);
@ -1081,7 +1083,7 @@ var GlodaIndexer = {
searchTerm.beginsGrouping = true;
searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
searchTerm.op = nsMsgSearchOp.Is;
value = searchTerm.value;
let value = searchTerm.value;
value.attrib = searchTerm.attrib;
value.status = 0;
searchTerm.value = value;
@ -1103,16 +1105,19 @@ var GlodaIndexer = {
if (!isLocal)
{
// third term: && Status Is nsMsgMessageFlags.Offline
searchTerm = searchSession.createTerm();
searchTerm.booleanAnd = true;
searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
searchTerm.op = nsMsgSearchOp.Is;
value = searchTerm.value;
value.attrib = searchTerm.attrib;
value.status = Ci.nsMsgMessageFlags.Offline;
searchTerm.value = value;
searchTerms.appendElement(searchTerm, false);
// If the folder is offline, then the message should be too
if (this._indexingFolder.flags & Ci.nsMsgFolderFlags.Offline) {
// third term: && Status Is nsMsgMessageFlags.Offline
searchTerm = searchSession.createTerm();
searchTerm.booleanAnd = true;
searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
searchTerm.op = nsMsgSearchOp.Is;
value = searchTerm.value;
value.attrib = searchTerm.attrib;
value.status = Ci.nsMsgMessageFlags.Offline;
searchTerm.value = value;
searchTerms.appendElement(searchTerm, false);
}
// fourth term: && Status Isnt nsMsgMessageFlags.Expunged
searchTerm = searchSession.createTerm();
@ -1652,10 +1657,10 @@ var GlodaIndexer = {
Ci.nsIMsgFolder);
if (!this.shouldIndexFolder(folder))
continue;
// we could also check nsMsgFolderFlags.Mail conceivably...
let isLocal = folder instanceof Ci.nsIMsgLocalMailFolder;
// we only index local folders or IMAP folders that are marked offline
if (!isLocal && !(folder.flags & Ci.nsMsgFolderFlags.Offline))
// we only index local or IMAP folders
if (!(folder instanceof Ci.nsIMsgLocalMailFolder) &&
!(folder instanceof Ci.nsIMsgImapMailFolder))
continue;
let glodaFolder = Gloda.getFolderForFolder(folder);
@ -1734,7 +1739,6 @@ var GlodaIndexer = {
// don't do something. so we will yield with kWorkSync for every block.
const HEADER_CHECK_BLOCK_SIZE = 25;
let isLocal = this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
// we can safely presume if we are here that this folder has been selected
// for offline processing...
@ -1835,8 +1839,6 @@ var GlodaIndexer = {
// if we are already in the correct folder, our "get in the folder" clause
// will not execute, so we need to make sure this value is accurate in
// that case. (and we want to avoid multiple checks...)
let folderIsLocal =
this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
for (; aJob.offset < aJob.items.length; aJob.offset++) {
let item = aJob.items[aJob.offset];
// item is either [folder ID, message key] or
@ -1850,9 +1852,6 @@ var GlodaIndexer = {
// stay out of folders we should not be in!
if (!this.shouldIndexFolder(this._indexingFolder))
continue;
folderIsLocal =
this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
}
let msgHdr;
@ -1863,12 +1862,9 @@ var GlodaIndexer = {
// TODO fixme to not assume singular message-id's.
msgHdr = this._indexingDatabase.getMsgHdrForMessageID(item[1]);
// it needs a header, the header needs to not be expunged, plus, the
// message needs to be considered offline.
// it needs a header, and the header needs to not be expunged.
if (msgHdr &&
!(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Expunged) &&
(folderIsLocal ||
(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Offline)))
!(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Expunged))
yield this._callbackHandle.pushAndGo(this._indexMessage(msgHdr,
this._callbackHandle));
else
@ -2069,9 +2065,16 @@ var GlodaIndexer = {
let msgFolder = aMsgHdr.folder;
if (!this.indexer.shouldIndexFolder(msgFolder))
return;
// Make sure the message is eligible for indexing.
// We index local messages, IMAP messages that are offline, and IMAP
// messages that aren't offline but whose folders aren't offline either
let isFolderLocal = msgFolder instanceof Ci.nsIMsgLocalMailFolder;
if (!isFolderLocal && !(msgFolder.flags&Ci.nsMsgFolderFlags.Offline))
return;
if (!isFolderLocal) {
if (!(aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) &&
(msgFolder.flags & Ci.nsMsgFolderFlags.Offline))
return;
}
// mark the folder dirty so we know to look in it, but there is no need
// to mark the message because it will lack a gloda-id anyways.
@ -2379,9 +2382,16 @@ var GlodaIndexer = {
let msgFolder = aMsgHdr.folder;
if (!this.indexer.shouldIndexFolder(msgFolder))
return;
// Make sure the message is eligible for indexing.
// We index local messages, IMAP messages that are offline, and IMAP
// messages that aren't offline but whose folders aren't offline either
let isFolderLocal = msgFolder instanceof Ci.nsIMsgLocalMailFolder;
if (!isFolderLocal && !(msgFolder.flags&Ci.nsMsgFolderFlags.Offline))
return;
if (!isFolderLocal) {
if (!(aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) &&
(msgFolder.flags & Ci.nsMsgFolderFlags.Offline))
return;
}
// mark the message as dirty
// (We could check for the presence of the gloda message id property
@ -2399,13 +2409,15 @@ var GlodaIndexer = {
}
// only queue the message if we haven't overflowed our event-driven budget
if (this.indexer._pendingAddJob.items.length <
this.indexer._indexMaxEventQueueMessages)
this.indexer._indexMaxEventQueueMessages) {
this.indexer._pendingAddJob.items.push(
[GlodaDatastore._mapFolder(msgFolder).id,
aMsgHdr.messageKey]);
else
this.indexer.indexing = true;
}
else {
this.indexer.indexingSweepNeeded = true;
this.indexer.indexing = true;
}
},
OnItemAdded: function gloda_indexer_OnItemAdded(aParentItem, aItem) {
@ -2485,13 +2497,27 @@ var GlodaIndexer = {
_indexMessage: function gloda_indexMessage(aMsgHdr, aCallbackHandle) {
let logDebug = this._log.level <= Log4Moz.Level.Debug;
if (logDebug)
this._log.debug("*** Indexing message: " + aMsgHdr.messageKey + " : " +
aMsgHdr.subject);
MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
aCallbackHandle.callback);
let [,aMimeMsg] = yield this.kWorkAsync;
// If the message is offline, then get the message body as well
let isMsgOffline = false;
let aMimeMsg;
if ((aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) ||
(aMsgHdr.folder instanceof Ci.nsIMsgLocalMailFolder)) {
isMsgOffline = true;
MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
aCallbackHandle.callback);
[,aMimeMsg] = yield this.kWorkAsync;
}
else {
if (logDebug)
this._log.debug(" * Message is not offline -- only headers indexed");
}
if (logDebug)
this._log.debug(" * Got message, subject " + aMsgHdr.subject);
if (this._unitTestSuperVerbose) {
if (aMimeMsg)
@ -2511,8 +2537,8 @@ var GlodaIndexer = {
// also see if we already know about the message...
references.push(aMsgHdr.messageId);
this._datastore.getMessagesByMessageID(references, aCallbackHandle.callback,
aCallbackHandle.callbackThis);
Gloda.getMessagesByMessageID(references, aCallbackHandle.callback,
aCallbackHandle.callbackThis);
// (ancestorLists has a direct correspondence to the message ids)
let ancestorLists = yield this.kWorkAsync;
@ -2668,7 +2694,7 @@ var GlodaIndexer = {
let bodyPlain = aMimeMsg.coerceBodyToPlaintext(aMsgHdr.folder);
if (bodyPlain) {
curMsg._bodyLines = bodyPlain.split(/\r?\n/);
curMsg._content = new GlodaContent();
// curMsg._content gets set by fundattr.js
}
}
@ -2676,20 +2702,20 @@ var GlodaIndexer = {
curMsg._isNew = true;
// curMsg._indexedBodyText is set by GlodaDatastore.insertMessage or
// GlodaDatastore.updateMessage
curMsg._subject = aMsgHdr.mime2DecodedSubject;
curMsg._attachmentNames = attachmentNames;
// curMsg._indexAuthor gets set by fundattr.js
// curMsg._indexRecipients gets set by fundattr.js
}
curMsg._subject = aMsgHdr.mime2DecodedSubject;
curMsg._attachmentNames = attachmentNames;
// curMsg._indexAuthor gets set by fundattr.js
// curMsg._indexRecipients gets set by fundattr.js
// zero the notability so everything in grokNounItem can just increment
curMsg.notability = 0;
yield aCallbackHandle.pushAndGo(
Gloda.grokNounItem(curMsg,
{header: aMsgHdr, mime: aMimeMsg,
bodyLines: curMsg._bodyLines, content: curMsg._content},
{header: aMsgHdr, mime: aMimeMsg, bodyLines: curMsg._bodyLines},
isConceptuallyNew, isRecordNew,
aCallbackHandle));

Просмотреть файл

@ -73,6 +73,9 @@ Cu.import("resource://app/modules/gloda/datastore.js");
* this makes things more readable but is unlikely to improve
* performance. (Namely, my use of 'offsets' for full-text stuff
* ends up in the EXPLAIN plan twice despite this.)
* - noDbQueryValidityConstraints: Indicates that any validity constraints
* should be ignored. This should be used when you need to get every
* match regardless of whether it's valid.
*
* @property _owner The query instance that holds the list of unions...
* @property _constraints A list of (lists of OR constraints) that are ANDed

Просмотреть файл

@ -20,6 +20,7 @@
*
* Contributor(s):
* Andrew Sutherland <asutherland@asutherland.org>
* Siddharth Agarwal <sid.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -35,8 +36,9 @@
*
* ***** END LICENSE BLOCK ***** */
// -- Pull in the POP3 fake-server / local account helper code
load("../../test_mailnewslocal/unit/head_maillocal.js");
// Import the main scripts that mailnews tests need to set up and tear down
load("../../mailnews/resources/mailDirService.js");
load("../../mailnews/resources/mailTestUtils.js");
/**
* Create a 'me' identity of "me@localhost" for the benefit of Gloda. At the
@ -217,6 +219,8 @@ const INJECT_FAKE_SERVER = 1;
const INJECT_MBOX = 2;
/** Inject messages using addMessage. */
const INJECT_ADDMESSAGE = 3;
/** Inject messages using the IMAP fakeserver. */
const INJECT_IMAP_FAKE_SERVER = 4;
/**
* Convert a list of synthetic messages to a form appropriate to feed to the
@ -227,7 +231,32 @@ function _synthMessagesToFakeRep(aSynthMessages) {
(msg in aSynthMessages)];
}
function lobotomizeAdaptiveIndexer() {
// The indexer doesn't need to worry about load; zero his rescheduling time.
GlodaIndexer._INDEX_INTERVAL = 0;
let realIdleService = GlodaIndexer._idleService;
// pretend we are always idle
GlodaIndexer._idleService = {
idleTime: 1000,
addIdleObserver: function() {
realIdleService.addIdleObserver.apply(realIdleService, arguments);
},
removeIdleObserver: function() {
realIdleService.removeIdleObserver.apply(realIdleService, arguments);
}
};
// Lobotomize the adaptive indexer
GlodaIndexer._cpuTargetIndexTime = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_ACTIVE = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_IDLE = 10000;
GlodaIndexer._CPU_IS_BUSY_TIME = 10000;
GlodaIndexer._PAUSE_LATE_IS_BUSY_TIME = 10000;
}
function imsInit() {
dump("Initializing index message state\n");
let ims = indexMessageState;
if (!ims.inited) {
@ -246,29 +275,12 @@ function imsInit() {
// Make the indexer be more verbose about indexing for us...
GlodaIndexer._unitTestSuperVerbose = true;
// The indexer doesn't need to worry about load; zero his rescheduling time.
GlodaIndexer._INDEX_INTERVAL = 0;
let realIdleService = GlodaIndexer._idleService;
// pretend we are always idle
GlodaIndexer._idleService = {
idleTime: 1000,
addIdleObserver: function() {
realIdleService.addIdleObserver.apply(realIdleService, arguments);
},
removeIdleObserver: function() {
realIdleService.removeIdleObserver.apply(realIdleService, arguments);
}
};
// Lobotomize the adaptive indexer
GlodaIndexer._cpuTargetIndexTime = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_ACTIVE = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_IDLE = 10000;
GlodaIndexer._CPU_IS_BUSY_TIME = 10000;
GlodaIndexer._PAUSE_LATE_IS_BUSY_TIME = 10000;
lobotomizeAdaptiveIndexer();
if (ims.injectMechanism == INJECT_FAKE_SERVER) {
// -- Pull in the POP3 fake-server / local account helper code
load("../../test_mailnewslocal/unit/head_maillocal.js");
// set up POP3 fakeserver to feed things in...
[ims.daemon, ims.server] = setupServerDaemon();
// (this will call loadLocalMailAccount())
@ -285,6 +297,42 @@ function imsInit() {
// we need an inbox
loadLocalMailAccount();
}
else if (ims.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
// Pull in the IMAP fake server code
load("../../test_imap/unit/head_server.js");
// set up IMAP fakeserver and incoming server
ims.daemon = new imapDaemon();
ims.server = makeServer(ims.daemon, "");
ims.incomingServer = createLocalIMAPServer();
// we need a local account for the IMAP server to have its sent messages in
loadLocalMailAccount();
// We need an identity so that updateFolder doesn't fail
let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
.getService(Ci.nsIMsgAccountManager);
let localAccount = acctMgr.createAccount();
let identity = acctMgr.createIdentity();
localAccount.addIdentity(identity);
localAccount.defaultIdentity = identity;
localAccount.incomingServer = gLocalIncomingServer;
acctMgr.defaultAccount = localAccount;
// Let's also have another account, using the same identity
let imapAccount = acctMgr.createAccount();
imapAccount.addIdentity(identity);
imapAccount.defaultIdentity = identity;
imapAccount.incomingServer = ims.incomingServer;
// The server doesn't support more than one connection
prefSvc.setIntPref("mail.server.server1.max_cached_connections", 1);
// We aren't interested in downloading messages automatically
prefSvc.setBoolPref("mail.server.server1.download_on_biff", false);
// Set the inbox to not be offline
ims.imapInbox = ims.incomingServer.rootFolder.getChildNamed("Inbox");
ims.imapInbox.flags &= ~Ci.nsMsgFolderFlags.Offline;
}
ims.inited = true;
}
@ -312,16 +360,11 @@ function imsInit() {
*/
function indexMessages(aSynthMessages, aVerifier, aOnDone) {
let ims = indexMessageState;
ims.inputMessages = aSynthMessages;
ims.glodaMessages = [];
ims.verifier = aVerifier;
ims.previousValue = undefined;
ims.onDone = aOnDone;
ims.expectMessages(aSynthMessages, aVerifier, aOnDone);
if (ims.injectMechanism == INJECT_FAKE_SERVER) {
ims.daemon.setMessages(_synthMessagesToFakeRep(aSynthMessages));
do_timeout(0, "driveFakeServer();");
do_timeout(0, "drivePOP3FakeServer();");
}
else if (ims.injectMechanism == INJECT_MBOX) {
ims.mboxName = "injecty" + ims.nextMboxNumber++;
@ -344,7 +387,20 @@ function indexMessages(aSynthMessages, aVerifier, aOnDone) {
localFolder.addMessage(msg.toMboxString());
}
}
else if (ims.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
let ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let serverInbox = ims.daemon.getMailbox("INBOX");
for (let [, msg] in Iterator(aSynthMessages)) {
// Generate a URI out of the message
let URI = ioService.newURI("data:text/plain;base64," + btoa(msg.toMessageString()), null, null);
// Add it to the server
serverInbox.addMessage(new imapMessage(URI.spec, serverInbox.uidnext++, []));
}
// Time to do stuff with the fakeserver
driveIMAPFakeServer();
}
}
function injectMessagesUsing(aInjectMechanism) {
@ -354,6 +410,8 @@ function injectMessagesUsing(aInjectMechanism) {
var indexMessageState = {
/** have we been initialized (hooked listeners, etc.) */
inited: false,
/** whether we're due for any index notifications */
expectingIndexNotifications: false,
/** our catch-all message collection that nets us all messages passing by */
catchAllCollection: null,
/** the set of synthetic messages passed in to indexMessages */
@ -370,20 +428,49 @@ var indexMessageState = {
injectMechanism: INJECT_ADDMESSAGE,
/* === Fake Server State === */
/** nsMailServer instance with POP3_RFC1939 handler */
/** nsMailServer instance (if POP3, with POP3_RFC1939 handler) */
server: null,
serverStarted: false,
/** pop3Daemon instance */
/** pop3Daemon/imapDaemon instance */
daemon: null,
/** incoming pop3 server */
/** incoming pop3/imap server */
incomingServer: null,
/** pop3 service */
/** pop3 service (not used for imap) */
pop3Service: null,
/** IMAP inbox */
imapInbox: null,
/* === MBox Injection State === */
nextMboxNumber: 0,
mboxName: null,
/**
* Sets up messages to expect index notifications for.
*
* @param aSynthMessages The synthetic messages to expect notifications
* for. We currently don't do anything with these other than count them,
* so pass whatever you want and it will be the 'source message' (1st
* argument) to your verifier function.
* @param aVerifier The function to call to verify that the indexing had the
* desired result. Takes arguments aSynthMessage (the synthetic message
* just indexed), aGlodaMessage (the gloda message representation of the
* indexed message), and aPreviousResult (the value last returned by the
* verifier function for this given set of messages, or undefined if it is
* the first message.)
* @param aOnDone The function to call when we complete processing this set of
* messages.
*/
expectMessages: function indexMessageState_expectMessages(aSynthMessages, aVerifier,
aOnDone) {
dump("^^^ setting up " + aSynthMessages.length + " message(s) to expect\n");
this.inputMessages = aSynthMessages;
this.expectingIndexNotifications = true;
this.glodaMessages = [];
this.verifier = aVerifier;
this.previousValue = undefined;
this.onDone = aOnDone;
},
/**
* Listener to handle the completion of the POP3 message retrieval (one way or
* the other.)
@ -418,38 +505,12 @@ var indexMessageState = {
}
};
/**
* Indicate that we should expect some modified messages to be indexed.
*
* @param aMessages The messages that will be modified and we should expect
* notifications about. We currently don't do anything with these other than
* count them, so pass whatever you want and it will be the 'source message'
* (1st argument) to your verifier function.
* @param aVerifier See indexMessage's aVerifier argument.
* @param aDone The (optional) callback to call on completion.
* Perform POP3 mail fetching, seeing it through to completion.
*/
function expectModifiedMessages(aMessages, aVerifier, aOnDone) {
function drivePOP3FakeServer() {
let ims = indexMessageState;
ims.inputMessages = aMessages;
ims.glodaMessages = [];
ims.verifier = aVerifier;
ims.previousValue = undefined;
ims.onDone = aOnDone;
// we don't actually need to do anything. the caller is going to be
// triggering a notification which will spur the indexer into action. the
// indexer uses its own scheduling mechanism to drive itself, so as long
// as an event loop is active, we're good.
}
/**
* Perform the mail fetching, seeing it through to completion.
*/
function driveFakeServer() {
let ims = indexMessageState;
dump(">>> enter driveFakeServer\n");
dump(">>> enter drivePOP3FakeServer\n");
// Handle the server in a try/catch/finally loop so that we always will stop
// the server if something fails.
try {
@ -480,15 +541,48 @@ dump(">>> enter driveFakeServer\n");
while (thread.hasPendingEvents())
thread.processNextEvent(true);
}
dump("<<< exit driveFakeServer\n");
dump("<<< exit drivePOP3FakeServer\n");
}
/**
* Perform an IMAP mail fetch, seeing it through to completion
*/
function driveIMAPFakeServer() {
dump(">>> enter driveIMAPFakeServer\n");
let ims = indexMessageState;
// Handle the server in a try/catch/finally loop so that we always will stop
// the server if something fails.
try {
dump(" resetting fake server\n");
ims.server.resetTest();
// Update the inbox
dump(" issuing updateFolder\n");
ims.imapInbox.updateFolder(null);
// performTest won't work here because that seemingly blocks until the
// socket is closed, which is something undesirable here
}
catch (e) {
ims.server.stop();
do_throw(e);
}
finally {
dump(" draining events\n");
let thread = gThreadManager.currentThread;
while (thread.hasPendingEvents())
thread.processNextEvent(true);
}
dump("<<< exit driveIMAPFakeServer\n");
}
/**
* Tear down the fake server. This is very important to avoid things getting
* upset during shutdown. (Namely, XPConnect will get mad about running in
* a context without "Components" defined.)
*/
function killFakeServer() {
dump("Killing fake server\n");
let ims = indexMessageState;
ims.incomingServer.closeCachedConnections();
@ -499,6 +593,8 @@ function killFakeServer() {
var thread = gThreadManager.currentThread;
while (thread.hasPendingEvents())
thread.processNextEvent(true);
do_test_finished();
}
/**
@ -549,30 +645,46 @@ var messageIndexerListener = {
callbackOnDone: null,
onIndexNotification: function(aStatus, aPrettyName, aJobIndex, aJobTotal,
aJobItemIndex, aJobItemGoal) {
dump("((( Index listener notified! aStatus = " + aStatus + "\n");
// Ignore moving/removing notifications
if (aStatus == Gloda.kIndexerMoving || aStatus == Gloda.kIndexerRemoving)
return;
let ims = indexMessageState;
// If we shouldn't be receiving notifications and we receive one with aStatus
// != kIndexerIdle. throw.
if (!ims.expectingIndexNotifications) {
if (aStatus == Gloda.kIndexerIdle) {
dump("((( Ignoring indexing notification since it's just kIndexerIdle.\n");
return;
}
else {
do_throw("Exception during index notification -- we weren't " +
"expecting one.");
}
}
// we only care if indexing has just completed...
if (!GlodaIndexer.indexing) {
if (aStatus == Gloda.kIndexerIdle) {
if (messageIndexerListener.callbackOnDone) {
let callback = messageIndexerListener.callbackOnDone;
messageIndexerListener.callbackOnDone = null;
callback();
}
let ims = indexMessageState;
// this is just the synthetic notification if inputMessages is null
if (ims.inputMessages === null) {
dump("((( ignoring indexing notification, assuming synthetic " +
"notification.\n");
return;
}
// if we haven't seen all the messages we should see, assume that the
// rest are on their way, and are just coming in a subsequent job...
// (Also, the first time we register our listener, we will get a synthetic
// idle status; at least if the indexer is idle.)
if (ims.glodaMessages.length < ims.inputMessages.length) {
let glodaLen = ims.glodaMessages.length, inputLen =
ims.inputMessages.length;
if (glodaLen < inputLen) {
dump("((( indexing is no longer indexing, but we're still expecting " +
"more results, ignoring.\n");
inputLen + " - " + glodaLen + " = " + (inputLen - glodaLen) +
" more results, ignoring.\n");
// If we're running IMAP, then update the folder once more
if (ims.imapInbox)
ims.imapInbox.updateFolder(null);
return;
}
@ -580,6 +692,8 @@ var messageIndexerListener = {
" messages) about to verify: " +
(ims.verifier ? ims.verifier.name : "none") + " and complete: " +
(ims.onDone ? ims.onDone.name : "none") + "\n");
// If we're verifying messages, we shouldn't be expecting them
ims.expectingIndexNotifications = false;
// call the verifier. (we expect them to generate an exception if the
// verification fails, using do_check_*/do_throw; we don't care about
@ -600,6 +714,7 @@ var messageIndexerListener = {
}
}
dump("((( Verification complete\n");
if (ims.onDone) {
try {
ims.onDone();
@ -770,9 +885,8 @@ function twiddleAndTest(aSynthMsg, aActionsAndTests) {
// the underlying nsIMsgDBHdr should exist at this point...
do_check_neq(gmsg.folderMessage, null);
// prepare
expectModifiedMessages([gmsg.folderMessage], verify_next_attr);
indexMessageState.expectMessages([gmsg.folderMessage], verify_next_attr);
// tell the function to perform its mutation to the desired state
dump("twiddling: " + twiddleFunc.name + ": " + desiredState + "\n");
twiddleFunc(gmsg.folderMessage, desiredState);
}
function verify_next_attr(smsg, gmsg) {
@ -1021,8 +1135,11 @@ function _gh_test_iterator() {
}
}
if (indexMessageState.injectMechanism == INJECT_FAKE_SERVER) {
killFakeServer();
if (indexMessageState.injectMechanism == INJECT_FAKE_SERVER ||
indexMessageState.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
do_test_pending();
// Give a bit of time for any remaining notifications to go through.
do_timeout(500, "killFakeServer()");
}
do_test_finished();
@ -1072,15 +1189,23 @@ function parameterizeTest(aTestFunc, aParameters) {
*
* @param aTests A list of test functions to call.
* @param aLongestTestRunTimeConceivableInSecs Optional parameter
* @param aDontInitIMS Optional parameter. If this is specified then the index
* message state is not initialized
*/
function glodaHelperRunTests(aTests, aLongestTestRunTimeConceivableInSecs) {
function glodaHelperRunTests(aTests, aLongestTestRunTimeConceivableInSecs,
aDontInitIMS) {
if (aLongestTestRunTimeConceivableInSecs == null)
aLongestTestRunTimeConceivableInSecs =
DEFAULT_LONGEST_TEST_RUN_CONCEIVABLE_SECS;
do_timeout(aLongestTestRunTimeConceivableInSecs * 1000,
"do_throw('Timeout running test, and we want you to have the log.');");
imsInit();
if (!aDontInitIMS)
imsInit();
// even if we don't want to init IMS, we want to avoid anything adaptive
// happening.
else
lobotomizeAdaptiveIndexer();
glodaHelperTests = aTests;
glodaHelperIterator = _gh_test_iterator();
next_test();
@ -1135,3 +1260,19 @@ function nukeGlodaCachesAndCollections() {
GlodaCollectionManager.defineCache(cache._nounDef, cache._maxCacheSize);
}
}
/**
* Given an IMAP folder, marks it offline and downloads its messages.
*
* @param aFolder an IMAP message folder
* @param aSynthMessages see indexMessageState.expectMessages
* @param aVerifier see indexMessageState.expectMessages
* @param aDone see indexMessageState.expectMessages
*/
function imapDownloadAllMessages(aFolder, aSynthMessages, aVerifier, aDone) {
// Let the message state know that we're expecting messages
indexMessageState.expectMessages(aSynthMessages, aVerifier, aDone);
aFolder.setFlag(Ci.nsMsgFolderFlags.Offline);
aFolder.downloadAllForOffline(null, null);
// The indexer listener is going to call aDone, so our job is done here
}

Просмотреть файл

@ -97,6 +97,17 @@ var messageInfos = [
/* ===== Tests ===== */
/**
* Hooks for pre/post setup message, used for the IMAP tests.
*/
var pre_inject_message_hook = function default_pre_inject_message_hook() {
next_test();
};
var post_inject_message_hook = function default_post_inject_message_hook() {
next_test();
};
function setup_create_message(info) {
info.body = {body: [tupe[1] for each
([, tupe] in Iterator(info.bode))].join("\r\n")};
@ -125,10 +136,12 @@ function glodaInfoStasher(aSynthMessage, aGlodaMessage) {
/**
* Actually inject all the messages we created above.
*/
var gSynMessages;
function setup_inject_messages() {
let synMessages = [info._synMsg for each
([, info] in Iterator(messageInfos))];
indexMessages(synMessages, glodaInfoStasher, next_test);
gSynMessages = [info._synMsg for each
([, info] in Iterator(messageInfos))];
indexMessages(gSynMessages, glodaInfoStasher, next_test);
}
function test_stream_message(info) {
@ -166,11 +179,15 @@ function verify_message_content(aInfo, aSynMsg, aGlodaMsg, aMsgHdr, aMimeMsg) {
var tests = [
parameterizeTest(setup_create_message, messageInfos),
function pre_inject_message() { pre_inject_message_hook(); },
setup_inject_messages,
function post_inject_message() { post_inject_message_hook(); },
// disable_index_notifications,
parameterizeTest(test_stream_message, messageInfos),
];
function run_test() {
injectMessagesUsing(INJECT_MBOX);
glodaHelperRunTests(tests);
}
injectMessagesUsing(INJECT_MBOX);

Просмотреть файл

@ -0,0 +1,17 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests the operation of the GlodaContent (in connotent.js) and its exposure
* via Gloda.getMessageContent for IMAP messages that are originally offline.
*/
load("test_gloda_content.js");
/**
* Set the imap folder to offline before adding the messages.
*/
var pre_inject_message_hook = function imap_pre_inject_message_hook() {
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
next_test();
};
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,19 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests the operation of the GlodaContent (in connotent.js) and its exposure
* via Gloda.getMessageContent for IMAP messages that were not originally
* offline, but were later made offline.
*/
load("test_gloda_content.js");
/**
* Set the imap folder to offline after adding the messages, then force a
* download of all messages.
*/
var post_inject_message_hook = function imap_post_inject_message_hook() {
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages,
glodaInfoStasher, next_test);
};
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -10,6 +10,9 @@
load("../../mailnews/resources/messageGenerator.js");
load("resources/glodaTestHelper.js");
// Whether we can expect fulltext results
var expectFulltextResults = true;
// Create a message generator
var msgGen = new MessageGenerator();
// Create a message scenario generator using that message generator
@ -17,13 +20,25 @@ var scenarios = new MessageScenarioFactory(msgGen);
/* ===== Threading / Conversation Grouping ===== */
var gSynMessages = [];
function allMessageInSameConversation(aSynthMessage, aGlodaMessage, aConvID) {
if (aConvID === undefined)
return aGlodaMessage.conversationID;
do_check_eq(aConvID, aGlodaMessage.conversationID);
// Cheat and stash the synthetic message (we need them for one of the IMAP
// tests)
gSynMessages.push(aSynthMessage);
return aConvID;
}
// These are overridden by the IMAP tests as needed
var pre_test_threading_hook = function default_pre_test_threading_hook() {
next_test();
};
var post_test_threading_hook = function default_post_test_threading_hook() {
next_test();
};
/**
* Test our conversation/threading logic in the straight-forward direct
* reply case, the missing intermediary case, and the siblings with missing
@ -72,12 +87,17 @@ function test_attributes_fundamental() {
indexMessages([smsg], verify_attributes_fundamental, next_test);
}
// Overridden by test_index_imap_mesasges
var get_expected_folder_URI = function local_get_expected_folder_URI() {
return gLocalInboxFolder.URI;
};
function verify_attributes_fundamental(smsg, gmsg) {
try {
// save off the message id for test_attributes_fundamental_from_disk
fundamentalGlodaMessageId = gmsg.id;
do_check_eq(gmsg.folderURI, gLocalInboxFolder.URI);
do_check_eq(gmsg.folderURI, get_expected_folder_URI());
// -- subject
do_check_eq(smsg.subject, gmsg.conversation.subject);
@ -97,12 +117,22 @@ function verify_attributes_fundamental(smsg, gmsg) {
// date
do_check_eq(smsg.date.valueOf(), gmsg.date.valueOf());
// -- message ID
do_check_eq(smsg.messageId, gmsg.headerMessageID);
// -- attachments
do_check_eq(gmsg.attachmentTypes.length, 1);
do_check_eq(gmsg.attachmentTypes[0], "text/plain");
do_check_eq(gmsg.attachmentNames.length, 1);
do_check_eq(gmsg.attachmentNames[0], "bob.txt");
// -- attachments. We won't have these if we don't have fulltext results
if (expectFulltextResults) {
do_check_eq(gmsg.attachmentTypes.length, 1);
do_check_eq(gmsg.attachmentTypes[0], "text/plain");
do_check_eq(gmsg.attachmentNames.length, 1);
do_check_eq(gmsg.attachmentNames[0], "bob.txt");
}
else {
// Make sure we don't actually get attachments!
do_check_eq(gmsg.attachmentTypes, null);
do_check_eq(gmsg.attachmentNames, null);
}
}
catch (ex) {
// print out some info on the various states of the messages...
@ -229,7 +259,9 @@ function test_message_deletion() {
var tests = [
function pre_test_threading() { pre_test_threading_hook(); },
test_threading,
function post_test_threading() { post_test_threading_hook(); },
test_attributes_fundamental,
test_attributes_fundamental_from_disk,
test_attributes_explicit,

Просмотреть файл

@ -14,22 +14,35 @@ var scenarios = new MessageScenarioFactory(msgGen);
/**
* Provide a bunch of messages to be indexed.
*/
var gSynMessages;
function test_index_a_bunch() {
// 4-children-per, 3-deep = 21
// 6-children-per, 3 deep = 43
// 7-children-per, 3-deep = 57
// 4-children-per, 4-deep = 85
// 4-children-per, 5-deep pyramid = 341
// 5-children-per, 5-deep pyramid = 781
// 4-children-per, 6-deep pyramid = 1365 messages
let messages = scenarios.fullPyramid(4, 3);
gSynMessages = scenarios.fullPyramid(6, 3);
// we have no need to verify.
indexMessages(messages, null, next_test);
indexMessages(gSynMessages, null, next_test);
}
var pre_test_hook = function default_pre_test_hook() {
next_test();
};
var post_test_hook = function default_post_test_hook() {
next_test();
};
var tests = [
function pre_test() { pre_test_hook(); },
test_index_a_bunch,
function post_test() { post_test_hook(); },
];
function run_test() {
injectMessagesUsing(INJECT_MBOX);
glodaHelperRunTests(tests);
}
injectMessagesUsing(INJECT_MBOX);

Просмотреть файл

@ -0,0 +1,16 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that aren't offline.
*/
// Most of the definitions are common, so just re-use those
load("test_index_messages.js");
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
return indexMessageState.imapInbox.URI;
};
var expectFulltextResults = false;
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,10 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
*/
// The definitions are common, so just re-use those
load("test_index_messages_in_bulk.js");
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,19 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that are offline from the start.
*/
// Most of the definitions are common, so just re-use those
load("test_index_messages.js");
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
return indexMessageState.imapInbox.URI;
};
var pre_test_threading_hook = function imap_pre_test_threading_hook() {
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
next_test();
};
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,15 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
*/
// The definitions are common, so just re-use those
load("test_index_messages_in_bulk.js");
var pre_test_hook = function imap_pre_test_hook() {
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
next_test();
};
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,21 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that are not offline at first, but
* are made offline later.
*/
// Most of the definitions are common, so just re-use those
load("test_index_messages.js");
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
return indexMessageState.imapInbox.URI;
};
var post_test_threading_hook = function imap_post_test_threading_hook() {
// We aren't concerned about verification here, so just pass in null
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages, null,
next_test);
};
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,16 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
*/
// The definitions are common, so just re-use those
load("test_index_messages_in_bulk.js");
var post_test_hook = function imap_post_test_hook() {
// We're not verifying anything
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages,
null, next_test);
};
// Switch to the IMAP fake server
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -241,7 +241,22 @@ function setup_search_ranking_idiom() {
new Widget(1, origin, "", 0, "bar baz", "bar baz bar bar"), // 6 + 0
new Widget(0, origin, "", 1, "bar baz", "bar baz bar bar") // 7 + 0
];
runOnIndexingComplete(next_test);
let indexingInProgress = false;
// Since we don't use the message indexer listener any more in this test, we
// need to add our own listener.
function genericIndexerCallback(aStatus) {
// If indexingInProgress is false, we've received the synthetic
// notification, so ignore it
if (indexingInProgress && aStatus == Gloda.kIndexerIdle) {
// We're done, so remove ourselves and move to the next test
Gloda.removeIndexerListener(genericIndexerCallback);
next_test();
}
}
Gloda.addIndexerListener(genericIndexerCallback);
indexingInProgress = true;
GenericIndexer.indexNewObjects(fooWidgets.concat(barBazWidgets));
}
@ -317,7 +332,6 @@ var tests = [
];
function run_test() {
// use mbox injection so we get multiple folders...
injectMessagesUsing(INJECT_MBOX);
glodaHelperRunTests(tests);
// Don't initialize the index message state
glodaHelperRunTests(tests, null, true);
}

Просмотреть файл

@ -22,6 +22,12 @@ load("resources/glodaTestHelper.js");
var msgGen = new MessageGenerator();
// Create a message scenario generator using that message generator
var scenarios = new MessageScenarioFactory(msgGen);
// Whether we're using a single folder to test. We need to skip a few tests if
// we're doing so
var singleFolder = false;
// Whether we expect fulltext results. IMAP folders that are offline shouldn't
// have their bodies indexed.
var expectFulltextResults = true;
/* ===== Populate ===== */
var world = {
@ -201,6 +207,15 @@ function glodaInfoStasher(aSynthMessage, aGlodaMessage) {
world.glodaFolders.push(aGlodaMessage.folder);
}
// We override these for the IMAP tests
var pre_setup_populate_hook = function default_pre_setup_populate_hook() {
next_test();
};
var post_setup_populate_hook = function default_post_setup_populate_hook() {
next_test();
};
var gSynMessages = [];
// first, we must populate our message store with delicious messages.
function setup_populate() {
world.glodaHolderCollection = Gloda.explicitCollection(Gloda.NOUN_MESSAGE,
@ -216,13 +231,22 @@ function setup_populate() {
world.glodaConversationIds.push(null);
}
indexMessages(generateFolderMessages(), glodaInfoStasher,
setup_populate_phase_two);
let messages = generateFolderMessages();
gSynMessages = gSynMessages.concat(messages);
indexMessages(messages, glodaInfoStasher, setup_populate_phase_two);
}
function setup_populate_phase_two() {
world.phase++;
indexMessages(generateFolderMessages(), glodaInfoStasher, next_test);
// If we have one folder, we don't attempt to populate the other one
if (singleFolder) {
next_test();
}
else {
world.phase++;
let messages = generateFolderMessages();
gSynMessages = gSynMessages.concat(messages);
indexMessages(messages, glodaInfoStasher, next_test);
}
}
/* ===== Non-text queries ===== */
@ -294,6 +318,12 @@ var ts_folderCollections = [];
* @tests gloda.datastore.sqlgen.kConstraintIn
*/
function test_query_messages_by_folder() {
// If we have one folder to test with, we can't do this test more times
if (singleFolder && ts_folderNum >= 1) {
next_test();
return;
}
let folderNum = ts_folderNum++;
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
query.folder(world.glodaFolders[folderNum]);
@ -307,7 +337,9 @@ function test_query_messages_by_folder() {
* @tests gloda.query.test.kConstraintIn
*/
function test_query_messages_by_folder_nonmatches() {
verify_nonMatches(ts_folderQueries, ts_folderCollections);
// No can do with one folder
if (!singleFolder)
verify_nonMatches(ts_folderQueries, ts_folderCollections);
next_test();
}
@ -458,7 +490,8 @@ function test_query_messages_by_body_text() {
let convBodyTerm = uniqueTermGenerator(
UNIQUE_OFFSET_BODY + UNIQUE_OFFSET_CONV + convNum);
query.bodyMatches(convBodyTerm);
queryExpect(query, world.conversationLists[convNum]); // calls next_test
queryExpect(query, expectFulltextResults ? world.conversationLists[convNum] :
[]); // calls next_test
}
/**
@ -473,7 +506,8 @@ function test_query_messages_by_attachment_names() {
let convUniqueAttachment = uniqueTermGenerator(
UNIQUE_OFFSET_ATTACHMENT + UNIQUE_OFFSET_CONV + convNum);
query.attachmentNamesMatch(convUniqueAttachment);
queryExpect(query, world.conversationLists[convNum]); // calls next_test
queryExpect(query, expectFulltextResults ? world.conversationLists[convNum] :
[]); // calls next_test
}
/**
@ -595,7 +629,9 @@ function test_query_identities_by_kind_and_value_nonmatches() {
/* ===== Driver ===== */
var tests = [
function pre_setup_populate() { pre_setup_populate_hook(); },
setup_populate,
function post_setup_populate() { post_setup_populate_hook(); },
test_query_messages_by_conversation,
test_query_messages_by_conversation,
test_query_messages_by_conversation_nonmatches,
@ -629,7 +665,8 @@ var tests = [
];
function run_test() {
// use mbox injection so we get multiple folders...
injectMessagesUsing(INJECT_MBOX);
glodaHelperRunTests(tests);
}
// use mbox injection so we get multiple folders...
injectMessagesUsing(INJECT_MBOX);

Просмотреть файл

@ -0,0 +1,10 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Test query support for IMAP messages that aren't offline.
*/
load("test_query_messages.js");
var expectFulltextResults = false;
// TODO: Make this use multiple folders, like the local folders test
var singleFolder = true;
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,16 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Test query support for IMAP messages that were offline before they were
* indexed.
*/
load("test_query_messages.js");
// Set the inbox to offline before proceeding
var pre_setup_populate_hook = function imap_pre_setup_populate_hook() {
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
next_test();
};
// TODO: Make this use multiple folders, like the local folders test
var singleFolder = true;
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -0,0 +1,19 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
* Test query support for IMAP messages that were indexed, then made available
* offline.
*/
load("test_query_messages.js");
/**
* Set the imap folder to offline after adding the messages, then force a
* download of all messages.
*/
var post_setup_populate_hook = function imap_post_setup_populate_hook() {
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages, null,
next_test);
};
// TODO: Make this use multiple folders, like the local folders test
var singleFolder = true;
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);

Просмотреть файл

@ -39,6 +39,11 @@
// Much of the original code is taken from netwerk's httpserver implementation
// Make sure we execute this file exactly once
var gMaild_js__;
if (!gMaild_js__) {
gMaild_js__ = true;
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
@ -120,6 +125,12 @@ function nsMailServer(handler) {
this._handler = handler;
this._readers = [];
this._test = false;
/**
* An array to hold refs to all the input streams below, so that they don't
* get GCed
*/
this._inputStreams = [];
}
nsMailServer.prototype = {
onSocketAccepted : function (socket, trans) {
@ -130,6 +141,7 @@ nsMailServer.prototype = {
const SEGMENT_COUNT = 1024;
var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
.QueryInterface(Ci.nsIAsyncInputStream);
this._inputStreams.push(input);
var reader = new nsMailReader(this, this._handler, trans, this._debug);
this._readers.push(reader);
@ -526,3 +538,5 @@ function server(port, handler) {
srv.performTest();
return srv.playTransaction();
}
} // gMaild_js__

Просмотреть файл

@ -9,6 +9,11 @@
* <objdir>/dist/Thunderbird{Debug}.app/Contents/MacOS/mailtest/ (on Mac OS X)
*/
// Make sure we execute this file exactly once
var gMailDirService_js__;
if (!gMailDirService_js__) {
gMailDirService_js__ = true;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
// Declare these globally for unit tests and be done with it.
@ -133,3 +138,5 @@ catch (e) {
}
// Always ensure the profile directory exists before we start the tests
gProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
} // gMailDirService_js__

Просмотреть файл

@ -35,6 +35,11 @@
*
* ***** END LICENSE BLOCK ***** */
// Make sure we execute this file exactly once
var gMailTestUtils_js__;
if (!gMailTestUtils_js__) {
gMailTestUtils_js__ = true;
// we would like for everyone to have fixIterator and toXPComArray
Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
@ -275,3 +280,5 @@ function updateFolderAndNotify(aFolder, aCallback, aCallbackThis,
aFolder.updateFolder(null);
}
} // gMailTestUtils_js__