1
/* ***** BEGIN LICENSE BLOCK *****
2
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
14
* The Original Code is Bookmarks Sync.
16
* The Initial Developer of the Original Code is Mozilla.
17
* Portions created by the Initial Developer are Copyright (C) 2007
18
* the Initial Developer. All Rights Reserved.
21
* Dan Mills <thunder@mozilla.com>
22
* Jono DiCarlo <jdicarlo@mozilla.org>
23
* Anant Narayanan <anant@kix.in>
25
* Alternatively, the contents of this file may be used under the terms of
26
* either the GNU General Public License Version 2 or later (the "GPL"), or
27
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
* in which case the provisions of the GPL or the LGPL are applicable instead
29
* of those above. If you wish to allow use of your version of this file only
30
* under the terms of either the GPL or the LGPL, and not to allow others to
31
* use your version of this file under the terms of the MPL, indicate your
32
* decision by deleting the provisions above and replace them with the notice
33
* and other provisions required by the GPL or the LGPL. If you do not delete
34
* the provisions above, a recipient may use your version of this file under
35
* the terms of any one of the MPL, the GPL or the LGPL.
37
* ***** END LICENSE BLOCK ***** */
39
const EXPORTED_SYMBOLS = ['BookmarksEngine', 'BookmarksSharingManager'];
41
const Cc = Components.classes;
42
const Ci = Components.interfaces;
43
const Cu = Components.utils;
45
const PARENT_ANNO = "weave/parent";
46
const PREDECESSOR_ANNO = "weave/predecessor";
47
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
50
Cu.import("resource://gre/modules/PlacesUtils.jsm");
53
Cu.import("resource://gre/modules/utils.js");
55
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
56
Cu.import("resource://weave/ext/Observers.js");
57
Cu.import("resource://weave/util.js");
58
Cu.import("resource://weave/engines.js");
59
Cu.import("resource://weave/stores.js");
60
Cu.import("resource://weave/trackers.js");
61
Cu.import("resource://weave/type_records/bookmark.js");
63
function archiveBookmarks() {
64
// Some nightly builds of 3.7 don't have this function
66
PlacesUtils.archiveBookmarksFile(null, true);
71
// Lazily initialize the special top level folders
73
[["menu", "bookmarksMenuFolder"],
74
["places", "placesRoot"],
75
["tags", "tagsFolder"],
76
["toolbar", "toolbarFolder"],
77
["unfiled", "unfiledBookmarksFolder"],
78
].forEach(function([guid, placeName]) {
79
Utils.lazy2(kSpecialIds, guid, function() Svc.Bookmark[placeName]);
81
Utils.lazy2(kSpecialIds, "mobile", function() {
82
// Use the (one) mobile root if it already exists
83
let anno = "mobile/bookmarksRoot";
84
let root = Svc.Annos.getItemsWithAnnotation(anno, {});
88
// Create the special mobile folder to store mobile bookmarks
89
let mobile = Svc.Bookmark.createFolder(Svc.Bookmark.placesRoot, "mobile", -1);
90
Utils.anno(mobile, anno, 1);
94
// Create some helper functions to convert GUID/ids
95
function idForGUID(guid) {
96
if (guid in kSpecialIds)
97
return kSpecialIds[guid];
98
return Svc.Bookmark.getItemIdForGUID(guid);
100
function GUIDForId(placeId) {
101
for (let [guid, id] in Iterator(kSpecialIds))
104
return Svc.Bookmark.getItemGUID(placeId);
107
function BookmarksEngine() {
108
SyncEngine.call(this, "Bookmarks");
109
this._handleImport();
111
BookmarksEngine.prototype = {
112
__proto__: SyncEngine.prototype,
113
_recordObj: PlacesItem,
114
_storeObj: BookmarksStore,
115
_trackerObj: BookmarksTracker,
117
_handleImport: function _handleImport() {
118
Observers.add("bookmarks-restore-begin", function() {
119
this._log.debug("Ignoring changes from importing bookmarks");
120
this._tracker.ignoreAll = true;
123
Observers.add("bookmarks-restore-success", function() {
124
this._log.debug("Tracking all items on successful import");
125
this._tracker.ignoreAll = false;
127
// Mark all the items as changed so they get uploaded
128
for (let id in this._store.getAllIDs())
129
this._tracker.addChangedID(id);
132
Observers.add("bookmarks-restore-failed", function() {
133
this._tracker.ignoreAll = false;
137
_sync: Utils.batchSync("Bookmark", SyncEngine),
139
_syncStartup: function _syncStart() {
140
SyncEngine.prototype._syncStartup.call(this);
142
// For first-syncs, make a backup for the user to restore
143
if (this.lastSync == 0)
146
// Lazily create a mapping of folder titles and separator positions to GUID
147
this.__defineGetter__("_lazyMap", function() {
148
delete this._lazyMap;
151
for (let guid in this._store.getAllIDs()) {
152
// Figure out what key to store the mapping
154
let id = idForGUID(guid);
155
switch (Svc.Bookmark.getItemType(id)) {
156
case Svc.Bookmark.TYPE_BOOKMARK:
157
key = "b" + Svc.Bookmark.getBookmarkURI(id).spec + ":" +
158
Svc.Bookmark.getItemTitle(id);
160
case Svc.Bookmark.TYPE_FOLDER:
161
key = "f" + Svc.Bookmark.getItemTitle(id);
163
case Svc.Bookmark.TYPE_SEPARATOR:
164
key = "s" + Svc.Bookmark.getItemIndex(id);
170
// The mapping is on a per parent-folder-name basis
171
let parent = Svc.Bookmark.getFolderIdForItem(id);
172
let parentName = Svc.Bookmark.getItemTitle(parent);
173
if (lazyMap[parentName] == null)
174
lazyMap[parentName] = {};
176
// If the entry already exists, remember that there are explicit dupes
177
let entry = new String(guid);
178
entry.hasDupe = lazyMap[parentName][key] != null;
180
// Remember this item's guid for its parent-name/key pair
181
lazyMap[parentName][key] = entry;
182
this._log.trace("Mapped: " + [parentName, key, entry, entry.hasDupe]);
185
// Expose a helper function to get a dupe guid for an item
186
return this._lazyMap = function(item) {
187
// Figure out if we have something to key with
193
key = "b" + item.bmkUri + ":" + item.title;
197
key = "f" + item.title;
200
key = "s" + item.pos;
206
// Give the guid if we have the matching pair
207
this._log.trace("Finding mapping: " + item.parentName + ", " + key);
208
let parent = lazyMap[item.parentName];
209
let dupe = parent && parent[key];
210
this._log.trace("Mapped dupe: " + dupe);
216
_syncFinish: function _syncFinish() {
217
SyncEngine.prototype._syncFinish.call(this);
218
delete this._lazyMap;
219
this._tracker._ensureMobileQuery();
222
_createRecord: function _createRecord(id) {
223
// Create the record like normal but mark it as having dupes if necessary
224
let record = SyncEngine.prototype._createRecord.call(this, id);
225
let entry = this._lazyMap(record);
226
if (entry != null && entry.hasDupe)
227
record.hasDupe = true;
231
_findDupe: function _findDupe(item) {
232
// Don't bother finding a dupe if the incoming item has duplicates
235
return this._lazyMap(item);
238
_handleDupe: function _handleDupe(item, dupeId) {
239
// The local dupe has the lower id, so make it the winning id
240
if (dupeId < item.id)
241
[item.id, dupeId] = [dupeId, item.id];
243
// Trigger id change from dupe to winning and update the server
244
this._store.changeItemID(dupeId, item.id);
245
this._deleteId(dupeId);
246
this._tracker.addChangedID(item.id, 0);
250
function BookmarksStore(name) {
251
Store.call(this, name);
253
BookmarksStore.prototype = {
254
__proto__: Store.prototype,
259
this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
260
getService(Ci.nsINavBookmarksService);
267
this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
268
getService(Ci.nsINavHistoryService);
275
this.__ls = Cc["@mozilla.org/browser/livemark-service;2"].
276
getService(Ci.nsILivemarkService);
283
ms = Cc["@mozilla.org/microsummary/service;1"].
284
getService(Ci.nsIMicrosummaryService);
287
this._log.warn("Could not load microsummary service");
290
this.__defineGetter__("_ms", function() ms);
297
this.__ts = Cc["@mozilla.org/browser/tagging-service;1"].
298
getService(Ci.nsITaggingService);
303
itemExists: function BStore_itemExists(id) {
304
return idForGUID(id) > 0;
307
// Hash of old GUIDs to the new renamed GUIDs
310
applyIncoming: function BStore_applyIncoming(record) {
311
// Ignore (accidental?) root changes
312
if (record.id in kSpecialIds) {
313
this._log.debug("Skipping change to root node: " + record.id);
317
// Convert GUID fields to the aliased GUID if necessary
318
["id", "parentid", "predecessorid"].forEach(function(field) {
319
let alias = this.aliases[record[field]];
321
record[field] = alias;
324
// Preprocess the record before doing the normal apply
325
switch (record.type) {
327
// Convert the query uri if necessary
328
if (record.bmkUri == null || record.folderName == null)
331
// Tag something so that the tag exists
332
let tag = record.folderName;
333
let dummyURI = Utils.makeURI("about:weave#BStore_preprocess");
334
this._ts.tagURI(dummyURI, [tag]);
336
// Look for the id of the tag (that might have just been added)
337
let tags = this._getNode(this._bms.tagsFolder);
338
if (!(tags instanceof Ci.nsINavHistoryQueryResultNode))
341
tags.containerOpen = true;
342
for (let i = 0; i < tags.childCount; i++) {
343
let child = tags.getChild(i);
344
// Found the tag, so fix up the query to use the right id
345
if (child.title == tag) {
346
this._log.debug("query folder: " + tag + " = " + child.itemId);
347
record.bmkUri = record.bmkUri.replace(/([:&]folder=)\d+/, "$1" +
356
// Figure out the local id of the parent GUID if available
357
let parentGUID = record.parentid;
358
record._orphan = false;
359
if (parentGUID != null) {
360
let parentId = idForGUID(parentGUID);
362
// Default to unfiled if we don't have the parent yet
364
this._log.trace("Reparenting to unfiled until parent is synced");
365
record._orphan = true;
366
parentId = kSpecialIds.unfiled;
369
// Save the parent id for modifying the bookmark later
370
record._parent = parentId;
373
// Default to append unless we're not an orphan with the predecessor
374
let predGUID = record.predecessorid;
375
record._insertPos = Svc.Bookmark.DEFAULT_INDEX;
376
if (!record._orphan) {
377
// No predecessor means it's the first item
378
if (predGUID == null)
379
record._insertPos = 0;
381
// The insert position is one after the predecessor of the same parent
382
let predId = idForGUID(predGUID);
383
if (predId != -1 && this._getParentGUIDForId(predId) == parentGUID) {
384
record._insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
385
record._predId = predId;
388
this._log.trace("Appending to end until predecessor is synced");
392
// Do the normal processing of incoming records
393
Store.prototype.applyIncoming.apply(this, arguments);
395
// Do some post-processing if we have an item
396
let itemId = idForGUID(record.id);
398
// Move any children that are looking for this folder as a parent
399
if (record.type == "folder")
400
this._reparentOrphans(itemId);
402
// Create an annotation to remember that it needs a parent
403
// XXX Work around Bug 510628 by prepending parenT
405
Utils.anno(itemId, PARENT_ANNO, "T" + parentGUID);
406
// It's now in the right folder, so move annotated items behind this
408
this._attachFollowers(itemId);
410
// Create an annotation if we have a predecessor but no position
411
// XXX Work around Bug 510628 by prepending predecessoR
412
if (predGUID != null && record._insertPos == Svc.Bookmark.DEFAULT_INDEX)
413
Utils.anno(itemId, PREDECESSOR_ANNO, "R" + predGUID);
418
* Find all ids of items that have a given value for an annotation
420
_findAnnoItems: function BStore__findAnnoItems(anno, val) {
421
// XXX Work around Bug 510628 by prepending parenT
422
if (anno == PARENT_ANNO)
424
// XXX Work around Bug 510628 by prepending predecessoR
425
else if (anno == PREDECESSOR_ANNO)
428
return Svc.Annos.getItemsWithAnnotation(anno, {}).filter(function(id)
429
Utils.anno(id, anno) == val);
433
* For the provided parent item, attach its children to it
435
_reparentOrphans: function _reparentOrphans(parentId) {
436
// Find orphans and reunite with this folder parent
437
let parentGUID = GUIDForId(parentId);
438
let orphans = this._findAnnoItems(PARENT_ANNO, parentGUID);
440
this._log.debug("Reparenting orphans " + orphans + " to " + parentId);
441
orphans.forEach(function(orphan) {
442
// Append the orphan under the parent unless it's supposed to be first
443
let insertPos = Svc.Bookmark.DEFAULT_INDEX;
444
if (!Svc.Annos.itemHasAnnotation(orphan, PREDECESSOR_ANNO))
447
// Move the orphan to the parent and drop the missing parent annotation
448
Svc.Bookmark.moveItem(orphan, parentId, insertPos);
449
Svc.Annos.removeItemAnnotation(orphan, PARENT_ANNO);
452
// Fix up the ordering of the now-parented items
453
orphans.forEach(this._attachFollowers, this);
457
* Move an item and all of its followers to a new position until reaching an
458
* item that shouldn't be moved
460
_moveItemChain: function BStore__moveItemChain(itemId, insertPos, stopId) {
461
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
463
// Keep processing the item chain until it loops to the stop item
465
// Figure out what's next in the chain
466
let itemPos = Svc.Bookmark.getItemIndex(itemId);
467
let nextId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
469
Svc.Bookmark.moveItem(itemId, parentId, insertPos);
470
this._log.trace("Moved " + itemId + " to " + insertPos);
472
// Prepare for the next item in the chain
473
insertPos = Svc.Bookmark.getItemIndex(itemId) + 1;
476
// Stop if we ran off the end or the item is looking for its pred.
477
if (itemId == -1 || Svc.Annos.itemHasAnnotation(itemId, PREDECESSOR_ANNO))
479
} while (itemId != stopId);
483
* For the provided predecessor item, attach its followers to it
485
_attachFollowers: function BStore__attachFollowers(predId) {
486
let predGUID = GUIDForId(predId);
487
let followers = this._findAnnoItems(PREDECESSOR_ANNO, predGUID);
488
if (followers.length > 1)
489
this._log.warn(predId + " has more than one followers: " + followers);
491
// Start at the first follower and move the chain of followers
492
let parent = Svc.Bookmark.getFolderIdForItem(predId);
493
followers.forEach(function(follow) {
494
this._log.trace("Repositioning " + follow + " behind " + predId);
495
if (Svc.Bookmark.getFolderIdForItem(follow) != parent) {
496
this._log.warn("Follower doesn't have the same parent: " + parent);
500
// Move the chain of followers to after the predecessor
501
let insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
502
this._moveItemChain(follow, insertPos, predId);
504
// Remove the annotation now that we're putting it in the right spot
505
Svc.Annos.removeItemAnnotation(follow, PREDECESSOR_ANNO);
509
create: function BStore_create(record) {
511
switch (record.type) {
514
case "microsummary": {
515
let uri = Utils.makeURI(record.bmkUri);
516
newId = this._bms.insertBookmark(record._parent, uri, record._insertPos,
518
this._log.debug(["created bookmark", newId, "under", record._parent, "at",
519
record._insertPos, "as", record.title, record.bmkUri].join(" "));
521
this._tagURI(uri, record.tags);
522
this._bms.setKeywordForBookmark(newId, record.keyword);
523
if (record.description)
524
Utils.anno(newId, "bookmarkProperties/description", record.description);
526
if (record.loadInSidebar)
527
Utils.anno(newId, "bookmarkProperties/loadInSidebar", true);
529
if (record.type == "microsummary") {
530
this._log.debug(" \-> is a microsummary");
531
Utils.anno(newId, "bookmarks/staticTitle", record.staticTitle || "");
532
let genURI = Utils.makeURI(record.generatorUri);
535
let micsum = this._ms.createMicrosummary(uri, genURI);
536
this._ms.setMicrosummary(newId, micsum);
538
catch(ex) { /* ignore "missing local generator" exceptions */ }
541
this._log.warn("Can't create microsummary -- not supported.");
545
newId = this._bms.createFolder(record._parent, record.title,
547
this._log.debug(["created folder", newId, "under", record._parent, "at",
548
record._insertPos, "as", record.title].join(" "));
550
if (record.description)
551
Utils.anno(newId, "bookmarkProperties/description", record.description);
555
if (record.siteUri != null)
556
siteURI = Utils.makeURI(record.siteUri);
558
newId = this._ls.createLivemark(record._parent, record.title, siteURI,
559
Utils.makeURI(record.feedUri), record._insertPos);
560
this._log.debug(["created livemark", newId, "under", record._parent, "at",
561
record._insertPos, "as", record.title, record.siteUri, record.feedUri].
565
newId = this._bms.insertSeparator(record._parent, record._insertPos);
566
this._log.debug(["created separator", newId, "under", record._parent,
567
"at", record._insertPos].join(" "));
570
this._log.debug(" -> got a generic places item.. do nothing?");
573
this._log.error("_create: Unknown item type: " + record.type);
577
this._log.trace("Setting GUID of new item " + newId + " to " + record.id);
578
this._setGUID(newId, record.id);
581
remove: function BStore_remove(record) {
582
let itemId = idForGUID(record.id);
584
this._log.debug("Item " + record.id + " already removed");
587
var type = this._bms.getItemType(itemId);
590
case this._bms.TYPE_BOOKMARK:
591
this._log.debug(" -> removing bookmark " + record.id);
592
this._ts.untagURI(this._bms.getBookmarkURI(itemId), null);
593
this._bms.removeItem(itemId);
595
case this._bms.TYPE_FOLDER:
596
this._log.debug(" -> removing folder " + record.id);
597
Svc.Bookmark.removeItem(itemId);
599
case this._bms.TYPE_SEPARATOR:
600
this._log.debug(" -> removing separator " + record.id);
601
this._bms.removeItem(itemId);
604
this._log.error("remove: Unknown item type: " + type);
609
update: function BStore_update(record) {
610
let itemId = idForGUID(record.id);
613
this._log.debug("Skipping update for unknown item: " + record.id);
617
this._log.trace("Updating " + record.id + " (" + itemId + ")");
619
// Move the bookmark to a new parent if necessary
620
if (Svc.Bookmark.getFolderIdForItem(itemId) != record._parent) {
621
this._log.trace("Moving item to a new parent");
622
Svc.Bookmark.moveItem(itemId, record._parent, record._insertPos);
624
// Move the chain of bookmarks to a new position
625
else if (Svc.Bookmark.getItemIndex(itemId) != record._insertPos &&
627
this._log.trace("Moving item and followers to a new position");
629
// Stop moving at the predecessor unless we don't have one
630
this._moveItemChain(itemId, record._insertPos, record._predId || itemId);
633
for (let [key, val] in Iterator(record.cleartext)) {
636
this._bms.setItemTitle(itemId, val);
639
this._bms.changeBookmarkURI(itemId, Utils.makeURI(val));
642
this._tagURI(this._bms.getBookmarkURI(itemId), val);
645
this._bms.setKeywordForBookmark(itemId, val);
648
Utils.anno(itemId, "bookmarkProperties/description", val);
650
case "loadInSidebar":
652
Utils.anno(itemId, "bookmarkProperties/loadInSidebar", true);
654
Svc.Annos.removeItemAnnotation(itemId, "bookmarkProperties/loadInSidebar");
656
case "generatorUri": {
658
let micsumURI = this._bms.getBookmarkURI(itemId);
659
let genURI = Utils.makeURI(val);
660
if (this._ms == SERVICE_NOT_SUPPORTED)
661
this._log.warn("Can't create microsummary -- not supported.");
663
let micsum = this._ms.createMicrosummary(micsumURI, genURI);
664
this._ms.setMicrosummary(itemId, micsum);
667
this._log.debug("Could not set microsummary generator URI: " + e);
671
this._ls.setSiteURI(itemId, Utils.makeURI(val));
674
this._ls.setFeedURI(itemId, Utils.makeURI(val));
680
changeItemID: function BStore_changeItemID(oldID, newID) {
681
// Remember the GUID change for incoming records
682
this.aliases[oldID] = newID;
684
// Update any existing annotation references
685
this._findAnnoItems(PARENT_ANNO, oldID).forEach(function(itemId) {
686
Utils.anno(itemId, PARENT_ANNO, "T" + newID);
688
this._findAnnoItems(PREDECESSOR_ANNO, oldID).forEach(function(itemId) {
689
Utils.anno(itemId, PREDECESSOR_ANNO, "R" + newID);
692
// Make sure there's an item to change GUIDs
693
let itemId = idForGUID(oldID);
697
this._log.debug("Changing GUID " + oldID + " to " + newID);
698
this._setGUID(itemId, newID);
701
_setGUID: function BStore__setGUID(itemId, guid) {
702
let collision = idForGUID(guid);
703
if (collision != -1) {
704
this._log.warn("Freeing up GUID " + guid + " used by " + collision);
705
Svc.Annos.removeItemAnnotation(collision, "placesInternal/GUID");
707
Svc.Bookmark.setItemGUID(itemId, guid);
710
_getNode: function BStore__getNode(folder) {
711
let query = this._hsvc.getNewQuery();
712
query.setFolders([folder], 1);
713
return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
716
_getTags: function BStore__getTags(uri) {
718
if (typeof(uri) == "string")
719
uri = Utils.makeURI(uri);
721
this._log.warn("Could not parse URI \"" + uri + "\": " + e);
723
return this._ts.getTagsForURI(uri, {});
726
_getDescription: function BStore__getDescription(id) {
728
return Utils.anno(id, "bookmarkProperties/description");
734
_isLoadInSidebar: function BStore__isLoadInSidebar(id) {
735
return Svc.Annos.itemHasAnnotation(id, "bookmarkProperties/loadInSidebar");
738
_getStaticTitle: function BStore__getStaticTitle(id) {
740
return Utils.anno(id, "bookmarks/staticTitle");
746
// Create a record starting from the weave id (places guid)
747
createRecord: function createRecord(guid) {
748
let placeId = idForGUID(guid);
750
if (placeId <= 0) { // deleted item
751
record = new PlacesItem();
752
record.deleted = true;
756
let parent = Svc.Bookmark.getFolderIdForItem(placeId);
757
switch (this._bms.getItemType(placeId)) {
758
case this._bms.TYPE_BOOKMARK:
759
let bmkUri = this._bms.getBookmarkURI(placeId).spec;
760
if (this._ms && this._ms.hasMicrosummary(placeId)) {
761
record = new BookmarkMicsum();
762
let micsum = this._ms.getMicrosummary(placeId);
763
record.generatorUri = micsum.generator.uri.spec; // breaks local generators
764
record.staticTitle = this._getStaticTitle(placeId);
767
if (bmkUri.search(/^place:/) == 0) {
768
record = new BookmarkQuery();
770
// Get the actual tag name instead of the local itemId
771
let folder = bmkUri.match(/[:&]folder=(\d+)/);
773
// There might not be the tag yet when creating on a new client
774
if (folder != null) {
776
record.folderName = this._bms.getItemTitle(folder);
777
this._log.debug("query id: " + folder + " = " + record.folderName);
783
record = new Bookmark();
784
record.title = this._bms.getItemTitle(placeId);
787
record.parentName = Svc.Bookmark.getItemTitle(parent);
788
record.bmkUri = bmkUri;
789
record.tags = this._getTags(record.bmkUri);
790
record.keyword = this._bms.getKeywordForBookmark(placeId);
791
record.description = this._getDescription(placeId);
792
record.loadInSidebar = this._isLoadInSidebar(placeId);
795
case this._bms.TYPE_FOLDER:
796
if (this._ls.isLivemark(placeId)) {
797
record = new Livemark();
799
let siteURI = this._ls.getSiteURI(placeId);
801
record.siteUri = siteURI.spec;
802
record.feedUri = this._ls.getFeedURI(placeId).spec;
805
record = new BookmarkFolder();
808
record.parentName = Svc.Bookmark.getItemTitle(parent);
809
record.title = this._bms.getItemTitle(placeId);
810
record.description = this._getDescription(placeId);
813
case this._bms.TYPE_SEPARATOR:
814
record = new BookmarkSeparator();
815
// Create a positioning identifier for the separator
816
record.parentName = Svc.Bookmark.getItemTitle(parent);
817
record.pos = Svc.Bookmark.getItemIndex(placeId);
820
case this._bms.TYPE_DYNAMIC_CONTAINER:
821
record = new PlacesItem();
822
this._log.warn("Don't know how to serialize dynamic containers yet");
826
record = new PlacesItem();
827
this._log.warn("Unknown item type, cannot serialize: " +
828
this._bms.getItemType(placeId));
831
record.parentid = this._getParentGUIDForId(placeId);
832
record.predecessorid = this._getPredecessorGUIDForId(placeId);
833
record.sortindex = this._calculateIndex(record);
839
this._log.trace("Creating SQL statement: _frecencyStm");
840
let stm = Svc.History.DBConnection.createStatement(
844
this.__defineGetter__("_frecencyStm", function() stm);
848
_calculateIndex: function _calculateIndex(record) {
849
// For anything directly under the toolbar, give it a boost of more than an
850
// unvisited bookmark
852
if (record.parentid == "toolbar")
855
// Add in the bookmark's frecency if we have something
856
if (record.bmkUri != null) {
858
this._frecencyStm.params.url = record.bmkUri;
859
if (this._frecencyStm.step())
860
index += this._frecencyStm.row.frecency;
863
this._frecencyStm.reset();
870
_getParentGUIDForId: function BStore__getParentGUIDForId(itemId) {
871
// Give the parent annotation if it exists
873
// XXX Work around Bug 510628 by removing prepended parenT
874
return Utils.anno(itemId, PARENT_ANNO).slice(1);
878
let parentid = this._bms.getFolderIdForItem(itemId);
879
if (parentid == -1) {
880
this._log.debug("Found orphan bookmark, reparenting to unfiled");
881
parentid = this._bms.unfiledBookmarksFolder;
882
this._bms.moveItem(itemId, parentid, -1);
884
return GUIDForId(parentid);
887
_getPredecessorGUIDForId: function BStore__getPredecessorGUIDForId(itemId) {
888
// Give the predecessor annotation if it exists
890
// XXX Work around Bug 510628 by removing prepended predecessoR
891
return Utils.anno(itemId, PREDECESSOR_ANNO).slice(1);
895
// Figure out the predecessor, unless it's the first item
896
let itemPos = Svc.Bookmark.getItemIndex(itemId);
900
// For items directly under unfiled/unsorted, give no predecessor
901
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
902
if (parentId == Svc.Bookmark.unfiledBookmarksFolder)
905
let predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos - 1);
906
if (predecessorId == -1) {
907
this._log.debug("No predecessor directly before " + itemId + " under " +
908
parentId + " at " + itemPos);
910
// Find the predecessor before the item
912
// No more items to check, it must be the first one
915
predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos);
916
} while (predecessorId == -1);
918
// Fix up the item to be at the right position for next time
920
this._log.debug("Fixing " + itemId + " to be at position " + itemPos);
921
Svc.Bookmark.moveItem(itemId, parentId, itemPos);
923
// There must be no predecessor for this item!
928
return GUIDForId(predecessorId);
931
_getChildren: function BStore_getChildren(guid, items) {
932
let node = guid; // the recursion case
933
if (typeof(node) == "string") // callers will give us the guid as the first arg
934
node = this._getNode(idForGUID(guid));
936
if (node.type == node.RESULT_TYPE_FOLDER &&
937
!this._ls.isLivemark(node.itemId)) {
938
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
939
node.containerOpen = true;
941
// Remember all the children GUIDs and recursively get more
942
for (var i = 0; i < node.childCount; i++) {
943
let child = node.getChild(i);
944
items[GUIDForId(child.itemId)] = true;
945
this._getChildren(child, items);
952
_tagURI: function BStore_tagURI(bmkURI, tags) {
953
// Filter out any null/undefined/empty tags
954
tags = tags.filter(function(t) t);
956
// Temporarily tag a dummy uri to preserve tag ids when untagging
957
let dummyURI = Utils.makeURI("about:weave#BStore_tagURI");
958
this._ts.tagURI(dummyURI, tags);
959
this._ts.untagURI(bmkURI, null);
960
this._ts.tagURI(bmkURI, tags);
961
this._ts.untagURI(dummyURI, null);
964
getAllIDs: function BStore_getAllIDs() {
966
for (let [guid, id] in Iterator(kSpecialIds))
967
if (guid != "places" && guid != "tags")
968
this._getChildren(guid, items);
972
wipe: function BStore_wipe() {
973
// Save a backup before clearing out all bookmarks
976
for (let [guid, id] in Iterator(kSpecialIds))
977
if (guid != "places")
978
this._bms.removeFolderChildren(id);
982
function BookmarksTracker(name) {
983
Tracker.call(this, name);
985
// Ignore changes to the special roots
986
for (let guid in kSpecialIds)
989
Svc.Bookmark.addObserver(this, false);
991
BookmarksTracker.prototype = {
992
__proto__: Tracker.prototype,
995
let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
996
getService(Ci.nsINavBookmarksService);
997
this.__defineGetter__("_bms", function() bms);
1002
let ls = Cc["@mozilla.org/browser/livemark-service;2"].
1003
getService(Ci.nsILivemarkService);
1004
this.__defineGetter__("_ls", function() ls);
1008
QueryInterface: XPCOMUtils.generateQI([
1009
Ci.nsINavBookmarkObserver,
1010
Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS
1014
* Add a bookmark (places) id to be uploaded and bump up the sync score
1017
* Places internal id of the bookmark to upload
1019
_addId: function BMT__addId(itemId) {
1020
if (this.addChangedID(GUIDForId(itemId)))
1025
* Add the successor id for the item that follows the given item
1027
_addSuccessor: function BMT__addSuccessor(itemId) {
1028
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
1029
let itemPos = Svc.Bookmark.getItemIndex(itemId);
1030
let succId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
1032
this._addId(succId);
1035
/* Every add/remove/change is worth 10 points */
1036
_upScore: function BMT__upScore() {
1041
* Determine if a change should be ignored: we're ignoring everything or the
1042
* folder is for livemarks
1045
* Item under consideration to ignore
1046
* @param folder (optional)
1047
* Folder of the item being changed
1049
_ignore: function BMT__ignore(itemId, folder) {
1050
// Ignore unconditionally if the engine tells us to
1054
// Ensure that the mobile bookmarks query is correct in the UI
1055
this._ensureMobileQuery();
1057
// Make sure to remove items that have the exclude annotation
1058
if (Svc.Annos.itemHasAnnotation(itemId, "places/excludeFromBackup")) {
1059
this.removeChangedID(GUIDForId(itemId));
1063
// Get the folder id if we weren't given one
1065
folder = this._bms.getFolderIdForItem(itemId);
1067
let tags = kSpecialIds.tags;
1068
// Ignore changes to tags (folders under the tags folder)
1072
// Ignore tag items (the actual instance of a tag for a bookmark)
1073
if (this._bms.getFolderIdForItem(folder) == tags)
1076
// Ignore livemark children
1077
return this._ls.isLivemark(folder);
1080
onItemAdded: function BMT_onEndUpdateBatch(itemId, folder, index) {
1081
if (this._ignore(itemId, folder))
1084
this._log.trace("onItemAdded: " + itemId);
1085
this._addId(itemId);
1086
this._addSuccessor(itemId);
1089
onBeforeItemRemoved: function BMT_onBeforeItemRemoved(itemId) {
1090
if (this._ignore(itemId))
1093
this._log.trace("onBeforeItemRemoved: " + itemId);
1094
this._addId(itemId);
1095
this._addSuccessor(itemId);
1098
_ensureMobileQuery: function _ensureMobileQuery() {
1099
let anno = "PlacesOrganizer/OrganizerQuery";
1100
let find = function(val) Svc.Annos.getItemsWithAnnotation(anno, {}).filter(
1101
function(id) Utils.anno(id, anno) == val);
1103
// Don't continue if the Library isn't ready
1104
let all = find("AllBookmarks");
1105
if (all.length == 0)
1108
// Disable handling of notifications while changing the mobile query
1109
this.ignoreAll = true;
1111
let mobile = find("MobileBookmarks");
1112
let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
1113
let title = Str.sync.get("mobile.label");
1115
// Don't add OR do remove the mobile bookmarks if there's nothing
1116
if (Svc.Bookmark.getIdForItemAt(kSpecialIds.mobile, 0) == -1) {
1117
if (mobile.length != 0)
1118
Svc.Bookmark.removeItem(mobile[0]);
1120
// Add the mobile bookmarks query if it doesn't exist
1121
else if (mobile.length == 0) {
1122
let query = Svc.Bookmark.insertBookmark(all[0], queryURI, -1, title);
1123
Utils.anno(query, anno, "MobileBookmarks");
1124
Utils.anno(query, "places/excludeFromBackup", 1);
1126
// Make sure the existing title is correct
1127
else if (Svc.Bookmark.getItemTitle(mobile[0]) != title)
1128
Svc.Bookmark.setItemTitle(mobile[0], title);
1130
this.ignoreAll = false;
1133
onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value) {
1134
if (this._ignore(itemId))
1137
// ignore annotations except for the ones that we sync
1138
let annos = ["bookmarkProperties/description",
1139
"bookmarkProperties/loadInSidebar", "bookmarks/staticTitle",
1140
"livemark/feedURI", "livemark/siteURI", "microsummary/generatorURI"];
1141
if (isAnno && annos.indexOf(property) == -1)
1144
// Ignore favicon changes to avoid unnecessary churn
1145
if (property == "favicon")
1148
this._log.trace("onItemChanged: " + itemId +
1149
(", " + property + (isAnno? " (anno)" : "")) +
1150
(value? (" = \"" + value + "\"") : ""));
1151
this._addId(itemId);
1154
onItemMoved: function BMT_onItemMoved(itemId, oldParent, oldIndex, newParent, newIndex) {
1155
if (this._ignore(itemId))
1158
this._log.trace("onItemMoved: " + itemId);
1159
this._addId(itemId);
1160
this._addSuccessor(itemId);
1162
// Get the thing that's now at the old place
1163
let oldSucc = Svc.Bookmark.getIdForItemAt(oldParent, oldIndex);
1165
this._addId(oldSucc);
1167
// Remove any position annotations now that the user moved the item
1168
Svc.Annos.removeItemAnnotation(itemId, PARENT_ANNO);
1169
Svc.Annos.removeItemAnnotation(itemId, PREDECESSOR_ANNO);
1172
onBeginUpdateBatch: function BMT_onBeginUpdateBatch() {},
1173
onEndUpdateBatch: function BMT_onEndUpdateBatch() {},
1174
onItemRemoved: function BMT_onItemRemoved(itemId, folder, index) {},
1175
onItemVisited: function BMT_onItemVisited(itemId, aVisitID, time) {}