mirror of https://github.com/roytam1/UXP
parent
21193dac4c
commit
3685261ed4
30 changed files with 1 additions and 2757 deletions
@ -1,89 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["CloudSync"]; |
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Adapters", |
||||
"resource://gre/modules/CloudSyncAdapters.jsm"); |
||||
XPCOMUtils.defineLazyModuleGetter(this, "Local", |
||||
"resource://gre/modules/CloudSyncLocal.jsm"); |
||||
XPCOMUtils.defineLazyModuleGetter(this, "Bookmarks", |
||||
"resource://gre/modules/CloudSyncBookmarks.jsm"); |
||||
XPCOMUtils.defineLazyModuleGetter(this, "Tabs", |
||||
"resource://gre/modules/CloudSyncTabs.jsm"); |
||||
|
||||
var API_VERSION = 1; |
||||
|
||||
var _CloudSync = function () { |
||||
}; |
||||
|
||||
_CloudSync.prototype = { |
||||
_adapters: null, |
||||
|
||||
get adapters () { |
||||
if (!this._adapters) { |
||||
this._adapters = new Adapters(); |
||||
} |
||||
return this._adapters; |
||||
}, |
||||
|
||||
_bookmarks: null, |
||||
|
||||
get bookmarks () { |
||||
if (!this._bookmarks) { |
||||
this._bookmarks = new Bookmarks(); |
||||
} |
||||
return this._bookmarks; |
||||
}, |
||||
|
||||
_local: null, |
||||
|
||||
get local () { |
||||
if (!this._local) { |
||||
this._local = new Local(); |
||||
} |
||||
return this._local; |
||||
}, |
||||
|
||||
_tabs: null, |
||||
|
||||
get tabs () { |
||||
if (!this._tabs) { |
||||
this._tabs = new Tabs(); |
||||
} |
||||
return this._tabs; |
||||
}, |
||||
|
||||
get tabsReady () { |
||||
return this._tabs ? true: false; |
||||
}, |
||||
|
||||
get version () { |
||||
return API_VERSION; |
||||
}, |
||||
}; |
||||
|
||||
this.CloudSync = function CloudSync () { |
||||
return _cloudSyncInternal.instance; |
||||
}; |
||||
|
||||
Object.defineProperty(CloudSync, "ready", { |
||||
get: function () { |
||||
return _cloudSyncInternal.ready; |
||||
} |
||||
}); |
||||
|
||||
var _cloudSyncInternal = { |
||||
instance: null, |
||||
ready: false, |
||||
}; |
||||
|
||||
XPCOMUtils.defineLazyGetter(_cloudSyncInternal, "instance", function () { |
||||
_cloudSyncInternal.ready = true; |
||||
return new _CloudSync(); |
||||
}.bind(this)); |
@ -1,88 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["Adapters"]; |
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm"); |
||||
Components.utils.import("resource://gre/modules/CloudSyncEventSource.jsm"); |
||||
|
||||
this.Adapters = function () { |
||||
let eventTypes = [ |
||||
"sync", |
||||
]; |
||||
|
||||
let suspended = true; |
||||
|
||||
let suspend = function () { |
||||
if (!suspended) { |
||||
Services.obs.removeObserver(observer, "cloudsync:user-sync", false); |
||||
suspended = true; |
||||
} |
||||
}.bind(this); |
||||
|
||||
let resume = function () { |
||||
if (suspended) { |
||||
Services.obs.addObserver(observer, "cloudsync:user-sync", false); |
||||
suspended = false; |
||||
} |
||||
}.bind(this); |
||||
|
||||
let eventSource = new EventSource(eventTypes, suspend, resume); |
||||
let registeredAdapters = new Map(); |
||||
|
||||
function register (name, opts) { |
||||
opts = opts || {}; |
||||
registeredAdapters.set(name, opts); |
||||
} |
||||
|
||||
function unregister (name) { |
||||
if (!registeredAdapters.has(name)) { |
||||
throw new Error("adapter is not registered: " + name) |
||||
} |
||||
registeredAdapters.delete(name); |
||||
} |
||||
|
||||
function getAdapterNames () { |
||||
let result = []; |
||||
for (let name of registeredAdapters.keys()) { |
||||
result.push(name); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
function getAdapter (name) { |
||||
if (!registeredAdapters.has(name)) { |
||||
throw new Error("adapter is not registered: " + name) |
||||
} |
||||
return registeredAdapters.get(name); |
||||
} |
||||
|
||||
function countAdapters () { |
||||
return registeredAdapters.size; |
||||
} |
||||
|
||||
let observer = { |
||||
observe: function (subject, topic, data) { |
||||
switch (topic) { |
||||
case "cloudsync:user-sync": |
||||
eventSource.emit("sync"); |
||||
break; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
this.addEventListener = eventSource.addEventListener; |
||||
this.removeEventListener = eventSource.removeEventListener; |
||||
this.register = register.bind(this); |
||||
this.get = getAdapter.bind(this); |
||||
this.unregister = unregister.bind(this); |
||||
this.__defineGetter__("names", getAdapterNames); |
||||
this.__defineGetter__("count", countAdapters); |
||||
}; |
||||
|
||||
Adapters.prototype = { |
||||
|
||||
}; |
@ -1,795 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["Bookmarks"]; |
||||
|
||||
const Cu = Components.utils; |
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
||||
Cu.import("resource://services-common/utils.js"); |
||||
Cu.import("resource://services-crypto/utils.js"); |
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm"); |
||||
Cu.import("resource:///modules/PlacesUIUtils.jsm"); |
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
||||
"resource://gre/modules/NetUtil.jsm"); |
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm"); |
||||
Cu.import("resource://gre/modules/Task.jsm"); |
||||
Cu.import("resource://gre/modules/CloudSyncPlacesWrapper.jsm"); |
||||
Cu.import("resource://gre/modules/CloudSyncEventSource.jsm"); |
||||
Cu.import("resource://gre/modules/CloudSyncBookmarksFolderCache.jsm"); |
||||
|
||||
const ITEM_TYPES = [ |
||||
"NULL", |
||||
"BOOKMARK", |
||||
"FOLDER", |
||||
"SEPARATOR", |
||||
"DYNAMIC_CONTAINER", // no longer used by Places, but this ID should not be used for future item types
|
||||
]; |
||||
|
||||
const CS_UNKNOWN = 0x1; |
||||
const CS_FOLDER = 0x1 << 1; |
||||
const CS_SEPARATOR = 0x1 << 2; |
||||
const CS_QUERY = 0x1 << 3; |
||||
const CS_LIVEMARK = 0x1 << 4; |
||||
const CS_BOOKMARK = 0x1 << 5; |
||||
|
||||
const EXCLUDE_BACKUP_ANNO = "places/excludeFromBackup"; |
||||
|
||||
const DATA_VERSION = 1; |
||||
|
||||
function asyncCallback(ctx, func, args) { |
||||
function invoke() { |
||||
func.apply(ctx, args); |
||||
} |
||||
CommonUtils.nextTick(invoke); |
||||
} |
||||
|
||||
var Record = function (params) { |
||||
this.id = params.guid; |
||||
this.parent = params.parent || null; |
||||
this.index = params.position; |
||||
this.title = params.title; |
||||
this.dateAdded = Math.floor(params.dateAdded/1000); |
||||
this.lastModified = Math.floor(params.lastModified/1000); |
||||
this.uri = params.url; |
||||
|
||||
let annos = params.annos || {}; |
||||
Object.defineProperty(this, "annos", { |
||||
get: function () { |
||||
return annos; |
||||
}, |
||||
enumerable: false |
||||
}); |
||||
|
||||
switch (params.type) { |
||||
case PlacesUtils.bookmarks.TYPE_FOLDER: |
||||
if (PlacesUtils.LMANNO_FEEDURI in annos) { |
||||
this.type = CS_LIVEMARK; |
||||
this.feed = annos[PlacesUtils.LMANNO_FEEDURI]; |
||||
this.site = annos[PlacesUtils.LMANNO_SITEURI]; |
||||
} else { |
||||
this.type = CS_FOLDER; |
||||
} |
||||
break; |
||||
case PlacesUtils.bookmarks.TYPE_BOOKMARK: |
||||
if (this.uri.startsWith("place:")) { |
||||
this.type = CS_QUERY; |
||||
} else { |
||||
this.type = CS_BOOKMARK; |
||||
} |
||||
break; |
||||
case PlacesUtils.bookmarks.TYPE_SEPARATOR: |
||||
this.type = CS_SEPARATOR; |
||||
break; |
||||
default: |
||||
this.type = CS_UNKNOWN; |
||||
} |
||||
}; |
||||
|
||||
Record.prototype = { |
||||
version: DATA_VERSION, |
||||
}; |
||||
|
||||
var Bookmarks = function () { |
||||
let createRootFolder = function (name) { |
||||
let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name; |
||||
let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name; |
||||
|
||||
let deferred = Promise.defer(); |
||||
let placesRootId = PlacesUtils.placesRootId; |
||||
let rootFolderId; |
||||
let rootShortcutId; |
||||
|
||||
function createAdapterShortcut(result) { |
||||
rootFolderId = result; |
||||
let uri = "place:folder=" + rootFolderId; |
||||
return PlacesWrapper.insertBookmark(PlacesUIUtils.allBookmarksFolderId, uri, |
||||
PlacesUtils.bookmarks.DEFAULT_INDEX, name); |
||||
} |
||||
|
||||
function setRootFolderCloudSyncAnnotation(result) { |
||||
rootShortcutId = result; |
||||
return PlacesWrapper.setItemAnnotation(rootFolderId, ROOT_FOLDER_ANNO, |
||||
1, 0, PlacesUtils.annotations.EXPIRE_NEVER); |
||||
} |
||||
|
||||
function setRootShortcutCloudSyncAnnotation() { |
||||
return PlacesWrapper.setItemAnnotation(rootShortcutId, ROOT_SHORTCUT_ANNO, |
||||
1, 0, PlacesUtils.annotations.EXPIRE_NEVER); |
||||
} |
||||
|
||||
function setRootFolderExcludeFromBackupAnnotation() { |
||||
return PlacesWrapper.setItemAnnotation(rootFolderId, EXCLUDE_BACKUP_ANNO, |
||||
1, 0, PlacesUtils.annotations.EXPIRE_NEVER); |
||||
} |
||||
|
||||
function finish() { |
||||
deferred.resolve(rootFolderId); |
||||
} |
||||
|
||||
Promise.resolve(PlacesUtils.bookmarks.createFolder(placesRootId, name, PlacesUtils.bookmarks.DEFAULT_INDEX)) |
||||
.then(createAdapterShortcut) |
||||
.then(setRootFolderCloudSyncAnnotation) |
||||
.then(setRootShortcutCloudSyncAnnotation) |
||||
.then(setRootFolderExcludeFromBackupAnnotation) |
||||
.then(finish, deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let getRootFolder = function (name) { |
||||
let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name; |
||||
let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name; |
||||
let deferred = Promise.defer(); |
||||
|
||||
function checkRootFolder(folderIds) { |
||||
if (!folderIds.length) { |
||||
return createRootFolder(name); |
||||
} |
||||
return Promise.resolve(folderIds[0]); |
||||
} |
||||
|
||||
function createFolderObject(folderId) { |
||||
return new RootFolder(folderId, name); |
||||
} |
||||
|
||||
PlacesWrapper.getLocalIdsWithAnnotation(ROOT_FOLDER_ANNO) |
||||
.then(checkRootFolder, deferred.reject) |
||||
.then(createFolderObject) |
||||
.then(deferred.resolve, deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let deleteRootFolder = function (name) { |
||||
let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name; |
||||
let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name; |
||||
|
||||
let deferred = Promise.defer(); |
||||
let placesRootId = PlacesUtils.placesRootId; |
||||
|
||||
function getRootShortcutId() { |
||||
return PlacesWrapper.getLocalIdsWithAnnotation(ROOT_SHORTCUT_ANNO); |
||||
} |
||||
|
||||
function deleteShortcut(shortcutIds) { |
||||
if (!shortcutIds.length) { |
||||
return Promise.resolve(); |
||||
} |
||||
return PlacesWrapper.removeItem(shortcutIds[0]); |
||||
} |
||||
|
||||
function getRootFolderId() { |
||||
return PlacesWrapper.getLocalIdsWithAnnotation(ROOT_FOLDER_ANNO); |
||||
} |
||||
|
||||
function deleteFolder(folderIds) { |
||||
let deleteFolderDeferred = Promise.defer(); |
||||
|
||||
if (!folderIds.length) { |
||||
return Promise.resolve(); |
||||
} |
||||
|
||||
let rootFolderId = folderIds[0]; |
||||
PlacesWrapper.removeFolderChildren(rootFolderId).then( |
||||
function () { |
||||
return PlacesWrapper.removeItem(rootFolderId); |
||||
} |
||||
).then(deleteFolderDeferred.resolve, deleteFolderDeferred.reject); |
||||
|
||||
return deleteFolderDeferred.promise; |
||||
} |
||||
|
||||
getRootShortcutId().then(deleteShortcut) |
||||
.then(getRootFolderId) |
||||
.then(deleteFolder) |
||||
.then(deferred.resolve, deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
/* PUBLIC API */ |
||||
this.getRootFolder = getRootFolder.bind(this); |
||||
this.deleteRootFolder = deleteRootFolder.bind(this); |
||||
|
||||
}; |
||||
|
||||
this.Bookmarks = Bookmarks; |
||||
|
||||
var RootFolder = function (rootId, rootName) { |
||||
let suspended = true; |
||||
let ignoreAll = false; |
||||
|
||||
let suspend = function () { |
||||
if (!suspended) { |
||||
PlacesUtils.bookmarks.removeObserver(observer); |
||||
suspended = true; |
||||
} |
||||
}.bind(this); |
||||
|
||||
let resume = function () { |
||||
if (suspended) { |
||||
PlacesUtils.bookmarks.addObserver(observer, false); |
||||
suspended = false; |
||||
} |
||||
}.bind(this); |
||||
|
||||
let eventTypes = [ |
||||
"add", |
||||
"remove", |
||||
"change", |
||||
"move", |
||||
]; |
||||
|
||||
let eventSource = new EventSource(eventTypes, suspend, resume); |
||||
|
||||
let folderCache = new FolderCache; |
||||
folderCache.insert(rootId, null); |
||||
|
||||
let getCachedFolderIds = function (cache, roots) { |
||||
let nodes = [...roots]; |
||||
let results = []; |
||||
|
||||
while (nodes.length) { |
||||
let node = nodes.shift(); |
||||
results.push(node); |
||||
let children = cache.getChildren(node); |
||||
nodes = nodes.concat([...children]); |
||||
} |
||||
return results; |
||||
}; |
||||
|
||||
let getLocalItems = function () { |
||||
let deferred = Promise.defer(); |
||||
|
||||
let folders = getCachedFolderIds(folderCache, folderCache.getChildren(rootId)); |
||||
|
||||
function getFolders(ids) { |
||||
let types = [ |
||||
PlacesUtils.bookmarks.TYPE_FOLDER, |
||||
]; |
||||
return PlacesWrapper.getItemsById(ids, types); |
||||
} |
||||
|
||||
function getContents(parents) { |
||||
parents.push(rootId); |
||||
let types = [ |
||||
PlacesUtils.bookmarks.TYPE_BOOKMARK, |
||||
PlacesUtils.bookmarks.TYPE_SEPARATOR, |
||||
]; |
||||
return PlacesWrapper.getItemsByParentId(parents, types) |
||||
} |
||||
|
||||
function getParentGuids(results) { |
||||
results = Array.prototype.concat.apply([], results); |
||||
let promises = []; |
||||
results.map(function (result) { |
||||
let promise = PlacesWrapper.localIdToGuid(result.parent).then( |
||||
function (guidResult) { |
||||
result.parent = guidResult; |
||||
return Promise.resolve(result); |
||||
}, |
||||
Promise.reject.bind(Promise) |
||||
); |
||||
promises.push(promise); |
||||
}); |
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
function getAnnos(results) { |
||||
results = Array.prototype.concat.apply([], results); |
||||
let promises = []; |
||||
results.map(function (result) { |
||||
let promise = PlacesWrapper.getItemAnnotationsForLocalId(result.id).then( |
||||
function (annos) { |
||||
result.annos = annos; |
||||
return Promise.resolve(result); |
||||
}, |
||||
Promise.reject.bind(Promise) |
||||
); |
||||
promises.push(promise); |
||||
}); |
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
let promises = [ |
||||
getFolders(folders), |
||||
getContents(folders), |
||||
]; |
||||
|
||||
Promise.all(promises) |
||||
.then(getParentGuids) |
||||
.then(getAnnos) |
||||
.then(function (results) { |
||||
results = results.map((result) => new Record(result)); |
||||
deferred.resolve(results); |
||||
}, |
||||
deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let getLocalItemsById = function (guids) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
let types = [ |
||||
PlacesUtils.bookmarks.TYPE_BOOKMARK, |
||||
PlacesUtils.bookmarks.TYPE_FOLDER, |
||||
PlacesUtils.bookmarks.TYPE_SEPARATOR, |
||||
PlacesUtils.bookmarks.TYPE_DYNAMIC_CONTAINER, |
||||
]; |
||||
|
||||
function getParentGuids(results) { |
||||
let promises = []; |
||||
results.map(function (result) { |
||||
let promise = PlacesWrapper.localIdToGuid(result.parent).then( |
||||
function (guidResult) { |
||||
result.parent = guidResult; |
||||
return Promise.resolve(result); |
||||
}, |
||||
Promise.reject.bind(Promise) |
||||
); |
||||
promises.push(promise); |
||||
}); |
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
PlacesWrapper.getItemsByGuid(guids, types) |
||||
.then(getParentGuids) |
||||
.then(function (results) { |
||||
results = results.map((result) => new Record(result)); |
||||
deferred.resolve(results); |
||||
}, |
||||
deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let _createItem = function (item) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
function getFolderId() { |
||||
if (item.parent) { |
||||
return PlacesWrapper.guidToLocalId(item.parent); |
||||
} |
||||
return Promise.resolve(rootId); |
||||
} |
||||
|
||||
function create(folderId) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
if (!folderId) { |
||||
folderId = rootId; |
||||
} |
||||
let index = item.hasOwnProperty("index") ? item.index : PlacesUtils.bookmarks.DEFAULT_INDEX; |
||||
|
||||
function complete(localId) { |
||||
folderCache.insert(localId, folderId); |
||||
deferred.resolve(localId); |
||||
} |
||||
|
||||
switch (item.type) { |
||||
case CS_BOOKMARK: |
||||
case CS_QUERY: |
||||
PlacesWrapper.insertBookmark(folderId, item.uri, index, item.title, item.id) |
||||
.then(complete, deferred.reject); |
||||
break; |
||||
case CS_FOLDER: |
||||
PlacesWrapper.createFolder(folderId, item.title, index, item.id) |
||||
.then(complete, deferred.reject); |
||||
break; |
||||
case CS_SEPARATOR: |
||||
PlacesWrapper.insertSeparator(folderId, index, item.id) |
||||
.then(complete, deferred.reject); |
||||
break; |
||||
case CS_LIVEMARK: |
||||
let livemark = { |
||||
title: item.title, |
||||
parentId: folderId, |
||||
index: item.index, |
||||
feedURI: item.feed, |
||||
siteURI: item.site, |
||||
guid: item.id, |
||||
}; |
||||
PlacesUtils.livemarks.addLivemark(livemark) |
||||
.then(complete, deferred.reject); |
||||
break; |
||||
default: |
||||
deferred.reject("invalid item type: " + item.type); |
||||
} |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
getFolderId().then(create) |
||||
.then(deferred.resolve, deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let _deleteItem = function (item) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
PlacesWrapper.guidToLocalId(item.id).then( |
||||
function (localId) { |
||||
folderCache.remove(localId); |
||||
return PlacesWrapper.removeItem(localId); |
||||
} |
||||
).then(deferred.resolve, deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let _updateItem = function (item) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
PlacesWrapper.guidToLocalId(item.id).then( |
||||
function (localId) { |
||||
let promises = []; |
||||
|
||||
if (item.hasOwnProperty("dateAdded")) { |
||||
promises.push(PlacesWrapper.setItemDateAdded(localId, item.dateAdded)); |
||||
} |
||||
|
||||
if (item.hasOwnProperty("lastModified")) { |
||||
promises.push(PlacesWrapper.setItemLastModified(localId, item.lastModified)); |
||||
} |
||||
|
||||
if ((CS_BOOKMARK | CS_FOLDER) & item.type && item.hasOwnProperty("title")) { |
||||
promises.push(PlacesWrapper.setItemTitle(localId, item.title)); |
||||
} |
||||
|
||||
if (CS_BOOKMARK & item.type && item.hasOwnProperty("uri")) { |
||||
promises.push(PlacesWrapper.changeBookmarkURI(localId, item.uri)); |
||||
} |
||||
|
||||
if (item.hasOwnProperty("parent")) { |
||||
let deferred = Promise.defer(); |
||||
PlacesWrapper.guidToLocalId(item.parent) |
||||
.then( |
||||
function (parent) { |
||||
let index = item.hasOwnProperty("index") ? item.index : PlacesUtils.bookmarks.DEFAULT_INDEX; |
||||
if (CS_FOLDER & item.type) { |
||||
folderCache.setParent(localId, parent); |
||||
} |
||||
return PlacesWrapper.moveItem(localId, parent, index); |
||||
} |
||||
) |
||||
.then(deferred.resolve, deferred.reject); |
||||
promises.push(deferred.promise); |
||||
} |
||||
|
||||
if (item.hasOwnProperty("index") && !item.hasOwnProperty("parent")) { |
||||
promises.push(Task.spawn(function* () { |
||||
let localItem = (yield getLocalItemsById([item.id]))[0]; |
||||
let parent = yield PlacesWrapper.guidToLocalId(localItem.parent); |
||||
let index = item.index; |
||||
if (CS_FOLDER & item.type) { |
||||
folderCache.setParent(localId, parent); |
||||
} |
||||
yield PlacesWrapper.moveItem(localId, parent, index); |
||||
})); |
||||
} |
||||
|
||||
Promise.all(promises) |
||||
.then(deferred.resolve, deferred.reject); |
||||
} |
||||
); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let mergeRemoteItems = function (items) { |
||||
ignoreAll = true; |
||||
let deferred = Promise.defer(); |
||||
|
||||
let newFolders = {}; |
||||
let newItems = []; |
||||
let updatedItems = []; |
||||
let deletedItems = []; |
||||
|
||||
let sortItems = function () { |
||||
let promises = []; |
||||
|
||||
let exists = function (item) { |
||||
let existsDeferred = Promise.defer(); |
||||
if (!item.id) { |
||||
Object.defineProperty(item, "__exists__", { |
||||
value: false, |
||||
enumerable: false |
||||
}); |
||||
existsDeferred.resolve(item); |
||||
} else { |
||||
PlacesWrapper.guidToLocalId(item.id).then( |
||||
function (localId) { |
||||
Object.defineProperty(item, "__exists__", { |
||||
value: localId ? true : false, |
||||
enumerable: false |
||||
}); |
||||
existsDeferred.resolve(item); |
||||
}, |
||||
existsDeferred.reject |
||||
); |
||||
} |
||||
return existsDeferred.promise; |
||||
} |
||||
|
||||
let handleSortedItem = function (item) { |
||||
if (!item.__exists__ && !item.deleted) { |
||||
if (CS_FOLDER == item.type) { |
||||
newFolders[item.id] = item; |
||||
item._children = []; |
||||
} else { |
||||
newItems.push(item); |
||||
} |
||||
} else if (item.__exists__ && item.deleted) { |
||||
deletedItems.push(item); |
||||
} else if (item.__exists__) { |
||||
updatedItems.push(item); |
||||
} |
||||
} |
||||
|
||||
for (let item of items) { |
||||
if (!item || 'object' !== typeof(item)) { |
||||
continue; |
||||
} |
||||
|
||||
let promise = exists(item).then(handleSortedItem, Promise.reject.bind(Promise)); |
||||
promises.push(promise); |
||||
} |
||||
|
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
let processNewFolders = function () { |
||||
let newFolderGuids = Object.keys(newFolders); |
||||
let newFolderRoots = []; |
||||
|
||||
for (let guid of newFolderGuids) { |
||||
let item = newFolders[guid]; |
||||
if (item.parent && newFolderGuids.indexOf(item.parent) >= 0) { |
||||
let parent = newFolders[item.parent]; |
||||
parent._children.push(item.id); |
||||
} else { |
||||
newFolderRoots.push(guid); |
||||
} |
||||
}; |
||||
|
||||
let promises = []; |
||||
for (let guid of newFolderRoots) { |
||||
let root = newFolders[guid]; |
||||
let promise = Promise.resolve(); |
||||
promise = promise.then( |
||||
function () { |
||||
return _createItem(root); |
||||
}, |
||||
Promise.reject.bind(Promise) |
||||
); |
||||
let items = [].concat(root._children); |
||||
|
||||
while (items.length) { |
||||
let item = newFolders[items.shift()]; |
||||
items = items.concat(item._children); |
||||
promise = promise.then( |
||||
function () { |
||||
return _createItem(item); |
||||
}, |
||||
Promise.reject.bind(Promise) |
||||
); |
||||
} |
||||
promises.push(promise); |
||||
} |
||||
|
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
let processItems = function () { |
||||
let promises = []; |
||||
|
||||
for (let item of newItems) { |
||||
promises.push(_createItem(item)); |
||||
} |
||||
|
||||
for (let item of updatedItems) { |
||||
promises.push(_updateItem(item)); |
||||
} |
||||
|
||||
for (let item of deletedItems) { |
||||
_deleteItem(item); |
||||
} |
||||
|
||||
return Promise.all(promises); |
||||
} |
||||
|
||||
sortItems().then(processNewFolders) |
||||
.then(processItems) |
||||
.then(function () { |
||||
ignoreAll = false; |
||||
deferred.resolve(items); |
||||
}, |
||||
function (err) { |
||||
ignoreAll = false; |
||||
deferred.reject(err); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let ignore = function (id, parent) { |
||||
if (ignoreAll) { |
||||
return true; |
||||
} |
||||
|
||||
if (rootId == parent || folderCache.has(parent)) { |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
let handleItemAdded = function (id, parent, index, type, uri, title, dateAdded, guid, parentGuid) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
if (PlacesUtils.bookmarks.TYPE_FOLDER == type) { |
||||
folderCache.insert(id, parent); |
||||
} |
||||
|
||||
eventSource.emit("add", guid); |
||||
deferred.resolve(); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let handleItemRemoved = function (id, parent, index, type, uri, guid, parentGuid) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
if (PlacesUtils.bookmarks.TYPE_FOLDER == type) { |
||||
folderCache.remove(id); |
||||
} |
||||
|
||||
eventSource.emit("remove", guid); |
||||
deferred.resolve(); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let handleItemChanged = function (id, property, isAnnotation, newValue, lastModified, type, parent, guid, parentGuid) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
eventSource.emit('change', guid); |
||||
deferred.resolve(); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let handleItemMoved = function (id, oldParent, oldIndex, newParent, newIndex, type, guid, oldParentGuid, newParentGuid) { |
||||
let deferred = Promise.defer(); |
||||
|
||||
function complete() { |
||||
eventSource.emit('move', guid); |
||||
deferred.resolve(); |
||||
} |
||||
|
||||
if (PlacesUtils.bookmarks.TYPE_FOLDER != type) { |
||||
complete(); |
||||
return deferred.promise; |
||||
} |
||||
|
||||
if (folderCache.has(oldParent) && folderCache.has(newParent)) { |
||||
// Folder move inside cloudSync root, so just update parents/children.
|
||||
folderCache.setParent(id, newParent); |
||||
complete(); |
||||
} else if (!folderCache.has(oldParent)) { |
||||
// Folder moved in from ouside cloudSync root.
|
||||
PlacesWrapper.updateCachedFolderIds(folderCache, newParent) |
||||
.then(complete, complete); |
||||
} else if (!folderCache.has(newParent)) { |
||||
// Folder moved out from inside cloudSync root.
|
||||
PlacesWrapper.updateCachedFolderIds(folderCache, oldParent) |
||||
.then(complete, complete); |
||||
} |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
let observer = { |
||||
onBeginBatchUpdate: function () { |
||||
}, |
||||
|
||||
onEndBatchUpdate: function () { |
||||
}, |
||||
|
||||
onItemAdded: function (id, parent, index, type, uri, title, dateAdded, guid, parentGuid) { |
||||
if (ignore(id, parent)) { |
||||
return; |
||||
} |
||||
|
||||
asyncCallback(this, handleItemAdded, Array.prototype.slice.call(arguments)); |
||||
}, |
||||
|
||||
onItemRemoved: function (id, parent, index, type, uri, guid, parentGuid) { |
||||
if (ignore(id, parent)) { |
||||
return; |
||||
} |
||||
|
||||
asyncCallback(this, handleItemRemoved, Array.prototype.slice.call(arguments)); |
||||
}, |
||||
|
||||
onItemChanged: function (id, property, isAnnotation, newValue, lastModified, type, parent, guid, parentGuid) { |
||||
if (ignore(id, parent)) { |
||||
return; |
||||
} |
||||
|
||||
asyncCallback(this, handleItemChanged, Array.prototype.slice.call(arguments)); |
||||
}, |
||||
|
||||
onItemMoved: function (id, oldParent, oldIndex, newParent, newIndex, type, guid, oldParentGuid, newParentGuid) { |
||||
if (ignore(id, oldParent) && ignore(id, newParent)) { |
||||
return; |
||||
} |
||||
|
||||
asyncCallback(this, handleItemMoved, Array.prototype.slice.call(arguments)); |
||||
} |
||||
}; |
||||
|
||||
/* PUBLIC API */ |
||||
this.addEventListener = eventSource.addEventListener; |
||||
this.removeEventListener = eventSource.removeEventListener; |
||||
this.getLocalItems = getLocalItems.bind(this); |
||||
this.getLocalItemsById = getLocalItemsById.bind(this); |
||||
this.mergeRemoteItems = mergeRemoteItems.bind(this); |
||||
|
||||
let rootGuid = null; // resolved before becoming ready (below)
|
||||
this.__defineGetter__("id", function () { |
||||
return rootGuid; |
||||
}); |
||||
this.__defineGetter__("name", function () { |
||||
return rootName; |
||||
}); |
||||
|
||||
let deferred = Promise.defer(); |
||||
let getGuidForRootFolder = function () { |
||||
return PlacesWrapper.localIdToGuid(rootId); |
||||
} |
||||
PlacesWrapper.updateCachedFolderIds(folderCache, rootId) |
||||
.then(getGuidForRootFolder, getGuidForRootFolder) |
||||
.then(function (guid) { |
||||
rootGuid = guid; |
||||
deferred.resolve(this); |
||||
}.bind(this), |
||||
deferred.reject); |
||||
return deferred.promise; |
||||
}; |
||||
|
||||
RootFolder.prototype = { |
||||
BOOKMARK: CS_BOOKMARK, |
||||
FOLDER: CS_FOLDER, |
||||
SEPARATOR: CS_SEPARATOR, |
||||
QUERY: CS_QUERY, |
||||
LIVEMARK: CS_LIVEMARK, |
||||
}; |
@ -1,105 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["FolderCache"]; |
||||
|
||||
// Cache for bookmarks folder heirarchy.
|
||||
var FolderCache = function () { |
||||
this.cache = new Map(); |
||||
} |
||||
|
||||
FolderCache.prototype = { |
||||
has: function (id) { |
||||
return this.cache.has(id); |
||||
}, |
||||
|
||||
insert: function (id, parentId) { |
||||
if (this.cache.has(id)) { |
||||
return; |
||||
} |
||||
|
||||
if (parentId && !(this.cache.has(parentId))) { |
||||
throw new Error("insert :: parentId not found in cache: " + parentId); |
||||
} |
||||
|
||||
this.cache.set(id, { |
||||
parent: parentId || null, |
||||
children: new Set(), |
||||
}); |
||||
|
||||
if (parentId) { |
||||
this.cache.get(parentId).children.add(id); |
||||
} |
||||
}, |
||||
|
||||
remove: function (id) { |
||||
if (!(this.cache.has(id))) { |
||||
throw new Error("remote :: id not found in cache: " + id); |
||||
} |
||||
|
||||
let parentId = this.cache.get(id).parent; |
||||
if (parentId) { |
||||
this.cache.get(parentId).children.delete(id); |
||||
} |
||||
|
||||
for (let child of this.cache.get(id).children) { |
||||
this.cache.get(child).parent = null; |
||||
} |
||||
|
||||
this.cache.delete(id); |
||||
}, |
||||
|
||||
setParent: function (id, parentId) { |
||||
if (!(this.cache.has(id))) { |
||||
throw new Error("setParent :: id not found in cache: " + id); |
||||
} |
||||
|
||||
if (parentId && !(this.cache.has(parentId))) { |
||||
throw new Error("setParent :: parentId not found in cache: " + parentId); |
||||
} |
||||
|
||||
let oldParent = this.cache.get(id).parent; |
||||
if (oldParent) { |
||||
this.cache.get(oldParent).children.delete(id); |
||||
} |
||||
this.cache.get(id).parent = parentId; |
||||
this.cache.get(parentId).children.add(id); |
||||
|
||||
return true; |
||||
}, |
||||
|
||||
getParent: function (id) { |
||||
if (this.cache.has(id)) { |
||||
return this.cache.get(id).parent; |
||||
} |
||||
|
||||
throw new Error("getParent :: id not found in cache: " + id); |
||||
}, |
||||
|
||||
getChildren: function (id) { |
||||
if (this.cache.has(id)) { |
||||
return this.cache.get(id).children; |
||||
} |
||||
|
||||
throw new Error("getChildren :: id not found in cache: " + id); |
||||
}, |
||||
|
||||
setChildren: function (id, children) { |
||||
for (let child of children) { |
||||
if (!this.cache.has(child)) { |
||||
this.insert(child, id); |
||||
} else { |
||||
this.setParent(child, id); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
dump: function () { |
||||
dump("FolderCache: " + JSON.stringify(this.cache) + "\n"); |
||||
}, |
||||
}; |
||||
|
||||
this.FolderCache = FolderCache; |
@ -1,65 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["EventSource"]; |
||||
|
||||
Components.utils.import("resource://services-common/utils.js"); |
||||
|
||||
var EventSource = function (types, suspendFunc, resumeFunc) { |
||||
this.listeners = new Map(); |
||||
for (let type of types) { |
||||
this.listeners.set(type, new Set()); |
||||
} |
||||
|
||||
this.suspend = suspendFunc || function () {}; |
||||
this.resume = resumeFunc || function () {}; |
||||
|
||||
this.addEventListener = this.addEventListener.bind(this); |
||||
this.removeEventListener = this.removeEventListener.bind(this); |
||||
}; |
||||
|
||||
EventSource.prototype = { |
||||
addEventListener: function (type, listener) { |
||||
if (!this.listeners.has(type)) { |
||||
return; |
||||
} |
||||
this.listeners.get(type).add(listener); |
||||
this.resume(); |
||||
}, |
||||
|
||||
removeEventListener: function (type, listener) { |
||||
if (!this.listeners.has(type)) { |
||||
return; |
||||
} |
||||
this.listeners.get(type).delete(listener); |
||||
if (!this.hasListeners()) { |
||||
this.suspend(); |
||||
} |
||||
}, |
||||
|
||||
hasListeners: function () { |
||||
for (let l of this.listeners.values()) { |
||||
if (l.size > 0) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
}, |
||||
|
||||
emit: function (type, arg) { |
||||
if (!this.listeners.has(type)) { |
||||
return; |
||||
} |
||||
CommonUtils.nextTick( |
||||
function () { |
||||
for (let listener of this.listeners.get(type)) { |
||||
listener.call(undefined, arg); |
||||
} |
||||
}, |
||||
this |
||||
); |
||||
}, |
||||
}; |
||||
|
||||
this.EventSource = EventSource; |
@ -1,87 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["Local"]; |
||||
|
||||
const Cu = Components.utils; |
||||
const Cc = Components.classes; |
||||
const Ci = Components.interfaces; |
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
||||
Cu.import("resource://services-common/stringbundle.js"); |
||||
Cu.import("resource://services-common/utils.js"); |
||||
Cu.import("resource://services-crypto/utils.js"); |
||||
Cu.import("resource://gre/modules/Preferences.jsm"); |
||||
|
||||
function lazyStrings(name) { |
||||
let bundle = "chrome://weave/locale/services/" + name + ".properties"; |
||||
return () => new StringBundle(bundle); |
||||
} |
||||
|
||||
this.Str = {}; |
||||
XPCOMUtils.defineLazyGetter(Str, "errors", lazyStrings("errors")); |
||||
XPCOMUtils.defineLazyGetter(Str, "sync", lazyStrings("sync")); |
||||
|
||||
function makeGUID() { |
||||
return CommonUtils.encodeBase64URL(CryptoUtils.generateRandomBytes(9)); |
||||
} |
||||
|
||||
this.Local = function () { |
||||
let prefs = new Preferences("services.cloudsync."); |
||||
this.__defineGetter__("prefs", function () { |
||||
return prefs; |
||||
}); |
||||
}; |
||||
|
||||
Local.prototype = { |
||||
get id() { |
||||
let clientId = this.prefs.get("client.GUID", ""); |
||||
return clientId == "" ? this.id = makeGUID(): clientId; |
||||
}, |
||||
|
||||
set id(value) { |
||||
this.prefs.set("client.GUID", value); |
||||
}, |
||||
|
||||
get name() { |
||||
let clientName = this.prefs.get("client.name", ""); |
||||
|
||||
if (clientName != "") { |
||||
return clientName; |
||||
} |
||||
|
||||
// Generate a client name if we don't have a useful one yet
|
||||
let env = Cc["@mozilla.org/process/environment;1"] |
||||
.getService(Ci.nsIEnvironment); |
||||
let user = env.get("USER") || env.get("USERNAME"); |
||||
let appName; |
||||
let brand = new StringBundle("chrome://branding/locale/brand.properties"); |
||||
let brandName = brand.get("brandShortName"); |
||||
|
||||
try { |
||||
let syncStrings = new StringBundle("chrome://browser/locale/sync.properties"); |
||||
appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]); |
||||
} catch (ex) { |
||||
} |
||||
|
||||
appName = appName || brandName; |
||||
|
||||
let system = |
||||
// 'device' is defined on unix systems
|
||||
Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") || |
||||
// hostname of the system, usually assigned by the user or admin
|
||||
Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") || |
||||
// fall back on ua info string
|
||||
Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu; |
||||
|
||||
return this.name = Str.sync.get("client.name2", [user, appName, system]); |
||||
}, |
||||
|
||||
set name(value) { |
||||
this.prefs.set("client.name", value); |
||||
}, |
||||
}; |
||||
|
@ -1,375 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public |
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"; |
||||
|
||||
this.EXPORTED_SYMBOLS = ["PlacesWrapper"]; |
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components; |
||||
const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR; |
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm"); |
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm"); |
||||
Cu.import("resource:///modules/PlacesUIUtils.jsm"); |
||||
Cu.import("resource://services-common/utils.js"); |
||||
|
||||
var PlacesQueries = function () { |
||||
} |
||||
|
||||
PlacesQueries.prototype = { |
||||
cachedStmts: {}, |
||||
|
||||