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:
Родитель
bfcb620735
Коммит
a9e842ca21
|
@ -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
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче