зеркало из https://github.com/mozilla/pjs.git
Remerge fx-sync
--HG-- extra : rebase_source : a4b61b8ac50d3abe829a18c304f4e23f0f5a4f2a
This commit is contained in:
Родитель
1adf8ac400
Коммит
eef12e9219
|
@ -499,11 +499,22 @@ WeaveCrypto.prototype = {
|
|||
return "" + outputBuffer.readString() + "";
|
||||
},
|
||||
|
||||
_importSymKey: function _importSymKey(slot, mechanism, origin, op, keyItem) {
|
||||
let symKey = this.nss.PK11_ImportSymKey(slot, mechanism, origin, op, keyItem.address(), null);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("symkey import failed", Cr.NS_ERROR_FAILURE);
|
||||
return symKey;
|
||||
},
|
||||
|
||||
_freeSymKey: function _freeSymKey(symKey) {
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
},
|
||||
|
||||
_commonCrypt : function (input, output, symmetricKey, iv, operation) {
|
||||
this.log("_commonCrypt() called");
|
||||
// Get rid of the base64 encoding and convert to SECItems.
|
||||
let keyItem = this.makeSECItem(symmetricKey, true);
|
||||
let keyItem = this.makeSECItem(symmetricKey, true, true);
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
|
||||
// Determine which (padded) PKCS#11 mechanism to use.
|
||||
|
@ -523,10 +534,7 @@ WeaveCrypto.prototype = {
|
|||
if (slot.isNull())
|
||||
throw Components.Exception("can't get internal key slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
symKey = this.nss.PK11_ImportSymKey(slot, mechanism, this.nss.PK11_OriginUnwrap, operation, keyItem.address(), null);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("symkey import failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
symKey = this._importSymKey(slot, mechanism, this.nss.PK11_OriginUnwrap, operation, keyItem);
|
||||
ctx = this.nss.PK11_CreateContextBySymKey(mechanism, operation, symKey, ivParam);
|
||||
if (ctx.isNull())
|
||||
throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
|
||||
|
@ -557,8 +565,7 @@ WeaveCrypto.prototype = {
|
|||
} finally {
|
||||
if (ctx && !ctx.isNull())
|
||||
this.nss.PK11_DestroyContext(ctx, true);
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
this._freeSymKey(symKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (ivParam && !ivParam.isNull())
|
||||
|
@ -693,7 +700,6 @@ WeaveCrypto.prototype = {
|
|||
return new this.nss_t.SECItem(this.nss.SIBUFFER, outputData, outputData.length);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Returns the expanded data string for the derived key.
|
||||
*/
|
||||
|
@ -751,3 +757,45 @@ WeaveCrypto.prototype = {
|
|||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Memoize makeSECItem for symmetric keys.
|
||||
WeaveCrypto.prototype.makeSECItem =
|
||||
(function (orig) {
|
||||
let memo = {};
|
||||
|
||||
return function(input, isEncoded, memoize) {
|
||||
if (memoize) {
|
||||
let memoKey = "" + input + !!isEncoded;
|
||||
let val = memo[memoKey];
|
||||
if (!val) {
|
||||
val = orig.apply(this, arguments);
|
||||
memo[memoKey] = val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return orig.apply(this, arguments);
|
||||
};
|
||||
}(WeaveCrypto.prototype.makeSECItem));
|
||||
|
||||
WeaveCrypto.prototype._importSymKey =
|
||||
(function (orig) {
|
||||
let memo = {}
|
||||
|
||||
return function(slot, mechanism, origin, op, keyItem) {
|
||||
// keyItem lookup is already memoized, so we can directly use the address.
|
||||
// Slot changes each time. Don't use it as memo key input.
|
||||
let memoKey = "" + "-" + mechanism +
|
||||
origin + op + "-" + keyItem.address();
|
||||
let val = memo[memoKey];
|
||||
if (!val) {
|
||||
val = orig.apply(this, arguments);
|
||||
memo[memoKey] = val;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
}(WeaveCrypto.prototype._importSymKey));
|
||||
|
||||
// Yes, this leaks. However, _importSymKey is now memoized, so the average user
|
||||
// will have only a single key, which we persist for the lifetime of the
|
||||
// session...
|
||||
WeaveCrypto.prototype._freeSymKey = function(symKey) {};
|
||||
|
|
|
@ -8,7 +8,69 @@ try {
|
|||
.getService(Ci.IWeaveCrypto);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
function weavecrypto_memo() {
|
||||
if (!cryptoSvc._importSymKey)
|
||||
return
|
||||
|
||||
let w = new WeaveCrypto();
|
||||
let key = w.generateRandomKey();
|
||||
let keyItem1 = w.makeSECItem(key, true, true);
|
||||
let keyItem2 = w.makeSECItem(key, true, true);
|
||||
do_check_eq(keyItem1, keyItem2);
|
||||
|
||||
do_check_eq("" + w.nss.PK11_AlgtagToMechanism(w.algorithm),
|
||||
"" + w.nss.PK11_AlgtagToMechanism(w.algorithm));
|
||||
|
||||
let symKey1 =
|
||||
w._importSymKey(w.nss.PK11_GetInternalKeySlot(),
|
||||
w.nss.PK11_AlgtagToMechanism(w.algorithm),
|
||||
w.nss.PK11_OriginUnwrap,
|
||||
w.nss.CKA_DECRYPT,
|
||||
keyItem1);
|
||||
let symKey2 =
|
||||
w._importSymKey(w.nss.PK11_GetInternalKeySlot(),
|
||||
w.nss.PK11_AlgtagToMechanism(w.algorithm),
|
||||
w.nss.PK11_OriginUnwrap,
|
||||
w.nss.CKA_DECRYPT,
|
||||
keyItem1);
|
||||
do_check_eq(symKey1, symKey2);
|
||||
}
|
||||
|
||||
/*
|
||||
With memoization:
|
||||
make check-one 10.39s user 0.75s system 100% cpu 11.041 total
|
||||
nsStringStats
|
||||
=> mAllocCount: 1923
|
||||
=> mReallocCount: 306
|
||||
=> mFreeCount: 1923
|
||||
=> mShareCount: 6764
|
||||
=> mAdoptCount: 101
|
||||
=> mAdoptFreeCount: 101
|
||||
<<<<<<<
|
||||
|
||||
Without memoization, it crashes after a few thousand iterations... and 5610 take
|
||||
make check-one 7.57s user 0.67s system 101% cpu 8.105 total
|
||||
nsStringStats
|
||||
=> mAllocCount: 1923
|
||||
=> mReallocCount: 306
|
||||
=> mFreeCount: 1923
|
||||
=> mShareCount: 6764
|
||||
=> mAdoptCount: 101
|
||||
=> mAdoptFreeCount: 101
|
||||
<<<<<<<
|
||||
*/
|
||||
function multiple_decrypts(iterations) {
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
let key = cryptoSvc.generateRandomKey();
|
||||
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
|
||||
for (let i = 0; i < iterations; ++i) {
|
||||
let clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(clearText + " " + i, "Hello, world. " + i);
|
||||
}
|
||||
}
|
||||
|
||||
function test_encryption() {
|
||||
// First, do a normal run with expected usage... Generate a random key and
|
||||
// iv, encrypt and decrypt a string.
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
|
@ -162,3 +224,9 @@ function run_test() {
|
|||
do_check_true(failure);
|
||||
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
weavecrypto_memo();
|
||||
multiple_decrypts(6000);
|
||||
test_encryption();
|
||||
}
|
||||
|
|
|
@ -323,6 +323,9 @@ function KeyBundle(realm, collectionName, keyStr) {
|
|||
Identity.call(this, realm, collectionName, keyStr);
|
||||
this._hmac = null;
|
||||
this._encrypt = null;
|
||||
|
||||
// Cache the key object.
|
||||
this._hmacObj = null;
|
||||
}
|
||||
|
||||
KeyBundle.prototype = {
|
||||
|
@ -345,11 +348,11 @@ KeyBundle.prototype = {
|
|||
|
||||
set hmacKey(value) {
|
||||
this._hmac = value;
|
||||
this._hmacObj = value ? Utils.makeHMACKey(value) : null;
|
||||
},
|
||||
|
||||
get hmacKeyObject() {
|
||||
if (this.hmacKey)
|
||||
return Utils.makeHMACKey(this.hmacKey);
|
||||
return this._hmacObj;
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -383,7 +386,7 @@ BulkKeyBundle.prototype = {
|
|||
let hm = value[1];
|
||||
|
||||
this.password = json;
|
||||
this._hmac = Utils.safeAtoB(hm);
|
||||
this.hmacKey = Utils.safeAtoB(hm);
|
||||
this._encrypt = en; // Store in base64.
|
||||
}
|
||||
else {
|
||||
|
@ -413,7 +416,8 @@ SyncKeyBundle.prototype = {
|
|||
|
||||
set keyStr(value) {
|
||||
this.password = value;
|
||||
this._hmac = null;
|
||||
this._hmac = null;
|
||||
this._hmacObj = null;
|
||||
this._encrypt = null;
|
||||
this.generateEntry();
|
||||
},
|
||||
|
@ -437,6 +441,12 @@ SyncKeyBundle.prototype = {
|
|||
return this._hmac;
|
||||
},
|
||||
|
||||
get hmacKeyObject() {
|
||||
if (!this._hmacObj)
|
||||
this.generateEntry();
|
||||
return this._hmacObj;
|
||||
},
|
||||
|
||||
/*
|
||||
* If we've got a string, hash it into keys and store them.
|
||||
*/
|
||||
|
@ -460,7 +470,10 @@ SyncKeyBundle.prototype = {
|
|||
|
||||
// Save them.
|
||||
this._encrypt = btoa(enc);
|
||||
this._hmac = hmac;
|
||||
|
||||
// Individual sets: cheaper than calling parent setter.
|
||||
this._hmac = hmac;
|
||||
this._hmacObj = Utils.makeHMACKey(hmac);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -101,6 +101,8 @@ PERMS_DIRECTORY: 0755,
|
|||
// FIXME: Record size limit is 256k (new cluster), so this can be quite large!
|
||||
// (Bug 569295)
|
||||
MAX_UPLOAD_RECORDS: 100,
|
||||
MAX_HISTORY_UPLOAD: 5000,
|
||||
MAX_HISTORY_DOWNLOAD: 5000,
|
||||
|
||||
// Top-level statuses:
|
||||
STATUS_OK: "success.status_ok",
|
||||
|
|
|
@ -139,6 +139,7 @@ EngineManagerSvc.prototype = {
|
|||
function Engine(name) {
|
||||
this.Name = name || "Unnamed";
|
||||
this.name = name.toLowerCase();
|
||||
this.downloadLimit = null;
|
||||
|
||||
this._notify = Utils.notify("weave:engine:");
|
||||
this._log = Log4Moz.repository.getLogger("Engine." + this.Name);
|
||||
|
@ -495,7 +496,13 @@ SyncEngine.prototype = {
|
|||
let toFetch = [];
|
||||
if (handled.length == newitems.limit) {
|
||||
let guidColl = new Collection(this.engineURL);
|
||||
|
||||
// Sort and limit so that on mobile we only get the last X records.
|
||||
guidColl.limit = this.downloadLimit;
|
||||
guidColl.newer = this.lastSync;
|
||||
|
||||
// index: Orders by the sortindex descending (highest weight first).
|
||||
guidColl.sort = "index";
|
||||
|
||||
let guids = guidColl.get();
|
||||
if (!guids.success)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* Dan Mills <thunder@mozilla.com>
|
||||
* Jono DiCarlo <jdicarlo@mozilla.org>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
*
|
||||
* 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
|
||||
|
@ -42,8 +43,9 @@ const Cc = Components.classes;
|
|||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const PARENT_ANNO = "weave/parent";
|
||||
const PREDECESSOR_ANNO = "weave/predecessor";
|
||||
const GUID_ANNO = "sync/guid";
|
||||
const PARENT_ANNO = "sync/parent";
|
||||
const CHILDREN_ANNO = "sync/children";
|
||||
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
|
||||
const FOLDER_SORTINDEX = 1000000;
|
||||
|
||||
|
@ -91,19 +93,6 @@ Utils.lazy2(kSpecialIds, "mobile", function() {
|
|||
return mobile;
|
||||
});
|
||||
|
||||
// Create some helper functions to convert GUID/ids
|
||||
function idForGUID(guid) {
|
||||
if (guid in kSpecialIds)
|
||||
return kSpecialIds[guid];
|
||||
return Svc.Bookmark.getItemIdForGUID(guid);
|
||||
}
|
||||
function GUIDForId(placeId) {
|
||||
for (let [guid, id] in Iterator(kSpecialIds))
|
||||
if (placeId == id)
|
||||
return guid;
|
||||
return Svc.Bookmark.getItemGUID(placeId);
|
||||
}
|
||||
|
||||
function BookmarksEngine() {
|
||||
SyncEngine.call(this, "Bookmarks");
|
||||
this._handleImport();
|
||||
|
@ -113,6 +102,7 @@ BookmarksEngine.prototype = {
|
|||
_recordObj: PlacesItem,
|
||||
_storeObj: BookmarksStore,
|
||||
_trackerObj: BookmarksTracker,
|
||||
version: 2,
|
||||
|
||||
_handleImport: function _handleImport() {
|
||||
Svc.Obs.add("bookmarks-restore-begin", function() {
|
||||
|
@ -151,7 +141,7 @@ BookmarksEngine.prototype = {
|
|||
for (let guid in this._store.getAllIDs()) {
|
||||
// Figure out what key to store the mapping
|
||||
let key;
|
||||
let id = idForGUID(guid);
|
||||
let id = this._store.idForGUID(guid);
|
||||
switch (Svc.Bookmark.getItemType(id)) {
|
||||
case Svc.Bookmark.TYPE_BOOKMARK:
|
||||
key = "b" + Svc.Bookmark.getBookmarkURI(id).spec + ":" +
|
||||
|
@ -169,6 +159,9 @@ BookmarksEngine.prototype = {
|
|||
|
||||
// The mapping is on a per parent-folder-name basis
|
||||
let parent = Svc.Bookmark.getFolderIdForItem(id);
|
||||
if (parent <= 0)
|
||||
continue;
|
||||
|
||||
let parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
if (lazyMap[parentName] == null)
|
||||
lazyMap[parentName] = {};
|
||||
|
@ -211,6 +204,17 @@ BookmarksEngine.prototype = {
|
|||
return dupe;
|
||||
};
|
||||
});
|
||||
|
||||
this._store._childrenToOrder = {};
|
||||
},
|
||||
|
||||
_processIncoming: function _processIncoming() {
|
||||
SyncEngine.prototype._processIncoming.call(this);
|
||||
// Reorder children.
|
||||
this._tracker.ignoreAll = true;
|
||||
this._store._orderChildren();
|
||||
this._tracker.ignoreAll = false;
|
||||
delete this._store._childrenToOrder;
|
||||
},
|
||||
|
||||
_syncFinish: function _syncFinish() {
|
||||
|
@ -257,8 +261,8 @@ function BookmarksStore(name) {
|
|||
this.__ls = null;
|
||||
this.__ms = null;
|
||||
this.__ts = null;
|
||||
if (this.__frecencyStm)
|
||||
this.__frecencyStm.finalize();
|
||||
for each ([query, stmt] in Iterator(this._stmts))
|
||||
stmt.finalize();
|
||||
}, this);
|
||||
}
|
||||
BookmarksStore.prototype = {
|
||||
|
@ -276,7 +280,8 @@ BookmarksStore.prototype = {
|
|||
get _hsvc() {
|
||||
if (!this.__hsvc)
|
||||
this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
|
||||
getService(Ci.nsINavHistoryService);
|
||||
getService(Ci.nsINavHistoryService).
|
||||
QueryInterface(Ci.nsPIPlacesDatabase);
|
||||
return this.__hsvc;
|
||||
},
|
||||
|
||||
|
@ -314,21 +319,23 @@ BookmarksStore.prototype = {
|
|||
|
||||
|
||||
itemExists: function BStore_itemExists(id) {
|
||||
return idForGUID(id) > 0;
|
||||
return this.idForGUID(id) > 0;
|
||||
},
|
||||
|
||||
// Hash of old GUIDs to the new renamed GUIDs
|
||||
aliases: {},
|
||||
|
||||
applyIncoming: function BStore_applyIncoming(record) {
|
||||
// Ignore (accidental?) root changes
|
||||
if (record.id in kSpecialIds) {
|
||||
this._log.debug("Skipping change to root node: " + record.id);
|
||||
// For special folders we're only interested in child ordering.
|
||||
if ((record.id in kSpecialIds) && record.children) {
|
||||
this._log.debug("Processing special node: " + record.id);
|
||||
// Reorder children later
|
||||
this._childrenToOrder[record.id] = record.children;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert GUID fields to the aliased GUID if necessary
|
||||
["id", "parentid", "predecessorid"].forEach(function(field) {
|
||||
["id", "parentid"].forEach(function(field) {
|
||||
let alias = this.aliases[record[field]];
|
||||
if (alias != null)
|
||||
record[field] = alias;
|
||||
|
@ -370,7 +377,7 @@ BookmarksStore.prototype = {
|
|||
let parentGUID = record.parentid;
|
||||
record._orphan = false;
|
||||
if (parentGUID != null) {
|
||||
let parentId = idForGUID(parentGUID);
|
||||
let parentId = this.idForGUID(parentGUID);
|
||||
|
||||
// Default to unfiled if we don't have the parent yet
|
||||
if (parentId <= 0) {
|
||||
|
@ -383,47 +390,23 @@ BookmarksStore.prototype = {
|
|||
record._parent = parentId;
|
||||
}
|
||||
|
||||
// Default to append unless we're not an orphan with the predecessor
|
||||
let predGUID = record.predecessorid;
|
||||
record._insertPos = Svc.Bookmark.DEFAULT_INDEX;
|
||||
if (!record._orphan) {
|
||||
// No predecessor means it's the first item
|
||||
if (predGUID == null)
|
||||
record._insertPos = 0;
|
||||
else {
|
||||
// The insert position is one after the predecessor of the same parent
|
||||
let predId = idForGUID(predGUID);
|
||||
if (predId != -1 && this._getParentGUIDForId(predId) == parentGUID) {
|
||||
record._insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
|
||||
record._predId = predId;
|
||||
}
|
||||
else
|
||||
this._log.trace("Appending to end until predecessor is synced");
|
||||
}
|
||||
}
|
||||
|
||||
// Do the normal processing of incoming records
|
||||
Store.prototype.applyIncoming.apply(this, arguments);
|
||||
|
||||
// Do some post-processing if we have an item
|
||||
let itemId = idForGUID(record.id);
|
||||
let itemId = this.idForGUID(record.id);
|
||||
if (itemId > 0) {
|
||||
// Move any children that are looking for this folder as a parent
|
||||
if (record.type == "folder")
|
||||
if (record.type == "folder") {
|
||||
this._reparentOrphans(itemId);
|
||||
// Reorder children later
|
||||
if (record.children)
|
||||
this._childrenToOrder[record.id] = record.children;
|
||||
}
|
||||
|
||||
// Create an annotation to remember that it needs a parent
|
||||
// XXX Work around Bug 510628 by prepending parenT
|
||||
if (record._orphan)
|
||||
Utils.anno(itemId, PARENT_ANNO, "T" + parentGUID);
|
||||
// It's now in the right folder, so move annotated items behind this
|
||||
else
|
||||
this._attachFollowers(itemId);
|
||||
|
||||
// Create an annotation if we have a predecessor but no position
|
||||
// XXX Work around Bug 510628 by prepending predecessoR
|
||||
if (predGUID != null && record._insertPos == Svc.Bookmark.DEFAULT_INDEX)
|
||||
Utils.anno(itemId, PREDECESSOR_ANNO, "R" + predGUID);
|
||||
Utils.anno(itemId, PARENT_ANNO, parentGUID);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -431,13 +414,6 @@ BookmarksStore.prototype = {
|
|||
* Find all ids of items that have a given value for an annotation
|
||||
*/
|
||||
_findAnnoItems: function BStore__findAnnoItems(anno, val) {
|
||||
// XXX Work around Bug 510628 by prepending parenT
|
||||
if (anno == PARENT_ANNO)
|
||||
val = "T" + val;
|
||||
// XXX Work around Bug 510628 by prepending predecessoR
|
||||
else if (anno == PREDECESSOR_ANNO)
|
||||
val = "R" + val;
|
||||
|
||||
return Svc.Annos.getItemsWithAnnotation(anno, {}).filter(function(id)
|
||||
Utils.anno(id, anno) == val);
|
||||
},
|
||||
|
@ -447,75 +423,14 @@ BookmarksStore.prototype = {
|
|||
*/
|
||||
_reparentOrphans: function _reparentOrphans(parentId) {
|
||||
// Find orphans and reunite with this folder parent
|
||||
let parentGUID = GUIDForId(parentId);
|
||||
let parentGUID = this.GUIDForId(parentId);
|
||||
let orphans = this._findAnnoItems(PARENT_ANNO, parentGUID);
|
||||
|
||||
this._log.debug("Reparenting orphans " + orphans + " to " + parentId);
|
||||
orphans.forEach(function(orphan) {
|
||||
// Append the orphan under the parent unless it's supposed to be first
|
||||
let insertPos = Svc.Bookmark.DEFAULT_INDEX;
|
||||
if (!Svc.Annos.itemHasAnnotation(orphan, PREDECESSOR_ANNO))
|
||||
insertPos = 0;
|
||||
|
||||
// Move the orphan to the parent and drop the missing parent annotation
|
||||
Svc.Bookmark.moveItem(orphan, parentId, insertPos);
|
||||
Svc.Bookmark.moveItem(orphan, parentId, Svc.Bookmark.DEFAULT_INDEX);
|
||||
Svc.Annos.removeItemAnnotation(orphan, PARENT_ANNO);
|
||||
});
|
||||
|
||||
// Fix up the ordering of the now-parented items
|
||||
orphans.forEach(this._attachFollowers, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move an item and all of its followers to a new position until reaching an
|
||||
* item that shouldn't be moved
|
||||
*/
|
||||
_moveItemChain: function BStore__moveItemChain(itemId, insertPos, stopId) {
|
||||
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
|
||||
|
||||
// Keep processing the item chain until it loops to the stop item
|
||||
do {
|
||||
// Figure out what's next in the chain
|
||||
let itemPos = Svc.Bookmark.getItemIndex(itemId);
|
||||
let nextId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
|
||||
|
||||
Svc.Bookmark.moveItem(itemId, parentId, insertPos);
|
||||
this._log.trace("Moved " + itemId + " to " + insertPos);
|
||||
|
||||
// Prepare for the next item in the chain
|
||||
insertPos = Svc.Bookmark.getItemIndex(itemId) + 1;
|
||||
itemId = nextId;
|
||||
|
||||
// Stop if we ran off the end or the item is looking for its pred.
|
||||
if (itemId == -1 || Svc.Annos.itemHasAnnotation(itemId, PREDECESSOR_ANNO))
|
||||
break;
|
||||
} while (itemId != stopId);
|
||||
},
|
||||
|
||||
/**
|
||||
* For the provided predecessor item, attach its followers to it
|
||||
*/
|
||||
_attachFollowers: function BStore__attachFollowers(predId) {
|
||||
let predGUID = GUIDForId(predId);
|
||||
let followers = this._findAnnoItems(PREDECESSOR_ANNO, predGUID);
|
||||
if (followers.length > 1)
|
||||
this._log.warn(predId + " has more than one followers: " + followers);
|
||||
|
||||
// Start at the first follower and move the chain of followers
|
||||
let parent = Svc.Bookmark.getFolderIdForItem(predId);
|
||||
followers.forEach(function(follow) {
|
||||
this._log.trace("Repositioning " + follow + " behind " + predId);
|
||||
if (Svc.Bookmark.getFolderIdForItem(follow) != parent) {
|
||||
this._log.warn("Follower doesn't have the same parent: " + parent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the chain of followers to after the predecessor
|
||||
let insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
|
||||
this._moveItemChain(follow, insertPos, predId);
|
||||
|
||||
// Remove the annotation now that we're putting it in the right spot
|
||||
Svc.Annos.removeItemAnnotation(follow, PREDECESSOR_ANNO);
|
||||
}, this);
|
||||
},
|
||||
|
||||
|
@ -526,10 +441,10 @@ BookmarksStore.prototype = {
|
|||
case "query":
|
||||
case "microsummary": {
|
||||
let uri = Utils.makeURI(record.bmkUri);
|
||||
newId = this._bms.insertBookmark(record._parent, uri, record._insertPos,
|
||||
record.title);
|
||||
this._log.debug(["created bookmark", newId, "under", record._parent, "at",
|
||||
record._insertPos, "as", record.title, record.bmkUri].join(" "));
|
||||
newId = this._bms.insertBookmark(record._parent, uri,
|
||||
Svc.Bookmark.DEFAULT_INDEX, record.title);
|
||||
this._log.debug(["created bookmark", newId, "under", record._parent,
|
||||
"as", record.title, record.bmkUri].join(" "));
|
||||
|
||||
this._tagURI(uri, record.tags);
|
||||
this._bms.setKeywordForBookmark(newId, record.keyword);
|
||||
|
@ -556,12 +471,14 @@ BookmarksStore.prototype = {
|
|||
} break;
|
||||
case "folder":
|
||||
newId = this._bms.createFolder(record._parent, record.title,
|
||||
record._insertPos);
|
||||
this._log.debug(["created folder", newId, "under", record._parent, "at",
|
||||
record._insertPos, "as", record.title].join(" "));
|
||||
Svc.Bookmark.DEFAULT_INDEX);
|
||||
this._log.debug(["created folder", newId, "under", record._parent,
|
||||
"as", record.title].join(" "));
|
||||
|
||||
if (record.description)
|
||||
Utils.anno(newId, "bookmarkProperties/description", record.description);
|
||||
|
||||
// record.children will be dealt with in _orderChildren.
|
||||
break;
|
||||
case "livemark":
|
||||
let siteURI = null;
|
||||
|
@ -569,15 +486,16 @@ BookmarksStore.prototype = {
|
|||
siteURI = Utils.makeURI(record.siteUri);
|
||||
|
||||
newId = this._ls.createLivemark(record._parent, record.title, siteURI,
|
||||
Utils.makeURI(record.feedUri), record._insertPos);
|
||||
this._log.debug(["created livemark", newId, "under", record._parent, "at",
|
||||
record._insertPos, "as", record.title, record.siteUri, record.feedUri].
|
||||
join(" "));
|
||||
Utils.makeURI(record.feedUri),
|
||||
Svc.Bookmark.DEFAULT_INDEX);
|
||||
this._log.debug(["created livemark", newId, "under", record._parent, "as",
|
||||
record.title, record.siteUri, record.feedUri].join(" "));
|
||||
break;
|
||||
case "separator":
|
||||
newId = this._bms.insertSeparator(record._parent, record._insertPos);
|
||||
this._log.debug(["created separator", newId, "under", record._parent,
|
||||
"at", record._insertPos].join(" "));
|
||||
newId = this._bms.insertSeparator(record._parent,
|
||||
Svc.Bookmark.DEFAULT_INDEX);
|
||||
this._log.debug(["created separator", newId, "under", record._parent]
|
||||
.join(" "));
|
||||
break;
|
||||
case "item":
|
||||
this._log.debug(" -> got a generic places item.. do nothing?");
|
||||
|
@ -592,7 +510,7 @@ BookmarksStore.prototype = {
|
|||
},
|
||||
|
||||
remove: function BStore_remove(record) {
|
||||
let itemId = idForGUID(record.id);
|
||||
let itemId = this.idForGUID(record.id);
|
||||
if (itemId <= 0) {
|
||||
this._log.debug("Item " + record.id + " already removed");
|
||||
return;
|
||||
|
@ -620,7 +538,7 @@ BookmarksStore.prototype = {
|
|||
},
|
||||
|
||||
update: function BStore_update(record) {
|
||||
let itemId = idForGUID(record.id);
|
||||
let itemId = this.idForGUID(record.id);
|
||||
|
||||
if (itemId <= 0) {
|
||||
this._log.debug("Skipping update for unknown item: " + record.id);
|
||||
|
@ -629,18 +547,10 @@ BookmarksStore.prototype = {
|
|||
|
||||
this._log.trace("Updating " + record.id + " (" + itemId + ")");
|
||||
|
||||
// Move the bookmark to a new parent if necessary
|
||||
// Move the bookmark to a new parent or new position if necessary
|
||||
if (Svc.Bookmark.getFolderIdForItem(itemId) != record._parent) {
|
||||
this._log.trace("Moving item to a new parent");
|
||||
Svc.Bookmark.moveItem(itemId, record._parent, record._insertPos);
|
||||
}
|
||||
// Move the chain of bookmarks to a new position
|
||||
else if (Svc.Bookmark.getItemIndex(itemId) != record._insertPos &&
|
||||
!record._orphan) {
|
||||
this._log.trace("Moving item and followers to a new position");
|
||||
|
||||
// Stop moving at the predecessor unless we don't have one
|
||||
this._moveItemChain(itemId, record._insertPos, record._predId || itemId);
|
||||
this._log.trace("Moving item to a new parent.");
|
||||
Svc.Bookmark.moveItem(itemId, record._parent, Svc.Bookmark.DEFAULT_INDEX);
|
||||
}
|
||||
|
||||
for (let [key, val] in Iterator(record.cleartext)) {
|
||||
|
@ -686,38 +596,98 @@ BookmarksStore.prototype = {
|
|||
case "feedUri":
|
||||
this._ls.setFeedURI(itemId, Utils.makeURI(val));
|
||||
break;
|
||||
case "children":
|
||||
Utils.anno(itemId, CHILDREN_ANNO, val.join(","));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_orderChildren: function _orderChildren() {
|
||||
for (let [guid, children] in Iterator(this._childrenToOrder)) {
|
||||
// Convert children GUIDs to aliases if they exist.
|
||||
children = children.map(function(guid) {
|
||||
return this.aliases[guid] || guid;
|
||||
}, this);
|
||||
|
||||
// Reorder children according to the GUID list. Gracefully deal
|
||||
// with missing items, e.g. locally deleted.
|
||||
let delta = 0;
|
||||
for (let idx = 0; idx < children.length; idx++) {
|
||||
let itemid = this.idForGUID(children[idx]);
|
||||
if (itemid == -1) {
|
||||
delta += 1;
|
||||
this._log.trace("Could not locate record " + children[idx]);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Svc.Bookmark.setItemIndex(itemid, idx - delta);
|
||||
} catch (ex) {
|
||||
this._log.debug("Could not move item " + children[idx] + ": " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the children annotation. If there were mismatches due to local
|
||||
// changes, we'll have to regenerate it from scratch, otherwise we can
|
||||
// just use the incoming value.
|
||||
let folderid = this.idForGUID(guid);
|
||||
if (delta) {
|
||||
this._updateChildrenAnno(folderid);
|
||||
} else {
|
||||
Utils.anno(folderid, CHILDREN_ANNO, children.join(","));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateChildrenAnno: function _updateChildrenAnno(itemid) {
|
||||
let node = node = this._getNode(itemid);
|
||||
let childids = [];
|
||||
|
||||
if (node.type == node.RESULT_TYPE_FOLDER &&
|
||||
!this._ls.isLivemark(node.itemId)) {
|
||||
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
|
||||
node.containerOpen = true;
|
||||
for (var i = 0; i < node.childCount; i++)
|
||||
childids.push(node.getChild(i).itemId);
|
||||
}
|
||||
let childGUIDs = childids.map(this.GUIDForId, this);
|
||||
Utils.anno(itemid, CHILDREN_ANNO, childGUIDs.join(","));
|
||||
return childGUIDs;
|
||||
},
|
||||
|
||||
get _removeAllChildrenAnnosStm() {
|
||||
let stmt = this._getStmt(
|
||||
"DELETE FROM moz_items_annos " +
|
||||
"WHERE anno_attribute_id = " +
|
||||
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)");
|
||||
stmt.params.anno_name = CHILDREN_ANNO;
|
||||
return stmt;
|
||||
},
|
||||
|
||||
_removeAllChildrenAnnos: function _removeAllChildrenAnnos() {
|
||||
Utils.queryAsync(this._removeAllChildrenAnnosStm);
|
||||
},
|
||||
|
||||
changeItemID: function BStore_changeItemID(oldID, newID) {
|
||||
// Remember the GUID change for incoming records
|
||||
this.aliases[oldID] = newID;
|
||||
|
||||
// Update any existing annotation references
|
||||
this._findAnnoItems(PARENT_ANNO, oldID).forEach(function(itemId) {
|
||||
Utils.anno(itemId, PARENT_ANNO, "T" + newID);
|
||||
}, this);
|
||||
this._findAnnoItems(PREDECESSOR_ANNO, oldID).forEach(function(itemId) {
|
||||
Utils.anno(itemId, PREDECESSOR_ANNO, "R" + newID);
|
||||
Utils.anno(itemId, PARENT_ANNO, newID);
|
||||
}, this);
|
||||
|
||||
// Make sure there's an item to change GUIDs
|
||||
let itemId = idForGUID(oldID);
|
||||
let itemId = this.idForGUID(oldID);
|
||||
if (itemId <= 0)
|
||||
return;
|
||||
|
||||
this._log.debug("Changing GUID " + oldID + " to " + newID);
|
||||
this._setGUID(itemId, newID);
|
||||
},
|
||||
|
||||
_setGUID: function BStore__setGUID(itemId, guid) {
|
||||
let collision = idForGUID(guid);
|
||||
if (collision != -1) {
|
||||
this._log.warn("Freeing up GUID " + guid + " used by " + collision);
|
||||
Svc.Annos.removeItemAnnotation(collision, "placesInternal/GUID");
|
||||
}
|
||||
Svc.Bookmark.setItemGUID(itemId, guid);
|
||||
// Update parent
|
||||
let parentid = this._bms.getFolderIdForItem(itemId);
|
||||
this._updateChildrenAnno(parentid);
|
||||
},
|
||||
|
||||
_getNode: function BStore__getNode(folder) {
|
||||
|
@ -756,9 +726,22 @@ BookmarksStore.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_getChildGUIDsForId: function _getChildGUIDsForId(itemid) {
|
||||
let anno;
|
||||
try {
|
||||
anno = Utils.anno(itemid, CHILDREN_ANNO);
|
||||
} catch(ex) {
|
||||
// Ignore
|
||||
}
|
||||
if (anno)
|
||||
return anno.split(",");
|
||||
|
||||
return this._updateChildrenAnno(itemid);
|
||||
},
|
||||
|
||||
// Create a record starting from the weave id (places guid)
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let placeId = idForGUID(id);
|
||||
let placeId = this.idForGUID(id);
|
||||
let record;
|
||||
if (placeId <= 0) { // deleted item
|
||||
record = new PlacesItem(collection, id);
|
||||
|
@ -818,15 +801,18 @@ BookmarksStore.prototype = {
|
|||
record = new BookmarkFolder(collection, id);
|
||||
}
|
||||
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
if (parent > 0)
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
record.title = this._bms.getItemTitle(placeId);
|
||||
record.description = this._getDescription(placeId);
|
||||
record.children = this._getChildGUIDsForId(placeId);
|
||||
break;
|
||||
|
||||
case this._bms.TYPE_SEPARATOR:
|
||||
record = new BookmarkSeparator(collection, id);
|
||||
// Create a positioning identifier for the separator
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
if (parent > 0)
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
// Create a positioning identifier for the separator, used by _lazyMap
|
||||
record.pos = Svc.Bookmark.getItemIndex(placeId);
|
||||
break;
|
||||
|
||||
|
@ -841,25 +827,147 @@ BookmarksStore.prototype = {
|
|||
this._bms.getItemType(placeId));
|
||||
}
|
||||
|
||||
record.parentid = this._getParentGUIDForId(placeId);
|
||||
record.predecessorid = this._getPredecessorGUIDForId(placeId);
|
||||
record.parentid = this.GUIDForId(parent);
|
||||
record.sortindex = this._calculateIndex(record);
|
||||
|
||||
return record;
|
||||
},
|
||||
|
||||
__frecencyStm: null,
|
||||
_stmts: {},
|
||||
_getStmt: function(query) {
|
||||
if (query in this._stmts)
|
||||
return this._stmts[query];
|
||||
|
||||
this._log.trace("Creating SQL statement: " + query);
|
||||
return this._stmts[query] = Utils.createStatement(this._hsvc.DBConnection,
|
||||
query);
|
||||
},
|
||||
|
||||
get _frecencyStm() {
|
||||
if (!this.__frecencyStm) {
|
||||
this._log.trace("Creating SQL statement: _frecencyStm");
|
||||
this.__frecencyStm = Utils.createStatement(
|
||||
Svc.History.DBConnection,
|
||||
return this._getStmt(
|
||||
"SELECT frecency " +
|
||||
"FROM moz_places " +
|
||||
"WHERE url = :url " +
|
||||
"LIMIT 1");
|
||||
},
|
||||
|
||||
get _addGUIDAnnotationNameStm() {
|
||||
let stmt = this._getStmt(
|
||||
"INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)");
|
||||
stmt.params.anno_name = GUID_ANNO;
|
||||
return stmt;
|
||||
},
|
||||
|
||||
get _checkGUIDItemAnnotationStm() {
|
||||
let stmt = this._getStmt(
|
||||
"SELECT b.id AS item_id, " +
|
||||
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS name_id, " +
|
||||
"a.id AS anno_id, a.dateAdded AS anno_date " +
|
||||
"FROM moz_bookmarks b " +
|
||||
"LEFT JOIN moz_items_annos a ON a.item_id = b.id " +
|
||||
"AND a.anno_attribute_id = name_id " +
|
||||
"WHERE b.id = :item_id");
|
||||
stmt.params.anno_name = GUID_ANNO;
|
||||
return stmt;
|
||||
},
|
||||
|
||||
get _addItemAnnotationStm() {
|
||||
return this._getStmt(
|
||||
"INSERT OR REPLACE INTO moz_items_annos " +
|
||||
"(id, item_id, anno_attribute_id, mime_type, content, flags, " +
|
||||
"expiration, type, dateAdded, lastModified) " +
|
||||
"VALUES (:id, :item_id, :name_id, :mime_type, :content, :flags, " +
|
||||
":expiration, :type, :date_added, :last_modified)");
|
||||
},
|
||||
|
||||
// Some helper functions to handle GUIDs
|
||||
_setGUID: function _setGUID(id, guid) {
|
||||
if (arguments.length == 1)
|
||||
guid = Utils.makeGUID();
|
||||
|
||||
// Ensure annotation name exists
|
||||
Utils.queryAsync(this._addGUIDAnnotationNameStm);
|
||||
|
||||
let stmt = this._checkGUIDItemAnnotationStm;
|
||||
stmt.params.item_id = id;
|
||||
let result = Utils.queryAsync(stmt, ["item_id", "name_id", "anno_id",
|
||||
"anno_date"])[0];
|
||||
if (!result) {
|
||||
let log = Log4Moz.repository.getLogger("Engine.Bookmarks");
|
||||
log.warn("Couldn't annotate bookmark id " + id);
|
||||
return guid;
|
||||
}
|
||||
return this.__frecencyStm;
|
||||
|
||||
stmt = this._addItemAnnotationStm;
|
||||
if (result.anno_id) {
|
||||
stmt.params.id = result.anno_id;
|
||||
stmt.params.date_added = result.anno_date;
|
||||
} else {
|
||||
stmt.params.id = null;
|
||||
stmt.params.date_added = Date.now() * 1000;
|
||||
}
|
||||
stmt.params.item_id = result.item_id;
|
||||
stmt.params.name_id = result.name_id;
|
||||
stmt.params.content = guid;
|
||||
stmt.params.flags = 0;
|
||||
stmt.params.expiration = Ci.nsIAnnotationService.EXPIRE_NEVER;
|
||||
stmt.params.type = Ci.nsIAnnotationService.TYPE_STRING;
|
||||
stmt.params.last_modified = Date.now() * 1000;
|
||||
Utils.queryAsync(stmt);
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
get _guidForIdStm() {
|
||||
let stmt = this._getStmt(
|
||||
"SELECT a.content AS guid " +
|
||||
"FROM moz_items_annos a " +
|
||||
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
|
||||
"JOIN moz_bookmarks b ON b.id = a.item_id " +
|
||||
"WHERE n.name = :anno_name AND b.id = :item_id");
|
||||
stmt.params.anno_name = GUID_ANNO;
|
||||
return stmt;
|
||||
},
|
||||
|
||||
GUIDForId: function GUIDForId(id) {
|
||||
for (let [guid, specialId] in Iterator(kSpecialIds))
|
||||
if (id == specialId)
|
||||
return guid;
|
||||
|
||||
let stmt = this._guidForIdStm;
|
||||
stmt.params.item_id = id;
|
||||
|
||||
// Use the existing GUID if it exists
|
||||
let result = Utils.queryAsync(stmt, ["guid"])[0];
|
||||
if (result)
|
||||
return result.guid;
|
||||
|
||||
// Give the uri a GUID if it doesn't have one
|
||||
return this._setGUID(id);
|
||||
},
|
||||
|
||||
get _idForGUIDStm() {
|
||||
let stmt = this._getStmt(
|
||||
"SELECT a.item_id AS item_id " +
|
||||
"FROM moz_items_annos a " +
|
||||
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
|
||||
"WHERE n.name = :anno_name AND a.content = :guid");
|
||||
stmt.params.anno_name = GUID_ANNO;
|
||||
return stmt;
|
||||
},
|
||||
|
||||
idForGUID: function idForGUID(guid) {
|
||||
if (guid in kSpecialIds)
|
||||
return kSpecialIds[guid];
|
||||
|
||||
let stmt = this._idForGUIDStm;
|
||||
// guid might be a String object rather than a string.
|
||||
stmt.params.guid = guid.toString();
|
||||
|
||||
let result = Utils.queryAsync(stmt, ["item_id"])[0];
|
||||
if (result)
|
||||
return result.item_id;
|
||||
return -1;
|
||||
},
|
||||
|
||||
_calculateIndex: function _calculateIndex(record) {
|
||||
|
@ -884,71 +992,10 @@ BookmarksStore.prototype = {
|
|||
return index;
|
||||
},
|
||||
|
||||
_getParentGUIDForId: function BStore__getParentGUIDForId(itemId) {
|
||||
// Give the parent annotation if it exists
|
||||
try {
|
||||
// XXX Work around Bug 510628 by removing prepended parenT
|
||||
return Utils.anno(itemId, PARENT_ANNO).slice(1);
|
||||
}
|
||||
catch(ex) {}
|
||||
|
||||
let parentid = this._bms.getFolderIdForItem(itemId);
|
||||
if (parentid == -1) {
|
||||
this._log.debug("Found orphan bookmark, reparenting to unfiled");
|
||||
parentid = this._bms.unfiledBookmarksFolder;
|
||||
this._bms.moveItem(itemId, parentid, -1);
|
||||
}
|
||||
return GUIDForId(parentid);
|
||||
},
|
||||
|
||||
_getPredecessorGUIDForId: function BStore__getPredecessorGUIDForId(itemId) {
|
||||
// Give the predecessor annotation if it exists
|
||||
try {
|
||||
// XXX Work around Bug 510628 by removing prepended predecessoR
|
||||
return Utils.anno(itemId, PREDECESSOR_ANNO).slice(1);
|
||||
}
|
||||
catch(ex) {}
|
||||
|
||||
// Figure out the predecessor, unless it's the first item
|
||||
let itemPos = Svc.Bookmark.getItemIndex(itemId);
|
||||
if (itemPos == 0)
|
||||
return;
|
||||
|
||||
// For items directly under unfiled/unsorted, give no predecessor
|
||||
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
|
||||
if (parentId == Svc.Bookmark.unfiledBookmarksFolder)
|
||||
return;
|
||||
|
||||
let predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos - 1);
|
||||
if (predecessorId == -1) {
|
||||
this._log.debug("No predecessor directly before " + itemId + " under " +
|
||||
parentId + " at " + itemPos);
|
||||
|
||||
// Find the predecessor before the item
|
||||
do {
|
||||
// No more items to check, it must be the first one
|
||||
if (--itemPos < 0)
|
||||
break;
|
||||
predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos);
|
||||
} while (predecessorId == -1);
|
||||
|
||||
// Fix up the item to be at the right position for next time
|
||||
itemPos++;
|
||||
this._log.debug("Fixing " + itemId + " to be at position " + itemPos);
|
||||
Svc.Bookmark.moveItem(itemId, parentId, itemPos);
|
||||
|
||||
// There must be no predecessor for this item!
|
||||
if (itemPos == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
return GUIDForId(predecessorId);
|
||||
},
|
||||
|
||||
_getChildren: function BStore_getChildren(guid, items) {
|
||||
let node = guid; // the recursion case
|
||||
if (typeof(node) == "string") // callers will give us the guid as the first arg
|
||||
node = this._getNode(idForGUID(guid));
|
||||
node = this._getNode(this.idForGUID(guid));
|
||||
|
||||
if (node.type == node.RESULT_TYPE_FOLDER &&
|
||||
!this._ls.isLivemark(node.itemId)) {
|
||||
|
@ -958,7 +1005,7 @@ BookmarksStore.prototype = {
|
|||
// Remember all the children GUIDs and recursively get more
|
||||
for (var i = 0; i < node.childCount; i++) {
|
||||
let child = node.getChild(i);
|
||||
items[GUIDForId(child.itemId)] = true;
|
||||
items[this.GUIDForId(child.itemId)] = true;
|
||||
this._getChildren(child, items);
|
||||
}
|
||||
}
|
||||
|
@ -979,7 +1026,8 @@ BookmarksStore.prototype = {
|
|||
},
|
||||
|
||||
getAllIDs: function BStore_getAllIDs() {
|
||||
let items = {};
|
||||
let items = {"menu": true,
|
||||
"toolbar": true};
|
||||
for (let [guid, id] in Iterator(kSpecialIds))
|
||||
if (guid != "places" && guid != "tags")
|
||||
this._getChildren(guid, items);
|
||||
|
@ -999,10 +1047,6 @@ BookmarksStore.prototype = {
|
|||
function BookmarksTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
|
||||
// Ignore changes to the special roots
|
||||
for (let guid in kSpecialIds)
|
||||
this.ignoreID(guid);
|
||||
|
||||
Svc.Obs.add("places-shutdown", this);
|
||||
Svc.Obs.add("weave:engine:start-tracking", this);
|
||||
Svc.Obs.add("weave:engine:stop-tracking", this);
|
||||
|
@ -1031,6 +1075,12 @@ BookmarksTracker.prototype = {
|
|||
this.__ls = null;
|
||||
this.__bms = null;
|
||||
break;
|
||||
case "weave:service:start-over":
|
||||
// User has decided to stop syncing, we're going to stop tracking soon.
|
||||
// This means we have to clean up the children annotations so that they
|
||||
// won't be out of sync with reality if/when we start tracking again.
|
||||
Engines.get("bookmarks")._store._removeAllChildrenAnnos();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1056,6 +1106,15 @@ BookmarksTracker.prototype = {
|
|||
Ci.nsISupportsWeakReference
|
||||
]),
|
||||
|
||||
_GUIDForId: function _GUIDForId(item_id) {
|
||||
// Isn't indirection fun...
|
||||
return Engines.get("bookmarks")._store.GUIDForId(item_id);
|
||||
},
|
||||
|
||||
_updateChildrenAnno: function _updateChildrenAnno(itemid) {
|
||||
return Engines.get("bookmarks")._store._updateChildrenAnno(itemid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a bookmark (places) id to be uploaded and bump up the sync score
|
||||
*
|
||||
|
@ -1063,21 +1122,10 @@ BookmarksTracker.prototype = {
|
|||
* Places internal id of the bookmark to upload
|
||||
*/
|
||||
_addId: function BMT__addId(itemId) {
|
||||
if (this.addChangedID(GUIDForId(itemId)))
|
||||
if (this.addChangedID(this._GUIDForId(itemId, true)))
|
||||
this._upScore();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the successor id for the item that follows the given item
|
||||
*/
|
||||
_addSuccessor: function BMT__addSuccessor(itemId) {
|
||||
let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
|
||||
let itemPos = Svc.Bookmark.getItemIndex(itemId);
|
||||
let succId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
|
||||
if (succId != -1)
|
||||
this._addId(succId);
|
||||
},
|
||||
|
||||
/* Every add/remove/change is worth 10 points */
|
||||
_upScore: function BMT__upScore() {
|
||||
this.score += 10;
|
||||
|
@ -1102,7 +1150,7 @@ BookmarksTracker.prototype = {
|
|||
|
||||
// Make sure to remove items that have the exclude annotation
|
||||
if (Svc.Annos.itemHasAnnotation(itemId, "places/excludeFromBackup")) {
|
||||
this.removeChangedID(GUIDForId(itemId));
|
||||
this.removeChangedID(this._GUIDForId(itemId, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1129,7 +1177,8 @@ BookmarksTracker.prototype = {
|
|||
|
||||
this._log.trace("onItemAdded: " + itemId);
|
||||
this._addId(itemId);
|
||||
this._addSuccessor(itemId);
|
||||
this._updateChildrenAnno(folder);
|
||||
this._addId(folder);
|
||||
},
|
||||
|
||||
onBeforeItemRemoved: function BMT_onBeforeItemRemoved(itemId) {
|
||||
|
@ -1138,7 +1187,9 @@ BookmarksTracker.prototype = {
|
|||
|
||||
this._log.trace("onBeforeItemRemoved: " + itemId);
|
||||
this._addId(itemId);
|
||||
this._addSuccessor(itemId);
|
||||
let folder = Svc.Bookmark.getFolderIdForItem(itemId);
|
||||
this._updateChildrenAnno(folder);
|
||||
this._addId(folder);
|
||||
},
|
||||
|
||||
_ensureMobileQuery: function _ensureMobileQuery() {
|
||||
|
@ -1202,17 +1253,16 @@ BookmarksTracker.prototype = {
|
|||
return;
|
||||
|
||||
this._log.trace("onItemMoved: " + itemId);
|
||||
this._addId(itemId);
|
||||
this._addSuccessor(itemId);
|
||||
|
||||
// Get the thing that's now at the old place
|
||||
let oldSucc = Svc.Bookmark.getIdForItemAt(oldParent, oldIndex);
|
||||
if (oldSucc != -1)
|
||||
this._addId(oldSucc);
|
||||
this._updateChildrenAnno(oldParent);
|
||||
this._addId(oldParent);
|
||||
if (oldParent != newParent) {
|
||||
this._addId(itemId);
|
||||
this._updateChildrenAnno(newParent);
|
||||
this._addId(newParent);
|
||||
}
|
||||
|
||||
// Remove any position annotations now that the user moved the item
|
||||
Svc.Annos.removeItemAnnotation(itemId, PARENT_ANNO);
|
||||
Svc.Annos.removeItemAnnotation(itemId, PREDECESSOR_ANNO);
|
||||
},
|
||||
|
||||
onBeginUpdateBatch: function BMT_onBeginUpdateBatch() {},
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Richard Newman <rnewman@mozilla.com>
|
||||
*
|
||||
* 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
|
||||
|
@ -43,6 +44,7 @@ const Cu = Components.utils;
|
|||
const GUID_ANNO = "sync/guid";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
|
@ -52,6 +54,7 @@ Cu.import("resource://services-sync/log4moz.js");
|
|||
|
||||
function HistoryEngine() {
|
||||
SyncEngine.call(this, "History");
|
||||
this.downloadLimit = MAX_HISTORY_DOWNLOAD;
|
||||
}
|
||||
HistoryEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
|
@ -322,7 +325,7 @@ HistoryStore.prototype = {
|
|||
getAllIDs: function HistStore_getAllIDs() {
|
||||
// Only get places visited within the last 30 days (30*24*60*60*1000ms)
|
||||
this._allUrlStm.params.cutoff_date = (Date.now() - 2592000000) * 1000;
|
||||
this._allUrlStm.params.max_results = 5000;
|
||||
this._allUrlStm.params.max_results = MAX_HISTORY_UPLOAD;
|
||||
|
||||
let urls = Utils.queryAsync(this._allUrlStm, "url");
|
||||
let self = this;
|
||||
|
|
|
@ -133,16 +133,13 @@ PrefStore.prototype = {
|
|||
let ltmExists = true;
|
||||
let ltm = {};
|
||||
let enabledBefore = false;
|
||||
let enabledPref = "lightweightThemes.isThemeSelected";
|
||||
let prevTheme = "";
|
||||
try {
|
||||
Cu.import("resource://gre/modules/LightweightThemeManager.jsm", ltm);
|
||||
ltm = ltm.LightweightThemeManager;
|
||||
|
||||
let enabledPref = "lightweightThemes.isThemeSelected";
|
||||
if (this._prefs.getPrefType(enabledPref) == this._prefs.PREF_BOOL) {
|
||||
enabledBefore = this._prefs.getBoolPref(enabledPref);
|
||||
prevTheme = ltm.currentTheme;
|
||||
}
|
||||
enabledBefore = this._prefs.get(enabledPref, false);
|
||||
prevTheme = ltm.currentTheme;
|
||||
} catch(ex) {
|
||||
ltmExists = false;
|
||||
} // LightweightThemeManager only exists in Firefox 3.6+
|
||||
|
@ -166,7 +163,7 @@ PrefStore.prototype = {
|
|||
|
||||
// Notify the lightweight theme manager of all the new values
|
||||
if (ltmExists) {
|
||||
let enabledNow = this._prefs.getBoolPref("lightweightThemes.isThemeSelected");
|
||||
let enabledNow = this._prefs.get(enabledPref, false);
|
||||
if (enabledBefore && !enabledNow)
|
||||
ltm.currentTheme = null;
|
||||
else if (enabledNow && ltm.usedThemes[0] != prevTheme) {
|
||||
|
|
|
@ -1062,6 +1062,27 @@ WeaveSvc.prototype = {
|
|||
|
||||
this._log.debug("Fetching global metadata record");
|
||||
let meta = Records.get(this.metaURL);
|
||||
|
||||
// Checking modified time of the meta record.
|
||||
if (infoResponse &&
|
||||
(infoResponse.obj.meta != this.metaModified) &&
|
||||
!meta.isNew) {
|
||||
|
||||
// Delete the cached meta record...
|
||||
this._log.debug("Clearing cached meta record. metaModified is " +
|
||||
JSON.stringify(this.metaModified) + ", setting to " +
|
||||
JSON.stringify(infoResponse.obj.meta));
|
||||
Records.del(this.metaURL);
|
||||
|
||||
// ... fetch the current record from the server, and COPY THE FLAGS.
|
||||
let newMeta = Records.get(this.metaURL);
|
||||
newMeta.isNew = meta.isNew;
|
||||
newMeta.changed = meta.changed;
|
||||
|
||||
// Switch in the new meta object and record the new time.
|
||||
meta = newMeta;
|
||||
this.metaModified = infoResponse.obj.meta;
|
||||
}
|
||||
|
||||
let remoteVersion = (meta && meta.payload.storageVersion)?
|
||||
meta.payload.storageVersion : "";
|
||||
|
@ -1073,6 +1094,8 @@ WeaveSvc.prototype = {
|
|||
// we need to convert it to a number as older clients used it as a string.
|
||||
if (!meta || !meta.payload.storageVersion || !meta.payload.syncID ||
|
||||
STORAGE_VERSION > parseFloat(remoteVersion)) {
|
||||
|
||||
this._log.info("One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed.");
|
||||
|
||||
// abort the server wipe if the GET status was anything other than 404 or 200
|
||||
let status = Records.response.status;
|
||||
|
@ -1108,6 +1131,8 @@ WeaveSvc.prototype = {
|
|||
return false;
|
||||
}
|
||||
else if (meta.payload.syncID != this.syncID) {
|
||||
|
||||
this._log.info("Sync IDs differ. Local is " + this.syncID + ", remote is " + meta.payload.syncID);
|
||||
this.resetClient();
|
||||
this.syncID = meta.payload.syncID;
|
||||
this._log.debug("Clear cached values and take syncId: " + this.syncID);
|
||||
|
@ -1129,7 +1154,7 @@ WeaveSvc.prototype = {
|
|||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (!this.upgradeSyncKey(meta.payload.syncID)) {
|
||||
|
@ -1142,7 +1167,7 @@ WeaveSvc.prototype = {
|
|||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1408,16 +1433,6 @@ WeaveSvc.prototype = {
|
|||
for each (let engine in [Clients].concat(Engines.getAll()))
|
||||
engine.lastModified = info.obj[engine.name] || 0;
|
||||
|
||||
// If the modified time of the meta record ever changes, clear the cache.
|
||||
// ... unless meta is marked as new.
|
||||
if ((info.obj.meta != this.metaModified) && !Records.get(this.metaURL).isNew) {
|
||||
this._log.debug("Clearing cached meta record. metaModified is " +
|
||||
JSON.stringify(this.metaModified) + ", setting to " +
|
||||
JSON.stringify(info.obj.meta));
|
||||
Records.del(this.metaURL);
|
||||
this.metaModified = info.obj.meta;
|
||||
}
|
||||
|
||||
if (!(this._remoteSetup(info)))
|
||||
throw "aborting sync, remote setup failed";
|
||||
|
||||
|
@ -1517,6 +1532,15 @@ WeaveSvc.prototype = {
|
|||
let meta = Records.get(this.metaURL);
|
||||
if (meta.isNew || !meta.payload.engines)
|
||||
return;
|
||||
|
||||
// If we're the only client, and no engines are marked as enabled,
|
||||
// thumb our noses at the server data: it can't be right.
|
||||
// Belt-and-suspenders approach to Bug 615926.
|
||||
if ((this.numClients <= 1) &&
|
||||
([e for (e in meta.payload.engines) if (e != "clients")].length == 0)) {
|
||||
this._log.info("One client and no enabled engines: not touching local engine status.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._ignorePrefObserver = true;
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ PlacesItem.prototype = {
|
|||
};
|
||||
|
||||
Utils.deferGetSet(PlacesItem, "cleartext", ["hasDupe", "parentid", "parentName",
|
||||
"predecessorid", "type"]);
|
||||
"type"]);
|
||||
|
||||
function Bookmark(collection, id, type) {
|
||||
PlacesItem.call(this, collection, id, type || "bookmark");
|
||||
|
@ -127,7 +127,8 @@ BookmarkFolder.prototype = {
|
|||
_logName: "Record.Folder",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title"]);
|
||||
Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title",
|
||||
"children"]);
|
||||
|
||||
function Livemark(collection, id) {
|
||||
BookmarkFolder.call(this, collection, id, "livemark");
|
||||
|
|
|
@ -392,3 +392,19 @@ let _ = function(some, debug, text, to) print(Array.slice(arguments).join(" "));
|
|||
_("Setting the identity for passphrase");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
|
||||
|
||||
/*
|
||||
* Test setup helpers.
|
||||
*/
|
||||
|
||||
// Turn WBO cleartext into "encrypted" payload as it goes over the wire
|
||||
function encryptPayload(cleartext) {
|
||||
if (typeof cleartext == "object") {
|
||||
cleartext = JSON.stringify(cleartext);
|
||||
}
|
||||
|
||||
return {ciphertext: cleartext, // ciphertext == cleartext with fake crypto
|
||||
IV: "irrelevant",
|
||||
hmac: Utils.sha256HMAC(cleartext, Utils.makeHMACKey(""))};
|
||||
}
|
||||
|
||||
|
|
|
@ -259,3 +259,12 @@ ServerCollection.prototype = {
|
|||
}
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Test setup helpers.
|
||||
*/
|
||||
function sync_httpd_setup(handlers) {
|
||||
handlers["/1.0/foo/storage/meta/global"]
|
||||
= (new ServerWBO('global', {})).handler();
|
||||
return httpd_setup(handlers);
|
||||
}
|
||||
|
|
|
@ -39,133 +39,96 @@ function check(expected) {
|
|||
}
|
||||
|
||||
function run_test() {
|
||||
let store = new BookmarksEngine()._store;
|
||||
initTestLogging("Trace");
|
||||
|
||||
_("Starting with a clean slate of no bookmarks");
|
||||
let store = new (new BookmarksEngine())._storeObj();
|
||||
store.wipe();
|
||||
check([]);
|
||||
|
||||
function $B(name, parent, pred) {
|
||||
function bookmark(name, parent) {
|
||||
let bookmark = new Bookmark("http://weave.server/my-bookmark");
|
||||
bookmark.id = name;
|
||||
bookmark.title = name;
|
||||
bookmark.bmkUri = "http://uri/";
|
||||
bookmark.parentid = parent || "unfiled";
|
||||
bookmark.predecessorid = pred;
|
||||
bookmark.tags = [];
|
||||
store.applyIncoming(bookmark);
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
function $F(name, parent, pred) {
|
||||
function folder(name, parent, children) {
|
||||
let folder = new BookmarkFolder("http://weave.server/my-bookmark-folder");
|
||||
folder.id = name;
|
||||
folder.title = name;
|
||||
folder.parentid = parent || "unfiled";
|
||||
folder.predecessorid = pred;
|
||||
store.applyIncoming(folder);
|
||||
folder.children = children;
|
||||
return folder;
|
||||
}
|
||||
|
||||
function apply(record) {
|
||||
store._childrenToOrder = {};
|
||||
store.applyIncoming(record);
|
||||
store._orderChildren();
|
||||
delete store._childrenToOrder;
|
||||
}
|
||||
|
||||
_("basic add first bookmark");
|
||||
$B("10", "");
|
||||
apply(bookmark("10", ""));
|
||||
check(["10"]);
|
||||
|
||||
_("basic append behind 10");
|
||||
$B("20", "", "10");
|
||||
apply(bookmark("20", ""));
|
||||
check(["10", "20"]);
|
||||
|
||||
_("basic create in folder");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30");
|
||||
apply(bookmark("31", "f30"));
|
||||
let f30 = folder("f30", "", ["31"]);
|
||||
apply(f30);
|
||||
check(["10", "20", ["31"]]);
|
||||
|
||||
_("insert missing predecessor -> append");
|
||||
$B("50", "", "f40");
|
||||
check(["10", "20", ["31"], "50"]);
|
||||
|
||||
_("insert missing parent -> append");
|
||||
$B("41", "f40");
|
||||
check(["10", "20", ["31"], "50", "41"]);
|
||||
_("insert missing parent -> append to unfiled");
|
||||
apply(bookmark("41", "f40"));
|
||||
check(["10", "20", ["31"], "41"]);
|
||||
|
||||
_("insert another missing parent -> append");
|
||||
$B("42", "f40", "41");
|
||||
check(["10", "20", ["31"], "50", "41", "42"]);
|
||||
apply(bookmark("42", "f40"));
|
||||
check(["10", "20", ["31"], "41", "42"]);
|
||||
|
||||
_("insert folder -> move children and followers");
|
||||
$F("f40", "", "f30");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
let f40 = folder("f40", "", ["41", "42"]);
|
||||
apply(f40);
|
||||
check(["10", "20", ["31"], ["41", "42"]]);
|
||||
|
||||
_("Moving 10 behind 50 -> update 10, 20");
|
||||
$B("10", "", "50");
|
||||
$B("20", "");
|
||||
check(["20", ["31"], ["41", "42"], "50", "10"]);
|
||||
_("Moving 41 behind 42 -> update f40");
|
||||
f40.children = ["42", "41"];
|
||||
apply(f40);
|
||||
check(["10", "20", ["31"], ["42", "41"]]);
|
||||
|
||||
_("Moving 10 back to front -> update 10, 20");
|
||||
$B("10", "");
|
||||
$B("20", "", "10");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
f40.children = ["41", "42"];
|
||||
apply(f40);
|
||||
check(["10", "20", ["31"], ["41", "42"]]);
|
||||
|
||||
_("Moving 10 behind 50 in different order -> update 20, 10");
|
||||
$B("20", "");
|
||||
$B("10", "", "50");
|
||||
check(["20", ["31"], ["41", "42"], "50", "10"]);
|
||||
_("Moving 20 behind 42 in f40 -> update 50");
|
||||
apply(bookmark("20", "f40"));
|
||||
check(["10", ["31"], ["41", "42", "20"]]);
|
||||
|
||||
_("Moving 10 back to front in different order -> update 20, 10");
|
||||
$B("20", "", "10");
|
||||
$B("10", "");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
_("Moving 10 in front of 31 in f30 -> update 10, f30");
|
||||
apply(bookmark("10", "f30"));
|
||||
f30.children = ["10", "31"];
|
||||
apply(f30);
|
||||
check([["10", "31"], ["41", "42", "20"]]);
|
||||
|
||||
_("Moving 50 behind 42 in f40 -> update 50");
|
||||
$B("50", "f40", "42");
|
||||
check(["10", "20", ["31"], ["41", "42", "50"]]);
|
||||
_("Moving 20 from f40 to f30 -> update 20, f30");
|
||||
apply(bookmark("20", "f30"));
|
||||
f30.children = ["10", "20", "31"];
|
||||
apply(f30);
|
||||
check([["10", "20", "31"], ["41", "42"]]);
|
||||
|
||||
_("Moving 10 in front of 31 in f30 -> update 10, 20, 31");
|
||||
$B("10", "f30");
|
||||
$B("20", "");
|
||||
$B("31", "f30", "10");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
_("Move 20 back to front -> update 20, f30");
|
||||
apply(bookmark("20", ""));
|
||||
f30.children = ["10", "31"];
|
||||
apply(f30);
|
||||
check([["10", "31"], ["41", "42"], "20"]);
|
||||
|
||||
_("Moving 20 between 10 and 31 -> update 20, f30, 31");
|
||||
$B("20", "f30", "10");
|
||||
$F("f30", "");
|
||||
$B("31", "f30", "20");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front -> update 20, f30, 31");
|
||||
$B("20", "");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30", "10");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 20 between 10 and 31 different order -> update f30, 20, 31");
|
||||
$F("f30", "");
|
||||
$B("20", "f30", "10");
|
||||
$B("31", "f30", "20");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front different order -> update f30, 31, 20");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30", "10");
|
||||
$B("20", "");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 20 between 10 and 31 different order 2 -> update 31, f30, 20");
|
||||
$B("31", "f30", "20");
|
||||
$F("f30", "");
|
||||
$B("20", "f30", "10");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front different order 2 -> update 31, f30, 20");
|
||||
$B("31", "f30", "10");
|
||||
$F("f30", "", "20");
|
||||
$B("20", "");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 10, 31 to f40 but update in reverse -> update 41, 31, 10");
|
||||
$B("41", "f40", "31");
|
||||
$B("31", "f40", "10");
|
||||
$B("10", "f40");
|
||||
check(["20", [], ["10", "31", "41", "42", "50"]]);
|
||||
|
||||
_("Reparent f40 into f30");
|
||||
$F("f40", "f30");
|
||||
check(["20", [["10", "31", "41", "42", "50"]]]);
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
_("Make sure bad bookmarks can still get their predecessors");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let baseuri = "http://fake/uri/";
|
||||
|
||||
_("Starting with a clean slate of no bookmarks");
|
||||
let store = new (new BookmarksEngine())._storeObj();
|
||||
store.wipe();
|
||||
|
||||
let uri = Utils.makeURI("http://uri/");
|
||||
function insert(pos, folder) {
|
||||
folder = folder || Svc.Bookmark.toolbarFolder;
|
||||
let name = "pos" + pos;
|
||||
let bmk = Svc.Bookmark.insertBookmark(folder, uri, pos, name);
|
||||
Svc.Bookmark.setItemGUID(bmk, name);
|
||||
return bmk;
|
||||
}
|
||||
|
||||
_("Creating a couple bookmarks that create holes");
|
||||
let first = insert(5);
|
||||
let second = insert(10);
|
||||
|
||||
_("Making sure the record created for the first has no predecessor");
|
||||
let pos5 = store.createRecord("pos5");
|
||||
do_check_eq(pos5.predecessorid, undefined);
|
||||
|
||||
_("Making sure the second record has the first as its predecessor");
|
||||
let pos10 = store.createRecord("pos10");
|
||||
do_check_eq(pos10.predecessorid, "pos5");
|
||||
|
||||
_("Make sure the index of item gets fixed");
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(first), 0);
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(second), 1);
|
||||
|
||||
_("Make sure things that are in unsorted don't set the predecessor");
|
||||
insert(0, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
insert(1, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
do_check_eq(store.createRecord("pos0").predecessorid,
|
||||
undefined);
|
||||
do_check_eq(store.createRecord("pos1").predecessorid,
|
||||
undefined);
|
||||
}
|
|
@ -1,33 +1,35 @@
|
|||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/type_records/bookmark.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let store = new BookmarksEngine()._store;
|
||||
store.wipe();
|
||||
Engines.register(BookmarksEngine);
|
||||
let engine = Engines.get("bookmarks");
|
||||
let store = engine._store;
|
||||
let fxuri = Utils.makeURI("http://getfirefox.com/");
|
||||
let tburi = Utils.makeURI("http://getthunderbird.com/");
|
||||
|
||||
function test_bookmark_create() {
|
||||
try {
|
||||
_("Ensure the record isn't present yet.");
|
||||
let fxuri = Utils.makeURI("http://getfirefox.com/");
|
||||
let ids = Svc.Bookmark.getBookmarkIdsForURI(fxuri, {});
|
||||
do_check_eq(ids.length, 0);
|
||||
|
||||
_("Let's create a new record.");
|
||||
let fxrecord = {id: "{5d81b87c-d5fc-42d9-a114-d69b7342f10e}0",
|
||||
type: "bookmark",
|
||||
bmkUri: fxuri.spec,
|
||||
title: "Get Firefox!",
|
||||
tags: [],
|
||||
keyword: "awesome",
|
||||
loadInSidebar: false,
|
||||
parentName: "Bookmarks Toolbar",
|
||||
parentid: "toolbar"};
|
||||
let fxrecord = new Bookmark("bookmarks", "get-firefox1");
|
||||
fxrecord.bmkUri = fxuri.spec;
|
||||
fxrecord.description = "Firefox is awesome.";
|
||||
fxrecord.title = "Get Firefox!";
|
||||
fxrecord.tags = ["firefox", "awesome", "browser"];
|
||||
fxrecord.keyword = "awesome";
|
||||
fxrecord.loadInSidebar = false;
|
||||
fxrecord.parentName = "Bookmarks Toolbar";
|
||||
fxrecord.parentid = "toolbar";
|
||||
store.applyIncoming(fxrecord);
|
||||
|
||||
_("Verify it has been created correctly.");
|
||||
ids = Svc.Bookmark.getBookmarkIdsForURI(fxuri, {});
|
||||
do_check_eq(ids.length, 1);
|
||||
let id = ids[0];
|
||||
do_check_eq(Svc.Bookmark.getItemGUID(id), fxrecord.id);
|
||||
let id = store.idForGUID(fxrecord.id);
|
||||
do_check_eq(store.GUIDForId(id), fxrecord.id);
|
||||
do_check_eq(Svc.Bookmark.getItemType(id), Svc.Bookmark.TYPE_BOOKMARK);
|
||||
do_check_eq(Svc.Bookmark.getItemTitle(id), fxrecord.title);
|
||||
do_check_eq(Svc.Bookmark.getFolderIdForItem(id),
|
||||
|
@ -36,21 +38,127 @@ function run_test() {
|
|||
|
||||
_("Have the store create a new record object. Verify that it has the same data.");
|
||||
let newrecord = store.createRecord(fxrecord.id);
|
||||
for each (let property in ["type", "bmkUri", "title", "keyword",
|
||||
"parentName", "parentid"])
|
||||
do_check_eq(newrecord[property], fxrecord[property]);
|
||||
do_check_true(newrecord instanceof Bookmark);
|
||||
for each (let property in ["type", "bmkUri", "description", "title",
|
||||
"keyword", "parentName", "parentid"])
|
||||
do_check_eq(newrecord[property], fxrecord[property]);
|
||||
do_check_true(Utils.deepEquals(newrecord.tags.sort(),
|
||||
fxrecord.tags.sort()));
|
||||
|
||||
_("The calculated sort index is based on frecency data.");
|
||||
do_check_true(newrecord.sortindex >= 150);
|
||||
|
||||
_("Folders have high sort index to ensure they're synced first.");
|
||||
let folder_id = Svc.Bookmark.createFolder(Svc.Bookmark.toolbarFolder,
|
||||
"Test Folder", 0);
|
||||
let folder_guid = Svc.Bookmark.getItemGUID(folder_id);
|
||||
let folder_record = store.createRecord(folder_guid);
|
||||
do_check_eq(folder_record.sortindex, 1000000);
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
store.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
function test_folder_create() {
|
||||
try {
|
||||
_("Create a folder.");
|
||||
let folder = new BookmarkFolder("bookmarks", "testfolder-1");
|
||||
folder.parentName = "Bookmarks Toolbar";
|
||||
folder.parentid = "toolbar";
|
||||
folder.title = "Test Folder";
|
||||
store.applyIncoming(folder);
|
||||
|
||||
_("Verify it has been created correctly.");
|
||||
let id = store.idForGUID(folder.id);
|
||||
do_check_eq(Svc.Bookmark.getItemType(id), Svc.Bookmark.TYPE_FOLDER);
|
||||
do_check_eq(Svc.Bookmark.getItemTitle(id), folder.title);
|
||||
do_check_eq(Svc.Bookmark.getFolderIdForItem(id),
|
||||
Svc.Bookmark.toolbarFolder);
|
||||
|
||||
_("Have the store create a new record object. Verify that it has the same data.");
|
||||
let newrecord = store.createRecord(folder.id);
|
||||
do_check_true(newrecord instanceof BookmarkFolder);
|
||||
for each (let property in ["title","title", "parentName", "parentid"])
|
||||
do_check_eq(newrecord[property], folder[property]);
|
||||
|
||||
_("Folders have high sort index to ensure they're synced first.");
|
||||
do_check_eq(newrecord.sortindex, 1000000);
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
store.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
function test_move_folder() {
|
||||
try {
|
||||
_("Create two folders and a bookmark in one of them.");
|
||||
let folder1_id = Svc.Bookmark.createFolder(
|
||||
Svc.Bookmark.toolbarFolder, "Folder1", 0);
|
||||
let folder1_guid = store.GUIDForId(folder1_id);
|
||||
let folder2_id = Svc.Bookmark.createFolder(
|
||||
Svc.Bookmark.toolbarFolder, "Folder2", 0);
|
||||
let folder2_guid = store.GUIDForId(folder2_id);
|
||||
let bmk_id = Svc.Bookmark.insertBookmark(
|
||||
folder1_id, fxuri, Svc.Bookmark.DEFAULT_INDEX, "Get Firefox!");
|
||||
let bmk_guid = store.GUIDForId(bmk_id);
|
||||
|
||||
_("Get a record, reparent it and apply it to the store.");
|
||||
let record = store.createRecord(bmk_guid);
|
||||
do_check_eq(record.parentid, folder1_guid);
|
||||
record.parentid = folder2_guid;
|
||||
record.description = ""; //TODO for some reason we need this
|
||||
store.applyIncoming(record);
|
||||
|
||||
_("Verify the new parent.");
|
||||
let new_folder_id = Svc.Bookmark.getFolderIdForItem(bmk_id);
|
||||
do_check_eq(store.GUIDForId(new_folder_id), folder2_guid);
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
store.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
function test_move_order() {
|
||||
// Make sure the tracker is turned on.
|
||||
Svc.Obs.notify("weave:engine:start-tracking");
|
||||
try {
|
||||
_("Create two bookmarks");
|
||||
let bmk1_id = Svc.Bookmark.insertBookmark(
|
||||
Svc.Bookmark.toolbarFolder, fxuri, Svc.Bookmark.DEFAULT_INDEX,
|
||||
"Get Firefox!");
|
||||
let bmk1_guid = store.GUIDForId(bmk1_id);
|
||||
let bmk2_id = Svc.Bookmark.insertBookmark(
|
||||
Svc.Bookmark.toolbarFolder, tburi, Svc.Bookmark.DEFAULT_INDEX,
|
||||
"Get Thunderbird!");
|
||||
let bmk2_guid = store.GUIDForId(bmk2_id);
|
||||
|
||||
_("Verify order.");
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(bmk1_id), 0);
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(bmk2_id), 1);
|
||||
let toolbar = store.createRecord("toolbar");
|
||||
dump(JSON.stringify(toolbar.cleartext));
|
||||
do_check_eq(toolbar.children.length, 2);
|
||||
do_check_eq(toolbar.children[0], bmk1_guid);
|
||||
do_check_eq(toolbar.children[1], bmk2_guid);
|
||||
|
||||
_("Move bookmarks around.");
|
||||
store._childrenToOrder = {};
|
||||
toolbar.children = [bmk2_guid, bmk1_guid];
|
||||
store.applyIncoming(toolbar);
|
||||
// Bookmarks engine does this at the end of _processIncoming
|
||||
engine._tracker.ignoreAll = true;
|
||||
store._orderChildren();
|
||||
engine._tracker.ignoreAll = false;
|
||||
delete store._childrenToOrder;
|
||||
|
||||
_("Verify new order.");
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(bmk2_id), 0);
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(bmk1_id), 1);
|
||||
|
||||
} finally {
|
||||
Svc.Obs.notify("weave:engine:stop-tracking");
|
||||
_("Clean up.");
|
||||
store.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_bookmark_create();
|
||||
test_folder_create();
|
||||
test_move_folder();
|
||||
test_move_order();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let engine = new BookmarksEngine();
|
||||
Engines.register(BookmarksEngine);
|
||||
let engine = Engines.get("bookmarks");
|
||||
engine._store.wipe();
|
||||
|
||||
_("Verify we've got an empty tracker to work with.");
|
||||
|
@ -27,12 +29,14 @@ function run_test() {
|
|||
_("Tell the tracker to start tracking changes.");
|
||||
Svc.Obs.notify("weave:engine:start-tracking");
|
||||
createBmk();
|
||||
do_check_eq([id for (id in tracker.changedIDs)].length, 1);
|
||||
// We expect two changed items because the containing folder
|
||||
// changed as well (new child).
|
||||
do_check_eq([id for (id in tracker.changedIDs)].length, 2);
|
||||
|
||||
_("Notifying twice won't do any harm.");
|
||||
Svc.Obs.notify("weave:engine:start-tracking");
|
||||
createBmk();
|
||||
do_check_eq([id for (id in tracker.changedIDs)].length, 2);
|
||||
do_check_eq([id for (id in tracker.changedIDs)].length, 3);
|
||||
|
||||
_("Let's stop tracking again.");
|
||||
tracker.clearChangedIDs();
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/engines/history.js");
|
||||
Cu.import("resource://services-sync/type_records/history.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function makeSteamEngine() {
|
||||
return new SteamEngine();
|
||||
}
|
||||
|
||||
var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
|
||||
function test_processIncoming_mobile_history_batched() {
|
||||
_("SyncEngine._processIncoming works on history engine.");
|
||||
|
||||
let FAKE_DOWNLOAD_LIMIT = 100;
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
Svc.Prefs.set("client.type", "mobile");
|
||||
Svc.History.removeAllPages();
|
||||
Engines.register(HistoryEngine);
|
||||
|
||||
// A collection that logs each GET
|
||||
let collection = new ServerCollection();
|
||||
collection.get_log = [];
|
||||
collection._get = collection.get;
|
||||
collection.get = function (options) {
|
||||
this.get_log.push(options);
|
||||
return this._get(options);
|
||||
};
|
||||
|
||||
// Let's create some 234 server side history records. They're all at least
|
||||
// 10 minutes old.
|
||||
let visitType = Ci.nsINavHistoryService.TRANSITION_LINK;
|
||||
for (var i = 0; i < 234; i++) {
|
||||
let id = 'record-no-' + i;
|
||||
let modified = Date.now()/1000 - 60*(i+10);
|
||||
let payload = encryptPayload({
|
||||
id: id,
|
||||
histUri: "http://foo/bar?" + id,
|
||||
title: id,
|
||||
sortindex: i,
|
||||
visits: [{date: (modified - 5), type: visitType}],
|
||||
deleted: false});
|
||||
|
||||
let wbo = new ServerWBO(id, payload);
|
||||
wbo.modified = modified;
|
||||
collection.wbos[id] = wbo;
|
||||
}
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/history": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
||||
let engine = new HistoryEngine("history");
|
||||
let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
|
||||
meta_global.payload.engines = {history: {version: engine.version,
|
||||
syncID: engine.syncID}};
|
||||
|
||||
try {
|
||||
|
||||
_("On a mobile client, we get new records from the server in batches of 50.");
|
||||
engine._syncStartup();
|
||||
|
||||
// Fake a lower limit.
|
||||
engine.downloadLimit = FAKE_DOWNLOAD_LIMIT;
|
||||
_("Last modified: " + engine.lastModified);
|
||||
_("Processing...");
|
||||
engine._processIncoming();
|
||||
|
||||
_("Last modified: " + engine.lastModified);
|
||||
engine._syncFinish();
|
||||
|
||||
// Back to the normal limit.
|
||||
_("Running again. Should fetch none, because of lastModified");
|
||||
engine.downloadLimit = MAX_HISTORY_DOWNLOAD;
|
||||
_("Processing...");
|
||||
engine._processIncoming();
|
||||
|
||||
_("Last modified: " + engine.lastModified);
|
||||
_("Running again. Expecting to pull everything");
|
||||
|
||||
engine.lastModified = undefined;
|
||||
engine.lastSync = 0;
|
||||
_("Processing...");
|
||||
engine._processIncoming();
|
||||
|
||||
_("Last modified: " + engine.lastModified);
|
||||
|
||||
// Verify that the right number of GET requests with the right
|
||||
// kind of parameters were made.
|
||||
do_check_eq(collection.get_log.length,
|
||||
// First try:
|
||||
1 + // First 50...
|
||||
1 + // 1 GUID fetch...
|
||||
// 1 fetch...
|
||||
Math.ceil((FAKE_DOWNLOAD_LIMIT - 50) / MOBILE_BATCH_SIZE) +
|
||||
// Second try: none
|
||||
// Third try:
|
||||
1 + // First 50...
|
||||
1 + // 1 GUID fetch...
|
||||
// 4 fetch...
|
||||
Math.ceil((234 - 50) / MOBILE_BATCH_SIZE));
|
||||
|
||||
// Check the structure of each HTTP request.
|
||||
do_check_eq(collection.get_log[0].full, 1);
|
||||
do_check_eq(collection.get_log[0].limit, MOBILE_BATCH_SIZE);
|
||||
do_check_eq(collection.get_log[1].full, undefined);
|
||||
do_check_eq(collection.get_log[1].sort, "index");
|
||||
do_check_eq(collection.get_log[1].limit, FAKE_DOWNLOAD_LIMIT);
|
||||
do_check_eq(collection.get_log[2].full, 1);
|
||||
do_check_eq(collection.get_log[3].full, 1);
|
||||
do_check_eq(collection.get_log[3].limit, MOBILE_BATCH_SIZE);
|
||||
do_check_eq(collection.get_log[4].full, undefined);
|
||||
do_check_eq(collection.get_log[4].sort, "index");
|
||||
do_check_eq(collection.get_log[4].limit, MAX_HISTORY_DOWNLOAD);
|
||||
for (let i = 0; i <= Math.floor((234 - 50) / MOBILE_BATCH_SIZE); i++) {
|
||||
let j = i + 5;
|
||||
do_check_eq(collection.get_log[j].full, 1);
|
||||
do_check_eq(collection.get_log[j].limit, undefined);
|
||||
if (i < Math.floor((234 - 50) / MOBILE_BATCH_SIZE))
|
||||
do_check_eq(collection.get_log[j].ids.length, MOBILE_BATCH_SIZE);
|
||||
else
|
||||
do_check_eq(collection.get_log[j].ids.length, 234 % MOBILE_BATCH_SIZE);
|
||||
}
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
test_processIncoming_mobile_history_batched();
|
||||
}
|
|
@ -18,7 +18,7 @@ function run_test() {
|
|||
}
|
||||
|
||||
try {
|
||||
_("Create bookmark. Won't show because we haven't started tracking yet");
|
||||
_("Create history item. Won't show because we haven't started tracking yet");
|
||||
addVisit();
|
||||
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
|
||||
|
||||
|
|
|
@ -4,6 +4,30 @@ Cu.import("resource://services-sync/base_records/crypto.js");
|
|||
Cu.import("resource://services-sync/constants.js");
|
||||
btoa = Cu.import("resource://services-sync/util.js").btoa;
|
||||
|
||||
function test_time_keyFromString(iterations) {
|
||||
let k;
|
||||
let o;
|
||||
let b = new BulkKeyBundle();
|
||||
let d = Utils.decodeKeyBase32("ababcdefabcdefabcdefabcdef");
|
||||
b.generateRandom();
|
||||
|
||||
_("Running " + iterations + " iterations of hmacKeyObject + sha256HMACBytes.");
|
||||
for (let i = 0; i < iterations; ++i) {
|
||||
let k = b.hmacKeyObject;
|
||||
o = Utils.sha256HMACBytes(d, k);
|
||||
}
|
||||
do_check_true(!!o);
|
||||
_("Done.");
|
||||
}
|
||||
|
||||
function test_repeated_hmac() {
|
||||
let testKey = "ababcdefabcdefabcdefabcdef";
|
||||
let k = Utils.makeHMACKey("foo");
|
||||
let one = Utils.sha256HMACBytes(Utils.decodeKeyBase32(testKey), k);
|
||||
let two = Utils.sha256HMACBytes(Utils.decodeKeyBase32(testKey), k);
|
||||
do_check_eq(one, two);
|
||||
}
|
||||
|
||||
function test_keymanager() {
|
||||
let testKey = "ababcdefabcdefabcdefabcdef";
|
||||
|
||||
|
@ -156,4 +180,8 @@ function run_test() {
|
|||
test_keymanager();
|
||||
test_collections_manager();
|
||||
test_key_persistence();
|
||||
test_repeated_hmac();
|
||||
|
||||
// Only do 1,000 to avoid a 5-second pause in test runs.
|
||||
test_time_keyFromString(1000);
|
||||
}
|
||||
|
|
|
@ -102,30 +102,6 @@ function makeSteamEngine() {
|
|||
|
||||
var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
|
||||
|
||||
/*
|
||||
* Test setup helpers
|
||||
*/
|
||||
|
||||
function sync_httpd_setup(handlers) {
|
||||
handlers["/1.0/foo/storage/meta/global"]
|
||||
= (new ServerWBO('global', {})).handler();
|
||||
return httpd_setup(handlers);
|
||||
}
|
||||
|
||||
// Turn WBO cleartext into "encrypted" payload as it goes over the wire
|
||||
function encryptPayload(cleartext) {
|
||||
if (typeof cleartext == "object") {
|
||||
cleartext = JSON.stringify(cleartext);
|
||||
}
|
||||
|
||||
return {encryption: "../crypto/steam",
|
||||
ciphertext: cleartext, // ciphertext == cleartext with fake crypto
|
||||
IV: "irrelevant",
|
||||
hmac: Utils.sha256HMAC(cleartext, null)};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Tests
|
||||
*
|
||||
|
@ -522,7 +498,7 @@ function test_processIncoming_mobile_batchSize() {
|
|||
|
||||
try {
|
||||
|
||||
// On a mobile client, we get new records from the server in batches of 50.
|
||||
_("On a mobile client, we get new records from the server in batches of 50.");
|
||||
engine._syncStartup();
|
||||
engine._processIncoming();
|
||||
do_check_eq([id for (id in engine._store.items)].length, 234);
|
||||
|
@ -556,7 +532,6 @@ function test_processIncoming_mobile_batchSize() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function test_uploadOutgoing_toEmptyServer() {
|
||||
_("SyncEngine._uploadOutgoing uploads new records to server");
|
||||
|
||||
|
@ -1017,6 +992,8 @@ function run_test() {
|
|||
if (DISABLE_TESTS_BUG_604565)
|
||||
return;
|
||||
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
test_syncStartup_emptyOrOutdatedGlobalsResetsSync();
|
||||
test_syncStartup_serverHasNewerVersion();
|
||||
test_syncStartup_syncIDMismatchResetsClient();
|
||||
|
|
Загрузка…
Ссылка в новой задаче