bug 469051: show Gloda indexing progress and results in the Activity Manager window; r=asuth, sr=bienvenu

--HG--
extra : rebase_source : 4f2dcb6b3c9d41aac173ff81d78d65680ef6e40c
This commit is contained in:
Myk Melez 2009-05-13 23:00:02 -07:00
Родитель bfcb620735
Коммит a9e842ca21
6 изменённых файлов: 324 добавлений и 26 удалений

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

@ -45,6 +45,8 @@ Components.utils.import("resource://app/modules/activity/sendLater.js");
sendLaterModule.init();
Components.utils.import("resource://app/modules/activity/moveCopy.js");
moveCopyModule.init();
Components.utils.import("resource://app/modules/activity/glodaIndexer.js");
glodaIndexerActivity.init();
Components.utils.import("resource://app/modules/activity/autosync.js");
autosyncModule.init();
Components.utils.import("resource://app/modules/activity/alertHook.js");

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

@ -0,0 +1,260 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Thunderbird Activity Manager.
*
* The Initial Developer of the Original Code is
* Mozilla Messaging.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark Banner <bugzilla@standard8.plus.com>
* David Ascher <dascher@mozillamessaging.com>
* Emre Birol <emrebirol@gmail.com>
* Myk Melez <myk@mozilla.org>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ["glodaIndexerActivity"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const nsActProcess = Components.Constructor("@mozilla.org/activity-process;1",
"nsIActivityProcess", "init");
const nsActEvent = Components.Constructor("@mozilla.org/activity-event;1",
"nsIActivityEvent", "init");
const nsActWarning = Components.Constructor("@mozilla.org/activity-warning;1",
"nsIActivityWarning", "init");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
Cu.import("resource://app/modules/gloda/log4moz.js");
Cu.import("resource://app/modules/gloda/gloda.js");
Cu.import("resource://app/modules/gloda/indexer.js");
// This module links the gloda indexer and the activity manager.
let glodaIndexerActivity =
{
get log() {
delete this.log;
return this.log = Log4Moz.getConfiguredLogger("glodaIndexerActivity");
},
get activityMgr() {
delete this.activityMgr;
return this.activityMgr = Cc["@mozilla.org/activity-manager;1"]
.getService(Ci.nsIActivityManager);
},
get bundle() {
delete this.bundle;
let bundleSvc = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService);
return this.bundle = bundleSvc
.createBundle("chrome://messenger/locale/activity.properties");
},
getString: function(stringName) {
try {
return this.bundle.GetStringFromName(stringName);
} catch (e) {
this.log.error("error trying to get a string called: " + stringName);
throw(e);
}
},
init: function() {
// Register a listener with the Gloda indexer that receives notifications
// about Gloda indexing status. We wrap the listener in this function so we
// can set |this| to the GlodaIndexerActivity object inside the listener.
function listenerWrapper(aStatus, aFolder, aJobNumber, aTotalJobNum,
aItemNumber, aTotalItemNum)
{
glodaIndexerActivity.listener.apply(glodaIndexerActivity, arguments);
};
GlodaIndexer.addListener(listenerWrapper);
},
/**
* Information about the current job. An object with these properties:
*
* folder {String}
* the name of the folder being processed by the job
* jobNumber {Number}
* the index of the job in the list of jobs
* process {nsIActivityProcess}
* the activity process corresponding to the current job
* startTime {Date}
* the time at which we were first notified about the job
* totalItemNum {Number}
* the total number of messages being indexed in the job
*/
currentJob: null,
listener: function(aStatus, aFolder, aJobNumber, aTotalJobNum, aItemNumber,
aTotalItemNum)
{
this.log.debug("Gloda Indexer Folder/Status: " + aFolder + "/" + aStatus);
this.log.debug("Gloda Indexer Job: " + aJobNumber + "/" + aTotalJobNum);
this.log.debug("Gloda Indexer Item: " + aItemNumber + "/" + aTotalItemNum);
if (aStatus == Gloda.kIndexerIdle)
{
if (this.currentJob)
this.onJobCompleted();
}
else
{
// If the job numbers have changed, the indexer has finished the job
// we were previously tracking, so convert the corresponding process
// into an event and start a new process to track the new job.
if (this.currentJob && aJobNumber != this.currentJob.jobNumber)
this.onJobCompleted();
// If we aren't tracking a job, either this is the first time we've been
// called or the last job we were tracking was completed. Either way,
// start tracking the new job.
if (!this.currentJob)
this.onJobBegun(aFolder, aJobNumber, aTotalItemNum);
this.onJobProgress(aFolder, aItemNumber, aTotalItemNum);
}
},
onJobBegun: function(aFolder, aJobNumber, aTotalItemNum) {
let displayText =
aFolder ? this.getString("indexingFolder").replace("#1", aFolder)
: this.getString("indexing");
let process = new nsActProcess(displayText, Gloda);
process.iconClass = "indexMail";
process.contextType = "account";
process.contextObj = aFolder;
process.addSubject(aFolder);
this.currentJob = {
folder: aFolder,
jobNumber: aJobNumber,
process: process,
startTime: new Date(),
totalItemNum: aTotalItemNum
};
this.activityMgr.addActivity(process);
},
onJobProgress: function(aFolder, aItemNumber, aTotalItemNum) {
this.currentJob.process.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
// The total number of items being processed in the job can change, as can
// the folder being processed, since we sometimes get notified about a job
// before it has determined these things, so we update them here.
this.currentJob.folder = aFolder;
this.currentJob.totalItemNum = aTotalItemNum;
let statusText;
if (aTotalItemNum == null) {
statusText = aFolder ? this.getString("indexingFolderStatusVague")
.replace("#1", aFolder)
: this.getString("indexingStatusVague");
}
else {
let percentComplete =
aTotalItemNum == 0 ? 100 : parseInt(aItemNumber / aTotalItemNum * 100);
// Note: we must replace the folder name placeholder last; otherwise,
// if the name happens to contain another one of the placeholders, we'll
// hork the name when replacing it.
statusText = this.getString(aFolder ? "indexingFolderStatusExact"
: "indexingStatusExact");
statusText = PluralForm.get(aTotalItemNum, statusText)
.replace("#1", aItemNumber)
.replace("#2", aTotalItemNum)
.replace("#3", percentComplete)
.replace("#4", aFolder);
}
this.currentJob.process.setProgress(statusText, aItemNumber, aTotalItemNum);
},
onJobCompleted: function() {
this.currentJob.process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
this.activityMgr.removeActivity(this.currentJob.process.id);
// We only create activity events when specific folders get indexed,
// since event-driven indexing jobs are too numerous.
// TODO: Aggregate event-driven indexing jobs into batches significant
// enough for us to create activity events for them.
if (this.currentJob.folder) {
// this.currentJob.totalItemNum might still be null at this point
// if we were first notified about the job before the indexer determined
// the number of messages to index and then it didn't find any to index.
let totalItemNum = this.currentJob.totalItemNum || 0;
// Note: we must replace the folder name placeholder last; otherwise,
// if the name happens to contain another one of the placeholders, we'll
// hork the name when replacing it.
let displayText = PluralForm.get(totalItemNum,
this.getString("indexedFolder"))
.replace("#1", totalItemNum)
.replace("#2", this.currentJob.folder);
let endTime = new Date();
let secondsElapsed = parseInt((endTime - this.currentJob.startTime)/1000);
let statusText = PluralForm.get(secondsElapsed,
this.getString("indexedFolderStatus"))
.replace("#1", secondsElapsed);
// XXX Should we really create an event for jobs that don't end up
// indexing any items? Perhaps it's not worth notifying users that we
// "indexed 0 messages" in a particular folder. On the other hand,
// maybe that is useful information (f.e. if they can't find a message
// in a folder and think it might be due to a bug in the indexer).
let event = new nsActEvent(displayText,
Gloda,
statusText,
this.currentJob.startTime,
endTime);
event.contextType = this.currentJob.contextType;
event.contextObj = this.currentJob.contextObj;
event.iconClass = "indexMail";
// Transfer subjects.
let subjects = this.currentJob.process.getSubjects({});
for each (let [, subject] in Iterator(subjects))
event.addSubject(subject);
this.activityMgr.addActivity(event);
}
this.currentJob = null;
}
};

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

@ -196,7 +196,7 @@ nsActivityProcess.prototype = {
},
setProgress: function(aStatusText, aWorkUnitsComplete, aTotalWorkUnits) {
this.percentComplete = 100.0 * aWorkUnitsComplete / aTotalWorkUnits;
this.percentComplete = parseInt(100 * aWorkUnitsComplete / aTotalWorkUnits);
this.workUnitComplete = aWorkUnitsComplete;
this.totalWorkUnits = aTotalWorkUnits;
this.lastStatusText = aStatusText;

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

@ -56,3 +56,24 @@ movedFolder=Moved folder #1 into folder #2
copiedFolder=Copied folder #1 into folder #2
# LOCALIZATION NOTE (renamedFolder): #1 and #2 are folder names
renamedFolder=Renamed folder #1 to #2
indexing=Indexing messages
# LOCALIZATION NOTE (indexingFolder): #1 is a folder name
indexingFolder=Indexing messages in #1
indexingStatusVague=Determining which messages to index
# LOCALIZATION NOTE (indexingFolderStatusVague): #1 is a folder name
indexingFolderStatusVague=Determining which messages to index in #1
# LOCALIZATION NOTE (indexingStatusExact):
# #1 is the number of the message currently being indexed
# #2 is the total number of messages being indexed
# #3 is the percentage of indexing that is complete
indexingStatusExact=Indexing #1 of #2 message;Indexing #1 of #2 messages (#3% complete)
# LOCALIZATION NOTE (indexingFolderStatusExact):
# #1 is the number of the message currently being indexed
# #2 is the total number of messages being indexed
# #3 is the percentage of indexing that is complete
# #4 is a folder name
indexingFolderStatusExact=Indexing #1 of #2 message in #4;Indexing #1 of #2 messages in #4 (#3% complete)
# LOCALIZATION NOTE (indexedFolder): #1 number of messages; #2 folder name
indexedFolder=Indexed #1 message in #2;Indexed #1 messages in #2
# LOCALIZATION NOTE (indexedFolderStatus): #1 number of seconds spent indexing
indexedFolderStatus=#1 second elapsed;#1 seconds elapsed

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

@ -386,6 +386,10 @@ var GlodaIndexer = {
this._perfTimer.cancel();
} catch (ex) {}
this._perfTimer = null;
// Remove listeners to avoid reference cycles on the off chance one of them
// holds a reference to the indexer object.
this._indexListeners = [];
this._indexerIsShutdown = true;
@ -946,8 +950,8 @@ var GlodaIndexer = {
else
status = Gloda.kIndexerRemoving;
let prettyName = (this._indexingFolder !== null) ?
this._indexingFolder.prettiestName : null;
prettyName = (this._indexingFolder !== null) ?
this._indexingFolder.prettiestName : null;
jobIndex = this._indexingJobCount-1;
jobTotal = this._indexingJobGoal;
@ -966,8 +970,13 @@ var GlodaIndexer = {
for (let iListener = this._indexListeners.length-1; iListener >= 0;
iListener--) {
let listener = this._indexListeners[iListener];
listener(status, prettyName, jobIndex, jobTotal, jobItemIndex,
jobItemGoal);
try {
listener(status, prettyName, jobIndex, jobTotal, jobItemIndex,
jobItemGoal);
}
catch(ex) {
this._log.error(ex);
}
}
},
@ -1391,7 +1400,8 @@ var GlodaIndexer = {
// try and get a job if we don't have one for the sake of the notification
if (this.indexing && (this._actualWorker === null))
this._hireJobWorker();
this._notifyListeners();
else
this._notifyListeners();
yield this.kWorkDone;
},
@ -1454,7 +1464,9 @@ var GlodaIndexer = {
else {
this._log.warning("Unknown job type: " + job.jobType);
}
this._notifyListeners();
if (generator) {
this._callbackHandle.push(generator);
return true;
@ -1575,7 +1587,10 @@ var GlodaIndexer = {
if (!this.shouldIndexFolder(this._indexingFolder))
yield this.kWorkDone;
// Make sure listeners get notified about this job.
this._notifyListeners();
// there is of course a cost to all this header investigation even if we
// don't do something. so we will yield with kWorkSync for every block.
const HEADER_CHECK_BLOCK_SIZE = 10;
@ -1656,26 +1671,27 @@ var GlodaIndexer = {
++numMessagesToIndex;
}
// We used up the iterator, get a new one.
this._indexerGetIterator();
aJob.goal = numMessagesToIndex;
// Pass 2: index the messages.
count = 0;
for (let msgHdr in this._indexingIterator) {
// per above, we want to periodically release control while doing all
// this header traversal/investigation.
if (++count % HEADER_CHECK_BLOCK_SIZE == 0) {
yield this.kWorkSync;
}
if (numMessagesToIndex > 0) {
// We used up the iterator, get a new one.
this._indexerGetIterator();
if (shouldIndexMessage(msgHdr)) {
++aJob.offset;
this._log.debug(">>> _indexMessage");
yield this._callbackHandle.pushAndGo(this._indexMessage(msgHdr,
this._callbackHandle));
this._log.debug("<<< _indexMessage");
// Pass 2: index the messages.
let count = 0;
for (let msgHdr in this._indexingIterator) {
// per above, we want to periodically release control while doing all
// this header traversal/investigation.
if (++count % HEADER_CHECK_BLOCK_SIZE == 0)
yield this.kWorkSync;
if (shouldIndexMessage(msgHdr)) {
++aJob.offset;
this._log.debug(">>> _indexMessage");
yield this._callbackHandle.pushAndGo(this._indexMessage(msgHdr,
this._callbackHandle));
this._log.debug("<<< _indexMessage");
}
}
}

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

@ -112,7 +112,6 @@ let Log4Moz = {
getConfiguredLogger: function(loggername, level, consoleLevel, dumpLevel) {
let log = Log4Moz.repository.getLogger(loggername);
let branch;
if (log._configured)
return log