~ubuntu-branches/ubuntu/trusty/enigmail/trusty-updates

« back to all changes in this revision

Viewing changes to services/sync/modules/engines/history.js

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2011-06-07 14:35:53 UTC
  • mfrom: (0.12.1 upstream)
  • Revision ID: package-import@ubuntu.com-20110607143553-fbgqhhvh8g8h6j1y
Tags: 2:1.2~a2~cvs20110606t2200-0ubuntu1
* Update to latest trunk snapshot for Thunderbird beta compat

* Remove build/pgo/profileserver.py from debian/clean. The new build
  system has a target depending on this
  - update debian/clean
* Drop debian/patches/autoconf.diff, just generate this at build time
* Refresh debian/patches/build_system_dont_link_libxul.diff
* libipc seems to be renamed to libipc-pipe. Fix genxpi and chrome.manifest
  to fix this 
  - add debian/patches/ipc-pipe_rename.diff
  - update debian/patches/series
* The makefiles in extensions/enigmail/ipc have an incorrect DEPTH
  attribute. Fix this so that they can find the rest of the build system
  - add debian/patches/makefile_depth.diff
  - update debian/patches/series
* Drop debian/patches/makefile-in-empty-xpcom-fix.diff - fixed in the
  current version
* Don't register a class ID multiple times, as this breaks enigmail entirely
  - add debian/patches/dont_register_cids_multiple_times.diff
  - update debian/patches/series
* Look for the Thunderbird 5 SDK
  - update debian/rules
  - update debian/control
* Run autoconf2.13 at build time
  - update debian/rules
  - update debian/control
* Add useless mesa-common-dev build-dep, just to satisfy the build system.
  We should just patch this out entirely really, but that's for another upload
  - update debian/control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ***** BEGIN LICENSE BLOCK *****
 
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
3
 *
 
4
 * The contents of this file are subject to the Mozilla Public License Version
 
5
 * 1.1 (the "License"); you may not use this file except in compliance with
 
6
 * the License. You may obtain a copy of the License at
 
7
 * http://www.mozilla.org/MPL/
 
8
 *
 
9
 * Software distributed under the License is distributed on an "AS IS" basis,
 
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
11
 * for the specific language governing rights and limitations under the
 
12
 * License.
 
13
 *
 
14
 * The Original Code is Weave
 
15
 *
 
16
 * The Initial Developer of the Original Code is
 
17
 * the Mozilla Foundation.
 
18
 * Portions created by the Initial Developer are Copyright (C) 2008
 
19
 * the Initial Developer. All Rights Reserved.
 
20
 *
 
21
 * Contributor(s):
 
22
 *   Dan Mills <thunder@mozilla.com>
 
23
 *   Philipp von Weitershausen <philipp@weitershausen.de>
 
24
 *   Richard Newman <rnewman@mozilla.com>
 
25
 *
 
26
 * Alternatively, the contents of this file may be used under the terms of
 
27
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
28
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
29
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
30
 * of those above. If you wish to allow use of your version of this file only
 
31
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
32
 * use your version of this file under the terms of the MPL, indicate your
 
33
 * decision by deleting the provisions above and replace them with the notice
 
34
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
35
 * the provisions above, a recipient may use your version of this file under
 
36
 * the terms of any one of the MPL, the GPL or the LGPL.
 
37
 *
 
38
 * ***** END LICENSE BLOCK ***** */
 
39
 
 
40
const EXPORTED_SYMBOLS = ['HistoryEngine', 'HistoryRec'];
 
41
 
 
42
const Cc = Components.classes;
 
43
const Ci = Components.interfaces;
 
44
const Cu = Components.utils;
 
45
const Cr = Components.results;
 
46
 
 
47
const HISTORY_TTL = 5184000; // 60 days
 
48
const TOPIC_UPDATEPLACES_COMPLETE = "places-updatePlaces-complete";
 
49
 
 
50
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
51
Cu.import("resource://services-sync/constants.js");
 
52
Cu.import("resource://services-sync/engines.js");
 
53
Cu.import("resource://services-sync/record.js");
 
54
Cu.import("resource://services-sync/util.js");
 
55
Cu.import("resource://services-sync/log4moz.js");
 
56
 
 
57
function HistoryRec(collection, id) {
 
58
  CryptoWrapper.call(this, collection, id);
 
59
}
 
60
HistoryRec.prototype = {
 
61
  __proto__: CryptoWrapper.prototype,
 
62
  _logName: "Record.History",
 
63
  ttl: HISTORY_TTL
 
64
};
 
65
 
 
66
Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]);
 
67
 
 
68
 
 
69
function HistoryEngine() {
 
70
  SyncEngine.call(this, "History");
 
71
}
 
72
HistoryEngine.prototype = {
 
73
  __proto__: SyncEngine.prototype,
 
74
  _recordObj: HistoryRec,
 
75
  _storeObj: HistoryStore,
 
76
  _trackerObj: HistoryTracker,
 
77
  downloadLimit: MAX_HISTORY_DOWNLOAD,
 
78
  applyIncomingBatchSize: HISTORY_STORE_BATCH_SIZE,
 
79
 
 
80
  _findDupe: function _findDupe(item) {
 
81
    return this._store.GUIDForUri(item.histUri);
 
82
  }
 
83
};
 
84
 
 
85
function HistoryStore(name) {
 
86
  Store.call(this, name);
 
87
 
 
88
  // Explicitly nullify our references to our cached services so we don't leak
 
89
  Svc.Obs.add("places-shutdown", function() {
 
90
    for each ([query, stmt] in Iterator(this._stmts))
 
91
      stmt.finalize();
 
92
    this.__hsvc = null;
 
93
    this._stmts = [];
 
94
  }, this);
 
95
}
 
96
HistoryStore.prototype = {
 
97
  __proto__: Store.prototype,
 
98
 
 
99
  __hsvc: null,
 
100
  get _hsvc() {
 
101
    if (!this.__hsvc)
 
102
      this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
 
103
                    getService(Ci.nsINavHistoryService).
 
104
                    QueryInterface(Ci.nsIGlobalHistory2).
 
105
                    QueryInterface(Ci.nsIBrowserHistory).
 
106
                    QueryInterface(Ci.nsPIPlacesDatabase);
 
107
    return this.__hsvc;
 
108
  },
 
109
 
 
110
  __asyncHistory: null,
 
111
  get _asyncHistory() {
 
112
    if (!this.__asyncHistory) {
 
113
      this.__asyncHistory = Cc["@mozilla.org/browser/history;1"]
 
114
                              .getService(Ci.mozIAsyncHistory);
 
115
    }
 
116
    return this.__asyncHistory;
 
117
  },
 
118
 
 
119
  _stmts: {},
 
120
  _getStmt: function(query) {
 
121
    if (query in this._stmts)
 
122
      return this._stmts[query];
 
123
 
 
124
    this._log.trace("Creating SQL statement: " + query);
 
125
    return this._stmts[query] = this._hsvc.DBConnection
 
126
                                    .createAsyncStatement(query);
 
127
  },
 
128
 
 
129
  get _setGUIDStm() {
 
130
    return this._getStmt(
 
131
      "UPDATE moz_places " +
 
132
      "SET guid = :guid " +
 
133
      "WHERE url = :page_url");
 
134
  },
 
135
 
 
136
  // Some helper functions to handle GUIDs
 
137
  setGUID: function setGUID(uri, guid) {
 
138
    uri = uri.spec ? uri.spec : uri;
 
139
 
 
140
    if (!guid)
 
141
      guid = Utils.makeGUID();
 
142
 
 
143
    let stmt = this._setGUIDStm;
 
144
    stmt.params.guid = guid;
 
145
    stmt.params.page_url = uri;
 
146
    Utils.queryAsync(stmt);
 
147
    return guid;
 
148
  },
 
149
 
 
150
  get _guidStm() {
 
151
    return this._getStmt(
 
152
      "SELECT guid " +
 
153
      "FROM moz_places " +
 
154
      "WHERE url = :page_url");
 
155
  },
 
156
  _guidCols: ["guid"],
 
157
 
 
158
  GUIDForUri: function GUIDForUri(uri, create) {
 
159
    let stm = this._guidStm;
 
160
    stm.params.page_url = uri.spec ? uri.spec : uri;
 
161
 
 
162
    // Use the existing GUID if it exists
 
163
    let result = Utils.queryAsync(stm, this._guidCols)[0];
 
164
    if (result && result.guid)
 
165
      return result.guid;
 
166
 
 
167
    // Give the uri a GUID if it doesn't have one
 
168
    if (create)
 
169
      return this.setGUID(uri);
 
170
  },
 
171
 
 
172
  get _visitStm() {
 
173
    return this._getStmt(
 
174
      "SELECT visit_type type, visit_date date " +
 
175
      "FROM moz_historyvisits " +
 
176
      "WHERE place_id = (SELECT id FROM moz_places WHERE url = :url) " +
 
177
      "ORDER BY date DESC LIMIT 10");
 
178
  },
 
179
  _visitCols: ["date", "type"],
 
180
 
 
181
  get _urlStm() {
 
182
    return this._getStmt(
 
183
      "SELECT url, title, frecency " +
 
184
      "FROM moz_places " +
 
185
      "WHERE guid = :guid");
 
186
  },
 
187
  _urlCols: ["url", "title", "frecency"],
 
188
 
 
189
  get _allUrlStm() {
 
190
    return this._getStmt(
 
191
      "SELECT url " +
 
192
      "FROM moz_places " +
 
193
      "WHERE last_visit_date > :cutoff_date " +
 
194
      "ORDER BY frecency DESC " +
 
195
      "LIMIT :max_results");
 
196
  },
 
197
  _allUrlCols: ["url"],
 
198
 
 
199
  // See bug 320831 for why we use SQL here
 
200
  _getVisits: function HistStore__getVisits(uri) {
 
201
    this._visitStm.params.url = uri;
 
202
    return Utils.queryAsync(this._visitStm, this._visitCols);
 
203
  },
 
204
 
 
205
  // See bug 468732 for why we use SQL here
 
206
  _findURLByGUID: function HistStore__findURLByGUID(guid) {
 
207
    this._urlStm.params.guid = guid;
 
208
    return Utils.queryAsync(this._urlStm, this._urlCols)[0];
 
209
  },
 
210
 
 
211
  changeItemID: function HStore_changeItemID(oldID, newID) {
 
212
    this.setGUID(this._findURLByGUID(oldID).url, newID);
 
213
  },
 
214
 
 
215
 
 
216
  getAllIDs: function HistStore_getAllIDs() {
 
217
    // Only get places visited within the last 30 days (30*24*60*60*1000ms)
 
218
    this._allUrlStm.params.cutoff_date = (Date.now() - 2592000000) * 1000;
 
219
    this._allUrlStm.params.max_results = MAX_HISTORY_UPLOAD;
 
220
 
 
221
    let urls = Utils.queryAsync(this._allUrlStm, this._allUrlCols);
 
222
    let self = this;
 
223
    return urls.reduce(function(ids, item) {
 
224
      ids[self.GUIDForUri(item.url, true)] = item.url;
 
225
      return ids;
 
226
    }, {});
 
227
  },
 
228
 
 
229
  applyIncomingBatch: function applyIncomingBatch(records) {
 
230
    let failed = [];
 
231
 
 
232
    // Convert incoming records to mozIPlaceInfo objects. Some records can be
 
233
    // ignored or handled directly, so we're rewriting the array in-place.
 
234
    let i, k;
 
235
    for (i = 0, k = 0; i < records.length; i++) {
 
236
      let record = records[k] = records[i];
 
237
      let shouldApply;
 
238
 
 
239
      // This is still synchronous I/O for now.
 
240
      try {
 
241
        if (record.deleted) {
 
242
          // Consider using nsIBrowserHistory::removePages() here.
 
243
          this.remove(record);
 
244
          // No further processing needed. Remove it from the list.
 
245
          shouldApply = false;
 
246
        } else {
 
247
          shouldApply = this._recordToPlaceInfo(record);
 
248
        }
 
249
      } catch(ex) {
 
250
        failed.push(record.id);
 
251
        shouldApply = false;
 
252
      }
 
253
 
 
254
      if (shouldApply) {
 
255
        k += 1;
 
256
      }
 
257
    }
 
258
    records.length = k; // truncate array
 
259
 
 
260
    // Nothing to do.
 
261
    if (!records.length) {
 
262
      return failed;
 
263
    }
 
264
 
 
265
    let cb = Utils.makeSyncCallback();
 
266
    let onPlace = function onPlace(result, placeInfo) {
 
267
      if (!Components.isSuccessCode(result)) {
 
268
        failed.push(placeInfo.guid);
 
269
      }
 
270
    };
 
271
    let onComplete = function onComplete(subject, topic, data) {
 
272
      Svc.Obs.remove(TOPIC_UPDATEPLACES_COMPLETE, onComplete);
 
273
      cb();
 
274
    };
 
275
    Svc.Obs.add(TOPIC_UPDATEPLACES_COMPLETE, onComplete);
 
276
    this._asyncHistory.updatePlaces(records, onPlace);
 
277
    Utils.waitForSyncCallback(cb);
 
278
    return failed;
 
279
  },
 
280
 
 
281
  /**
 
282
   * Converts a Sync history record to a mozIPlaceInfo.
 
283
   * 
 
284
   * Throws if an invalid record is encountered (invalid URI, etc.),
 
285
   * returns true if the record is to be applied, false otherwise
 
286
   * (no visits to add, etc.),
 
287
   */
 
288
  _recordToPlaceInfo: function _recordToPlaceInfo(record) {
 
289
    // Sort out invalid URIs and ones Places just simply doesn't want.
 
290
    record.uri = Utils.makeURI(record.histUri);
 
291
    if (!record.uri) {
 
292
      this._log.warn("Attempted to process invalid URI, skipping.");
 
293
      throw "Invalid URI in record";
 
294
    }
 
295
 
 
296
    if (!Utils.checkGUID(record.id)) {
 
297
      this._log.warn("Encountered record with invalid GUID: " + record.id);
 
298
      return false;
 
299
    }
 
300
    record.guid = record.id;
 
301
 
 
302
    if (!this._hsvc.canAddURI(record.uri)) {
 
303
      this._log.trace("Ignoring record " + record.id + " with URI "
 
304
                      + record.uri.spec + ": can't add this URI.");
 
305
      return false;
 
306
    }
 
307
 
 
308
    // We dupe visits by date and type. So an incoming visit that has
 
309
    // the same timestamp and type as a local one won't get applied.
 
310
    // To avoid creating new objects, we rewrite the query result so we
 
311
    // can simply check for containment below.
 
312
    let curVisits = this._getVisits(record.histUri);
 
313
    for (let i = 0; i < curVisits.length; i++) {
 
314
      curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
 
315
    }
 
316
 
 
317
    // Walk through the visits, make sure we have sound data, and eliminate
 
318
    // dupes. The latter is done by rewriting the array in-place.
 
319
    let k;
 
320
    for (i = 0, k = 0; i < record.visits.length; i++) {
 
321
      let visit = record.visits[k] = record.visits[i];
 
322
 
 
323
      if (!visit.date || typeof visit.date != "number") {
 
324
        this._log.warn("Encountered record with invalid visit date: "
 
325
                       + visit.date);
 
326
        throw "Visit has no date!";
 
327
      }
 
328
      if (!visit.type || !(visit.type >= Svc.History.TRANSITION_LINK &&
 
329
                           visit.type <= Svc.History.TRANSITION_FRAMED_LINK)) {
 
330
        this._log.warn("Encountered record with invalid visit type: "
 
331
                       + visit.type);
 
332
        throw "Invalid visit type!";
 
333
      }
 
334
      // Dates need to be integers
 
335
      visit.date = Math.round(visit.date);
 
336
 
 
337
      if (curVisits.indexOf(visit.date + "," + visit.type) != -1) {
 
338
        // Visit is a dupe, don't increment 'k' so the element will be
 
339
        // overwritten.
 
340
        continue;
 
341
      }
 
342
      visit.visitDate = visit.date;
 
343
      visit.transitionType = visit.type;
 
344
      k += 1;
 
345
    }
 
346
    record.visits.length = k; // truncate array
 
347
 
 
348
    // No update if there aren't any visits to apply.
 
349
    // mozIAsyncHistory::updatePlaces() wants at least one visit.
 
350
    // In any case, the only thing we could change would be the title
 
351
    // and that shouldn't change without a visit.
 
352
    if (!record.visits.length) {
 
353
      this._log.trace("Ignoring record " + record.id + " with URI "
 
354
                      + record.uri.spec + ": no visits to add.");
 
355
      return false;
 
356
    }
 
357
 
 
358
    return true;
 
359
  },
 
360
 
 
361
  remove: function HistStore_remove(record) {
 
362
    let page = this._findURLByGUID(record.id);
 
363
    if (page == null) {
 
364
      this._log.debug("Page already removed: " + record.id);
 
365
      return;
 
366
    }
 
367
 
 
368
    let uri = Utils.makeURI(page.url);
 
369
    Svc.History.removePage(uri);
 
370
    this._log.trace("Removed page: " + [record.id, page.url, page.title]);
 
371
  },
 
372
 
 
373
  itemExists: function HistStore_itemExists(id) {
 
374
    if (this._findURLByGUID(id))
 
375
      return true;
 
376
    return false;
 
377
  },
 
378
 
 
379
  urlExists: function HistStore_urlExists(url) {
 
380
    if (typeof(url) == "string")
 
381
      url = Utils.makeURI(url);
 
382
    // Don't call isVisited on a null URL to work around crasher bug 492442.
 
383
    return url ? this._hsvc.isVisited(url) : false;
 
384
  },
 
385
 
 
386
  createRecord: function createRecord(id, collection) {
 
387
    let foo = this._findURLByGUID(id);
 
388
    let record = new HistoryRec(collection, id);
 
389
    if (foo) {
 
390
      record.histUri = foo.url;
 
391
      record.title = foo.title;
 
392
      record.sortindex = foo.frecency;
 
393
      record.visits = this._getVisits(record.histUri);
 
394
    }
 
395
    else
 
396
      record.deleted = true;
 
397
 
 
398
    return record;
 
399
  },
 
400
 
 
401
  wipe: function HistStore_wipe() {
 
402
    this._hsvc.removeAllPages();
 
403
  }
 
404
};
 
405
 
 
406
function HistoryTracker(name) {
 
407
  Tracker.call(this, name);
 
408
  Svc.Obs.add("weave:engine:start-tracking", this);
 
409
  Svc.Obs.add("weave:engine:stop-tracking", this);
 
410
}
 
411
HistoryTracker.prototype = {
 
412
  __proto__: Tracker.prototype,
 
413
 
 
414
  _enabled: false,
 
415
  observe: function observe(subject, topic, data) {
 
416
    switch (topic) {
 
417
      case "weave:engine:start-tracking":
 
418
        if (!this._enabled) {
 
419
          Svc.History.addObserver(this, true);
 
420
          this._enabled = true;
 
421
        }
 
422
        break;
 
423
      case "weave:engine:stop-tracking":
 
424
        if (this._enabled) {
 
425
          Svc.History.removeObserver(this);
 
426
          this._enabled = false;
 
427
        }
 
428
        break;
 
429
    }
 
430
  },
 
431
 
 
432
  _GUIDForUri: function _GUIDForUri(uri, create) {
 
433
    // Isn't indirection fun...
 
434
    return Engines.get("history")._store.GUIDForUri(uri, create);
 
435
  },
 
436
 
 
437
  QueryInterface: XPCOMUtils.generateQI([
 
438
    Ci.nsINavHistoryObserver,
 
439
    Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS,
 
440
    Ci.nsISupportsWeakReference
 
441
  ]),
 
442
 
 
443
  onBeginUpdateBatch: function HT_onBeginUpdateBatch() {},
 
444
  onEndUpdateBatch: function HT_onEndUpdateBatch() {},
 
445
  onPageChanged: function HT_onPageChanged() {},
 
446
  onTitleChanged: function HT_onTitleChanged() {},
 
447
 
 
448
  /* Every add or remove is worth 1 point.
 
449
   * Clearing the whole history is worth 50 points (see below)
 
450
   */
 
451
  _upScore: function BMT__upScore() {
 
452
    this.score += 1;
 
453
  },
 
454
 
 
455
  onVisit: function HT_onVisit(uri, vid, time, session, referrer, trans) {
 
456
    if (this.ignoreAll)
 
457
      return;
 
458
    this._log.trace("onVisit: " + uri.spec);
 
459
    let self = this;
 
460
    Utils.delay(function() {
 
461
      if (self.addChangedID(self._GUIDForUri(uri, true))) {
 
462
        self._upScore();
 
463
      }
 
464
    }, 0);
 
465
  },
 
466
  onDeleteVisits: function onDeleteVisits() {
 
467
  },
 
468
  onPageExpired: function HT_onPageExpired(uri, time, entry) {
 
469
  },
 
470
  onBeforeDeleteURI: function onBeforeDeleteURI(uri) {
 
471
    if (this.ignoreAll)
 
472
      return;
 
473
    this._log.trace("onBeforeDeleteURI: " + uri.spec);
 
474
    let self = this;
 
475
    if (this.addChangedID(this._GUIDForUri(uri, true))) {
 
476
      this._upScore();
 
477
    }
 
478
  },
 
479
  onDeleteURI: function HT_onDeleteURI(uri) {
 
480
  },
 
481
  onClearHistory: function HT_onClearHistory() {
 
482
    this._log.trace("onClearHistory");
 
483
    this.score += 500;
 
484
  }
 
485
};