engines/history.js

331 lines, 200 LOC, 62 covered (31%)

2 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 Mozilla.
17
 * Portions created by the Initial Developer are Copyright (C) 2008
18
 * the Initial Developer. All Rights Reserved.
19
 *
20
 * Contributor(s):
21
 *  Dan Mills <thunder@mozilla.com>
22
 *
23
 * Alternatively, the contents of this file may be used under the terms of
24
 * either the GNU General Public License Version 2 or later (the "GPL"), or
25
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26
 * in which case the provisions of the GPL or the LGPL are applicable instead
27
 * of those above. If you wish to allow use of your version of this file only
28
 * under the terms of either the GPL or the LGPL, and not to allow others to
29
 * use your version of this file under the terms of the MPL, indicate your
30
 * decision by deleting the provisions above and replace them with the notice
31
 * and other provisions required by the GPL or the LGPL. If you do not delete
32
 * the provisions above, a recipient may use your version of this file under
33
 * the terms of any one of the MPL, the GPL or the LGPL.
34
 *
35
 * ***** END LICENSE BLOCK ***** */
36
14 37
const EXPORTED_SYMBOLS = ['HistoryEngine'];
38
8 39
const Cc = Components.classes;
8 40
const Ci = Components.interfaces;
8 41
const Cu = Components.utils;
42
6 43
const GUID_ANNO = "weave/guid";
44
10 45
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 46
Cu.import("resource://weave/ext/Sync.js");
10 47
Cu.import("resource://weave/util.js");
10 48
Cu.import("resource://weave/engines.js");
10 49
Cu.import("resource://weave/stores.js");
10 50
Cu.import("resource://weave/trackers.js");
10 51
Cu.import("resource://weave/type_records/history.js");
52
53
// Create some helper functions to handle GUIDs
4 54
function setGUID(uri, guid) {
55
  if (arguments.length == 1)
56
    guid = Utils.makeGUID();
57
  Utils.anno(uri, GUID_ANNO, guid, "WITH_HISTORY");
58
  return guid;
59
}
4 60
function GUIDForUri(uri, create) {
61
  try {
62
    // Use the existing GUID if it exists
63
    return Utils.anno(uri, GUID_ANNO);
64
  }
65
  catch (ex) {
66
    // Give the uri a GUID if it doesn't have one
67
    if (create)
68
      return setGUID(uri);
69
  }
70
}
71
6 72
function HistoryEngine() {
14 73
  SyncEngine.call(this, "History");
74
}
4 75
HistoryEngine.prototype = {
6 76
  __proto__: SyncEngine.prototype,
4 77
  _recordObj: HistoryRec,
4 78
  _storeObj: HistoryStore,
4 79
  _trackerObj: HistoryTracker,
80
12 81
  _sync: Utils.batchSync("History", SyncEngine),
82
10 83
  _findDupe: function _findDupe(item) {
84
    return GUIDForUri(item.histUri);
85
  }
86
};
87
4 88
function HistoryStore(name) {
89
  Store.call(this, name);
90
}
4 91
HistoryStore.prototype = {
6 92
  __proto__: Store.prototype,
93
4 94
  get _hsvc() {
95
    let hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
96
      getService(Ci.nsINavHistoryService);
97
    hsvc.QueryInterface(Ci.nsIGlobalHistory2);
98
    hsvc.QueryInterface(Ci.nsIBrowserHistory);
99
    hsvc.QueryInterface(Ci.nsPIPlacesDatabase);
100
    this.__defineGetter__("_hsvc", function() hsvc);
101
    return hsvc;
102
  },
103
4 104
  get _db() {
105
    return this._hsvc.DBConnection;
106
  },
107
4 108
  get _visitStm() {
109
    this._log.trace("Creating SQL statement: _visitStm");
110
    let stm = this._db.createStatement(
111
      "SELECT visit_type type, visit_date date " +
112
      "FROM moz_historyvisits " +
113
      "WHERE place_id = (" +
114
        "SELECT id " +
115
        "FROM moz_places " +
116
        "WHERE url = :url) " +
117
      "ORDER BY date DESC LIMIT 10");
118
    this.__defineGetter__("_visitStm", function() stm);
119
    return stm;
120
  },
121
4 122
  get _urlStm() {
123
    this._log.trace("Creating SQL statement: _urlStm");
124
    let stm = this._db.createStatement(
125
      "SELECT url, title, frecency " +
126
      "FROM moz_places " +
127
      "WHERE id = (" +
128
        "SELECT place_id " +
129
        "FROM moz_annos " +
130
        "WHERE content = :guid AND anno_attribute_id = (" +
131
          "SELECT id " +
132
          "FROM moz_anno_attributes " +
133
          "WHERE name = '" + GUID_ANNO + "'))");
134
    this.__defineGetter__("_urlStm", function() stm);
135
    return stm;
136
  },
137
138
  // See bug 320831 for why we use SQL here
4 139
  _getVisits: function HistStore__getVisits(uri) {
140
    this._visitStm.params.url = uri;
141
    let [exec, execCb] = Sync.withCb(this._visitStm.executeAsync, this._visitStm);
142
    return exec({
143
      visits: [],
144
      handleResult: function handleResult(results) {
145
        let row;
146
        while ((row = results.getNextRow()) != null) {
147
          this.visits.push({
148
            date: row.getResultByName("date"),
149
            type: row.getResultByName("type")
150
          });
151
        }
152
      },
153
      handleError: function handleError(error) {
154
        execCb.throw(error);
155
      },
156
      handleCompletion: function handleCompletion(reason) {
157
        execCb(this.visits);
158
      }
159
    });
160
  },
161
162
  // See bug 468732 for why we use SQL here
4 163
  _findURLByGUID: function HistStore__findURLByGUID(guid) {
164
    this._urlStm.params.guid = guid;
165
    let [exec, execCb] = Sync.withCb(this._urlStm.executeAsync, this._urlStm);
166
    return exec({
167
      handleResult: function(results) {
168
        // Save the one result and its columns
169
        let row = results.getNextRow();
170
        this.urlInfo = {
171
          url: row.getResultByName("url"),
172
          title: row.getResultByName("title"),
173
          frecency: row.getResultByName("frecency"),
174
        };
175
      },
176
      handleError: function(error) {
177
        execCb.throw(error);
178
      },
179
      handleCompletion: function(reason) {
180
        execCb(this.urlInfo);
181
      }
182
    });
183
  },
184
4 185
  changeItemID: function HStore_changeItemID(oldID, newID) {
186
    setGUID(this._findURLByGUID(oldID).url, newID);
187
  },
188
189
4 190
  getAllIDs: function HistStore_getAllIDs() {
191
    let query = this._hsvc.getNewQuery(),
192
        options = this._hsvc.getNewQueryOptions();
193
194
    query.minVisits = 1;
195
    options.maxResults = 1000;
196
    options.sortingMode = options.SORT_BY_DATE_DESCENDING;
197
    options.queryType = options.QUERY_TYPE_HISTORY;
198
199
    let root = this._hsvc.executeQuery(query, options).root;
200
    root.QueryInterface(Ci.nsINavHistoryQueryResultNode);
201
    root.containerOpen = true;
202
203
    let items = {};
204
    for (let i = 0; i < root.childCount; i++) {
205
      let item = root.getChild(i);
206
      let guid = GUIDForUri(item.uri, true);
207
      items[guid] = item.uri;
208
    }
209
    return items;
210
  },
211
4 212
  create: function HistStore_create(record) {
213
    // Add the url and set the GUID
214
    this.update(record);
215
    setGUID(record.histUri, record.id);
216
  },
217
4 218
  remove: function HistStore_remove(record) {
219
    let page = this._findURLByGUID(record.id)
220
    if (page == null) {
221
      this._log.debug("Page already removed: " + record.id);
222
      return;
223
    }
224
225
    let uri = Utils.makeURI(page.url);
226
    Svc.History.removePage(uri);
227
    this._log.trace("Removed page: " + [record.id, page.url, page.title]);
228
  },
229
4 230
  update: function HistStore_update(record) {
231
    this._log.trace("  -> processing history entry: " + record.histUri);
232
233
    let uri = Utils.makeURI(record.histUri);
234
    if (!uri) {
235
      this._log.warn("Attempted to process invalid URI, skipping");
236
      throw "invalid URI in record";
237
    }
238
    let curvisits = [];
239
    if (this.urlExists(uri))
240
      curvisits = this._getVisits(record.histUri);
241
242
    // Add visits if there's no local visit with the same date
243
    for each (let {date, type} in record.visits)
244
      if (curvisits.every(function(cur) cur.date != date))
245
        Svc.History.addVisit(uri, date, null, type, type == 5 || type == 6, 0);
246
247
    this._hsvc.setPageTitle(uri, record.title);
248
  },
249
4 250
  itemExists: function HistStore_itemExists(id) {
251
    if (this._findURLByGUID(id))
252
      return true;
253
    return false;
254
  },
255
4 256
  urlExists: function HistStore_urlExists(url) {
257
    if (typeof(url) == "string")
258
      url = Utils.makeURI(url);
259
    // Don't call isVisited on a null URL to work around crasher bug 492442.
260
    return url ? this._hsvc.isVisited(url) : false;
261
  },
262
4 263
  createRecord: function createRecord(guid) {
264
    let foo = this._findURLByGUID(guid);
265
    let record = new HistoryRec();
266
    if (foo) {
267
      record.histUri = foo.url;
268
      record.title = foo.title;
269
      record.sortindex = foo.frecency;
270
      record.visits = this._getVisits(record.histUri);
271
    }
272
    else
273
      record.deleted = true;
274
275
    return record;
276
  },
277
10 278
  wipe: function HistStore_wipe() {
279
    this._hsvc.removeAllPages();
280
  }
281
};
282
6 283
function HistoryTracker(name) {
12 284
  Tracker.call(this, name);
16 285
  Svc.History.addObserver(this, false);
286
}
4 287
HistoryTracker.prototype = {
6 288
  __proto__: Tracker.prototype,
289
8 290
  QueryInterface: XPCOMUtils.generateQI([
8 291
    Ci.nsINavHistoryObserver,
12 292
    Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS
293
  ]),
294
4 295
  onBeginUpdateBatch: function HT_onBeginUpdateBatch() {},
4 296
  onEndUpdateBatch: function HT_onEndUpdateBatch() {},
4 297
  onPageChanged: function HT_onPageChanged() {},
4 298
  onTitleChanged: function HT_onTitleChanged() {},
299
300
  /* Every add or remove is worth 1 point.
301
   * Clearing the whole history is worth 50 points (see below)
302
   */
4 303
  _upScore: function BMT__upScore() {
304
    this.score += 1;
305
  },
306
4 307
  onVisit: function HT_onVisit(uri, vid, time, session, referrer, trans) {
308
    if (this.ignoreAll)
309
      return;
310
    this._log.trace("onVisit: " + uri.spec);
311
    if (this.addChangedID(GUIDForUri(uri, true)))
312
      this._upScore();
313
  },
4 314
  onDeleteVisits: function onDeleteVisits() {
315
  },
4 316
  onPageExpired: function HT_onPageExpired(uri, time, entry) {
317
  },
4 318
  onBeforeDeleteURI: function onBeforeDeleteURI(uri) {
319
    if (this.ignoreAll)
320
      return;
321
    this._log.trace("onBeforeDeleteURI: " + uri.spec);
322
    if (this.addChangedID(GUIDForUri(uri, true)))
323
      this._upScore();
324
  },
4 325
  onDeleteURI: function HT_onDeleteURI(uri) {
326
  },
10 327
  onClearHistory: function HT_onClearHistory() {
328
    this._log.trace("onClearHistory");
329
    this.score += 500;
330
  }
2 331
};