michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "experimental", michael@0: "engines": { michael@0: "Firefox": "*" michael@0: } michael@0: }; michael@0: michael@0: const { Cc, Ci } = require('chrome'); michael@0: const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"]. michael@0: getService(Ci.nsIBrowserHistory); michael@0: const asyncHistory = Cc["@mozilla.org/browser/history;1"]. michael@0: getService(Ci.mozIAsyncHistory); michael@0: const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. michael@0: getService(Ci.nsINavBookmarksService); michael@0: const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. michael@0: getService(Ci.nsITaggingService); michael@0: const ios = Cc['@mozilla.org/network/io-service;1']. michael@0: getService(Ci.nsIIOService); michael@0: const { query } = require('./host-query'); michael@0: const { michael@0: defer, all, resolve, promised, reject michael@0: } = require('../../core/promise'); michael@0: const { request, response } = require('../../addon/host'); michael@0: const { send } = require('../../addon/events'); michael@0: const { on, emit } = require('../../event/core'); michael@0: const { filter } = require('../../event/utils'); michael@0: const { URL, isValidURI } = require('../../url'); michael@0: const { newURI } = require('../../url/utils'); michael@0: michael@0: const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX; michael@0: const UNSORTED_ID = bmsrv.unfiledBookmarksFolder; michael@0: const ROOT_FOLDERS = [ michael@0: bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder, michael@0: bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder michael@0: ]; michael@0: michael@0: const EVENT_MAP = { michael@0: 'sdk-places-bookmarks-create': createBookmarkItem, michael@0: 'sdk-places-bookmarks-save': saveBookmarkItem, michael@0: 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated, michael@0: 'sdk-places-bookmarks-get': getBookmarkItem, michael@0: 'sdk-places-bookmarks-remove': removeBookmarkItem, michael@0: 'sdk-places-bookmarks-get-all': getAllBookmarks, michael@0: 'sdk-places-bookmarks-get-children': getChildren michael@0: }; michael@0: michael@0: function typeMap (type) { michael@0: if (typeof type === 'number') { michael@0: if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; michael@0: if (bmsrv.TYPE_FOLDER === type) return 'group'; michael@0: if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; michael@0: } else { michael@0: if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; michael@0: if ('group' === type) return bmsrv.TYPE_FOLDER; michael@0: if ('separator' === type) return bmsrv.TYPE_SEPARATOR; michael@0: } michael@0: } michael@0: michael@0: function getBookmarkLastUpdated ({id}) michael@0: resolve(bmsrv.getItemLastModified(id)) michael@0: exports.getBookmarkLastUpdated; michael@0: michael@0: function createBookmarkItem (data) { michael@0: let error; michael@0: michael@0: if (data.group == null) data.group = UNSORTED_ID; michael@0: if (data.index == null) data.index = DEFAULT_INDEX; michael@0: michael@0: if (data.type === 'group') michael@0: data.id = bmsrv.createFolder( michael@0: data.group, data.title, data.index michael@0: ); michael@0: else if (data.type === 'separator') michael@0: data.id = bmsrv.insertSeparator( michael@0: data.group, data.index michael@0: ); michael@0: else michael@0: data.id = bmsrv.insertBookmark( michael@0: data.group, newURI(data.url), data.index, data.title michael@0: ); michael@0: michael@0: // In the event where default or no index is provided (-1), michael@0: // query the actual index for the response michael@0: if (data.index === -1) michael@0: data.index = bmsrv.getItemIndex(data.id); michael@0: michael@0: data.updated = bmsrv.getItemLastModified(data.id); michael@0: michael@0: return tag(data, true).then(() => data); michael@0: } michael@0: exports.createBookmarkItem = createBookmarkItem; michael@0: michael@0: function saveBookmarkItem (data) { michael@0: let id = data.id; michael@0: if (!id) michael@0: reject('Item is missing id'); michael@0: michael@0: let group = bmsrv.getFolderIdForItem(id); michael@0: let index = bmsrv.getItemIndex(id); michael@0: let type = bmsrv.getItemType(id); michael@0: let title = typeMap(type) !== 'separator' ? michael@0: bmsrv.getItemTitle(id) : michael@0: undefined; michael@0: let url = typeMap(type) === 'bookmark' ? michael@0: bmsrv.getBookmarkURI(id).spec : michael@0: undefined; michael@0: michael@0: if (url != data.url) michael@0: bmsrv.changeBookmarkURI(id, newURI(data.url)); michael@0: else if (typeMap(type) === 'bookmark') michael@0: data.url = url; michael@0: michael@0: if (title != data.title) michael@0: bmsrv.setItemTitle(id, data.title); michael@0: else if (typeMap(type) !== 'separator') michael@0: data.title = title; michael@0: michael@0: if (data.group && data.group !== group) michael@0: bmsrv.moveItem(id, data.group, data.index || -1); michael@0: else if (data.index != null && data.index !== index) { michael@0: // We use moveItem here instead of setItemIndex michael@0: // so we don't have to manage the indicies of the siblings michael@0: bmsrv.moveItem(id, group, data.index); michael@0: } else if (data.index == null) michael@0: data.index = index; michael@0: michael@0: data.updated = bmsrv.getItemLastModified(data.id); michael@0: michael@0: return tag(data).then(() => data); michael@0: } michael@0: exports.saveBookmarkItem = saveBookmarkItem; michael@0: michael@0: function removeBookmarkItem (data) { michael@0: let id = data.id; michael@0: michael@0: if (!id) michael@0: reject('Item is missing id'); michael@0: michael@0: bmsrv.removeItem(id); michael@0: return resolve(null); michael@0: } michael@0: exports.removeBookmarkItem = removeBookmarkItem; michael@0: michael@0: function getBookmarkItem (data) { michael@0: let id = data.id; michael@0: michael@0: if (!id) michael@0: reject('Item is missing id'); michael@0: michael@0: let type = bmsrv.getItemType(id); michael@0: michael@0: data.type = typeMap(type); michael@0: michael@0: if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER) michael@0: data.title = bmsrv.getItemTitle(id); michael@0: michael@0: if (type === bmsrv.TYPE_BOOKMARK) { michael@0: data.url = bmsrv.getBookmarkURI(id).spec; michael@0: // Should be moved into host-tags as a method michael@0: data.tags = taggingService.getTagsForURI(newURI(data.url), {}); michael@0: } michael@0: michael@0: data.group = bmsrv.getFolderIdForItem(id); michael@0: data.index = bmsrv.getItemIndex(id); michael@0: data.updated = bmsrv.getItemLastModified(data.id); michael@0: michael@0: return resolve(data); michael@0: } michael@0: exports.getBookmarkItem = getBookmarkItem; michael@0: michael@0: function getAllBookmarks () { michael@0: return query({}, { queryType: 1 }).then(bookmarks => michael@0: all(bookmarks.map(getBookmarkItem))); michael@0: } michael@0: exports.getAllBookmarks = getAllBookmarks; michael@0: michael@0: function getChildren ({ id }) { michael@0: if (typeMap(bmsrv.getItemType(id)) !== 'group') return []; michael@0: let ids = []; michael@0: for (let i = 0; ids[ids.length - 1] !== -1; i++) michael@0: ids.push(bmsrv.getIdForItemAt(id, i)); michael@0: ids.pop(); michael@0: return all(ids.map(id => getBookmarkItem({ id: id }))); michael@0: } michael@0: exports.getChildren = getChildren; michael@0: michael@0: /* michael@0: * Hook into host michael@0: */ michael@0: michael@0: let reqStream = filter(request, function (data) /sdk-places-bookmarks/.test(data.event)); michael@0: on(reqStream, 'data', function ({event, id, data}) { michael@0: if (!EVENT_MAP[event]) return; michael@0: michael@0: let resData = { michael@0: id: id, michael@0: event: event michael@0: }; michael@0: michael@0: promised(EVENT_MAP[event])(data).then(res => { michael@0: resData.data = res; michael@0: respond(resData); michael@0: }, reason => { michael@0: resData.error = reason; michael@0: respond(resData); michael@0: }); michael@0: }); michael@0: michael@0: function respond (data) { michael@0: emit(response, 'data', data); michael@0: } michael@0: michael@0: function tag (data, isNew) { michael@0: // If a new item, we can skip checking what other tags michael@0: // are on the item michael@0: if (data.type !== 'bookmark') { michael@0: return resolve(); michael@0: } else if (!isNew) { michael@0: return send('sdk-places-tags-get-tags-by-url', { url: data.url }) michael@0: .then(tags => { michael@0: return send('sdk-places-tags-untag', { michael@0: tags: tags.filter(tag => !~data.tags.indexOf(tag)), michael@0: url: data.url michael@0: }); michael@0: }).then(() => send('sdk-places-tags-tag', { michael@0: url: data.url, tags: data.tags michael@0: })); michael@0: } michael@0: else if (data.tags && data.tags.length) { michael@0: return send('sdk-places-tags-tag', { url: data.url, tags: data.tags }); michael@0: } michael@0: else michael@0: return resolve(); michael@0: } michael@0: