summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/activity/modules/glodaIndexer.jsm
blob: 5307d5cefa439ea1f550668e657b7c56ca9531b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = ["glodaIndexerActivity"];

var nsActProcess = Components.Constructor(
  "@mozilla.org/activity-process;1",
  "nsIActivityProcess",
  "init"
);
var nsActEvent = Components.Constructor(
  "@mozilla.org/activity-event;1",
  "nsIActivityEvent",
  "init"
);

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PluralForm: "resource://gre/modules/PluralForm.sys.mjs",
});

XPCOMUtils.defineLazyModuleGetters(lazy, {
  Gloda: "resource:///modules/gloda/GlodaPublic.jsm",
  GlodaConstants: "resource:///modules/gloda/GlodaConstants.jsm",
  GlodaIndexer: "resource:///modules/gloda/GlodaIndexer.jsm",
});

/**
 * Gloda message indexer feedback.
 */
var glodaIndexerActivity = {
  get log() {
    delete this.log;
    return (this.log = console.createInstance({
      prefix: "mail.activity",
      maxLogLevel: "Warn",
      maxLogLevelPref: "mail.activity.loglevel",
    }));
  },

  get activityMgr() {
    delete this.activityMgr;
    return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
      Ci.nsIActivityManager
    ));
  },

  get bundle() {
    delete this.bundle;
    return (this.bundle = Services.strings.createBundle(
      "chrome://messenger/locale/activity.properties"
    ));
  },

  getString(stringName) {
    try {
      return this.bundle.GetStringFromName(stringName);
    } catch (e) {
      this.log.error("error trying to get a string called: " + stringName);
      throw e;
    }
  },

  init() {
    // 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(...aArgs) {
      glodaIndexerActivity.listener(...aArgs);
    }
    lazy.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
   *   jobType      {String}
   *                The IndexinbJob jobType (ex: "folder", "folderCompact")
   */
  currentJob: null,

  listener(aStatus, aFolder, aJobNumber, aItemNumber, aTotalItemNum, aJobType) {
    this.log.debug("Gloda Indexer Folder/Status: " + aFolder + "/" + aStatus);
    this.log.debug("Gloda Indexer Job: " + aJobNumber);
    this.log.debug("Gloda Indexer Item: " + aItemNumber + "/" + aTotalItemNum);

    if (aStatus == lazy.GlodaConstants.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, aJobType);
      }

      // If there is only one item, don't bother creating a progress item.
      if (aTotalItemNum != 1) {
        this.onJobProgress(aFolder, aItemNumber, aTotalItemNum);
      }
    }
  },

  onJobBegun(aFolder, aJobNumber, aTotalItemNum, aJobType) {
    let displayText = aFolder
      ? this.getString("indexingFolder").replace("#1", aFolder)
      : this.getString("indexing");
    let process = new nsActProcess(displayText, lazy.Gloda);

    process.iconClass = "indexMail";
    process.contextType = "account";
    process.contextObj = aFolder;
    process.addSubject(aFolder);

    this.currentJob = {
      folder: aFolder,
      jobNumber: aJobNumber,
      process,
      startTime: new Date(),
      totalItemNum: aTotalItemNum,
      jobType: aJobType,
    };

    this.activityMgr.addActivity(process);
  },

  onJobProgress(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 = lazy.PluralForm.get(aTotalItemNum, statusText)
        .replace("#1", aItemNumber + 1)
        .replace("#2", aTotalItemNum)
        .replace("#3", percentComplete)
        .replace("#4", aFolder);
    }

    this.currentJob.process.setProgress(statusText, aItemNumber, aTotalItemNum);
  },

  onJobCompleted() {
    this.currentJob.process.state = Ci.nsIActivityProcess.STATE_COMPLETED;

    this.activityMgr.removeActivity(this.currentJob.process.id);

    // 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;

    // We only create activity events when specific folders get indexed,
    // since event-driven indexing jobs are too numerous.  We also only create
    // them when we ended up indexing something in the folder, since otherwise
    // we'd spam the activity manager with too many "indexed 0 messages" items
    // that aren't useful enough to justify their presence in the manager.
    // TODO: Aggregate event-driven indexing jobs into batches significant
    // enough for us to create activity events for them.
    if (
      this.currentJob.jobType == "folder" &&
      this.currentJob.folder &&
      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 = lazy.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 = lazy.PluralForm.get(
        secondsElapsed,
        this.getString("indexedFolderStatus")
      ).replace("#1", secondsElapsed);

      let event = new nsActEvent(
        displayText,
        lazy.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 (let subject of subjects) {
        event.addSubject(subject);
      }

      this.activityMgr.addActivity(event);
    }

    this.currentJob = null;
  },
};