|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "experimental", |
|
9 "engines": { |
|
10 "Firefox": "*" |
|
11 } |
|
12 }; |
|
13 |
|
14 const { Cc, Ci } = require('chrome'); |
|
15 const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"]. |
|
16 getService(Ci.nsIBrowserHistory); |
|
17 const asyncHistory = Cc["@mozilla.org/browser/history;1"]. |
|
18 getService(Ci.mozIAsyncHistory); |
|
19 const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. |
|
20 getService(Ci.nsINavBookmarksService); |
|
21 const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. |
|
22 getService(Ci.nsITaggingService); |
|
23 const ios = Cc['@mozilla.org/network/io-service;1']. |
|
24 getService(Ci.nsIIOService); |
|
25 const { query } = require('./host-query'); |
|
26 const { |
|
27 defer, all, resolve, promised, reject |
|
28 } = require('../../core/promise'); |
|
29 const { request, response } = require('../../addon/host'); |
|
30 const { send } = require('../../addon/events'); |
|
31 const { on, emit } = require('../../event/core'); |
|
32 const { filter } = require('../../event/utils'); |
|
33 const { URL, isValidURI } = require('../../url'); |
|
34 const { newURI } = require('../../url/utils'); |
|
35 |
|
36 const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX; |
|
37 const UNSORTED_ID = bmsrv.unfiledBookmarksFolder; |
|
38 const ROOT_FOLDERS = [ |
|
39 bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder, |
|
40 bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder |
|
41 ]; |
|
42 |
|
43 const EVENT_MAP = { |
|
44 'sdk-places-bookmarks-create': createBookmarkItem, |
|
45 'sdk-places-bookmarks-save': saveBookmarkItem, |
|
46 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated, |
|
47 'sdk-places-bookmarks-get': getBookmarkItem, |
|
48 'sdk-places-bookmarks-remove': removeBookmarkItem, |
|
49 'sdk-places-bookmarks-get-all': getAllBookmarks, |
|
50 'sdk-places-bookmarks-get-children': getChildren |
|
51 }; |
|
52 |
|
53 function typeMap (type) { |
|
54 if (typeof type === 'number') { |
|
55 if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; |
|
56 if (bmsrv.TYPE_FOLDER === type) return 'group'; |
|
57 if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; |
|
58 } else { |
|
59 if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; |
|
60 if ('group' === type) return bmsrv.TYPE_FOLDER; |
|
61 if ('separator' === type) return bmsrv.TYPE_SEPARATOR; |
|
62 } |
|
63 } |
|
64 |
|
65 function getBookmarkLastUpdated ({id}) |
|
66 resolve(bmsrv.getItemLastModified(id)) |
|
67 exports.getBookmarkLastUpdated; |
|
68 |
|
69 function createBookmarkItem (data) { |
|
70 let error; |
|
71 |
|
72 if (data.group == null) data.group = UNSORTED_ID; |
|
73 if (data.index == null) data.index = DEFAULT_INDEX; |
|
74 |
|
75 if (data.type === 'group') |
|
76 data.id = bmsrv.createFolder( |
|
77 data.group, data.title, data.index |
|
78 ); |
|
79 else if (data.type === 'separator') |
|
80 data.id = bmsrv.insertSeparator( |
|
81 data.group, data.index |
|
82 ); |
|
83 else |
|
84 data.id = bmsrv.insertBookmark( |
|
85 data.group, newURI(data.url), data.index, data.title |
|
86 ); |
|
87 |
|
88 // In the event where default or no index is provided (-1), |
|
89 // query the actual index for the response |
|
90 if (data.index === -1) |
|
91 data.index = bmsrv.getItemIndex(data.id); |
|
92 |
|
93 data.updated = bmsrv.getItemLastModified(data.id); |
|
94 |
|
95 return tag(data, true).then(() => data); |
|
96 } |
|
97 exports.createBookmarkItem = createBookmarkItem; |
|
98 |
|
99 function saveBookmarkItem (data) { |
|
100 let id = data.id; |
|
101 if (!id) |
|
102 reject('Item is missing id'); |
|
103 |
|
104 let group = bmsrv.getFolderIdForItem(id); |
|
105 let index = bmsrv.getItemIndex(id); |
|
106 let type = bmsrv.getItemType(id); |
|
107 let title = typeMap(type) !== 'separator' ? |
|
108 bmsrv.getItemTitle(id) : |
|
109 undefined; |
|
110 let url = typeMap(type) === 'bookmark' ? |
|
111 bmsrv.getBookmarkURI(id).spec : |
|
112 undefined; |
|
113 |
|
114 if (url != data.url) |
|
115 bmsrv.changeBookmarkURI(id, newURI(data.url)); |
|
116 else if (typeMap(type) === 'bookmark') |
|
117 data.url = url; |
|
118 |
|
119 if (title != data.title) |
|
120 bmsrv.setItemTitle(id, data.title); |
|
121 else if (typeMap(type) !== 'separator') |
|
122 data.title = title; |
|
123 |
|
124 if (data.group && data.group !== group) |
|
125 bmsrv.moveItem(id, data.group, data.index || -1); |
|
126 else if (data.index != null && data.index !== index) { |
|
127 // We use moveItem here instead of setItemIndex |
|
128 // so we don't have to manage the indicies of the siblings |
|
129 bmsrv.moveItem(id, group, data.index); |
|
130 } else if (data.index == null) |
|
131 data.index = index; |
|
132 |
|
133 data.updated = bmsrv.getItemLastModified(data.id); |
|
134 |
|
135 return tag(data).then(() => data); |
|
136 } |
|
137 exports.saveBookmarkItem = saveBookmarkItem; |
|
138 |
|
139 function removeBookmarkItem (data) { |
|
140 let id = data.id; |
|
141 |
|
142 if (!id) |
|
143 reject('Item is missing id'); |
|
144 |
|
145 bmsrv.removeItem(id); |
|
146 return resolve(null); |
|
147 } |
|
148 exports.removeBookmarkItem = removeBookmarkItem; |
|
149 |
|
150 function getBookmarkItem (data) { |
|
151 let id = data.id; |
|
152 |
|
153 if (!id) |
|
154 reject('Item is missing id'); |
|
155 |
|
156 let type = bmsrv.getItemType(id); |
|
157 |
|
158 data.type = typeMap(type); |
|
159 |
|
160 if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER) |
|
161 data.title = bmsrv.getItemTitle(id); |
|
162 |
|
163 if (type === bmsrv.TYPE_BOOKMARK) { |
|
164 data.url = bmsrv.getBookmarkURI(id).spec; |
|
165 // Should be moved into host-tags as a method |
|
166 data.tags = taggingService.getTagsForURI(newURI(data.url), {}); |
|
167 } |
|
168 |
|
169 data.group = bmsrv.getFolderIdForItem(id); |
|
170 data.index = bmsrv.getItemIndex(id); |
|
171 data.updated = bmsrv.getItemLastModified(data.id); |
|
172 |
|
173 return resolve(data); |
|
174 } |
|
175 exports.getBookmarkItem = getBookmarkItem; |
|
176 |
|
177 function getAllBookmarks () { |
|
178 return query({}, { queryType: 1 }).then(bookmarks => |
|
179 all(bookmarks.map(getBookmarkItem))); |
|
180 } |
|
181 exports.getAllBookmarks = getAllBookmarks; |
|
182 |
|
183 function getChildren ({ id }) { |
|
184 if (typeMap(bmsrv.getItemType(id)) !== 'group') return []; |
|
185 let ids = []; |
|
186 for (let i = 0; ids[ids.length - 1] !== -1; i++) |
|
187 ids.push(bmsrv.getIdForItemAt(id, i)); |
|
188 ids.pop(); |
|
189 return all(ids.map(id => getBookmarkItem({ id: id }))); |
|
190 } |
|
191 exports.getChildren = getChildren; |
|
192 |
|
193 /* |
|
194 * Hook into host |
|
195 */ |
|
196 |
|
197 let reqStream = filter(request, function (data) /sdk-places-bookmarks/.test(data.event)); |
|
198 on(reqStream, 'data', function ({event, id, data}) { |
|
199 if (!EVENT_MAP[event]) return; |
|
200 |
|
201 let resData = { |
|
202 id: id, |
|
203 event: event |
|
204 }; |
|
205 |
|
206 promised(EVENT_MAP[event])(data).then(res => { |
|
207 resData.data = res; |
|
208 respond(resData); |
|
209 }, reason => { |
|
210 resData.error = reason; |
|
211 respond(resData); |
|
212 }); |
|
213 }); |
|
214 |
|
215 function respond (data) { |
|
216 emit(response, 'data', data); |
|
217 } |
|
218 |
|
219 function tag (data, isNew) { |
|
220 // If a new item, we can skip checking what other tags |
|
221 // are on the item |
|
222 if (data.type !== 'bookmark') { |
|
223 return resolve(); |
|
224 } else if (!isNew) { |
|
225 return send('sdk-places-tags-get-tags-by-url', { url: data.url }) |
|
226 .then(tags => { |
|
227 return send('sdk-places-tags-untag', { |
|
228 tags: tags.filter(tag => !~data.tags.indexOf(tag)), |
|
229 url: data.url |
|
230 }); |
|
231 }).then(() => send('sdk-places-tags-tag', { |
|
232 url: data.url, tags: data.tags |
|
233 })); |
|
234 } |
|
235 else if (data.tags && data.tags.length) { |
|
236 return send('sdk-places-tags-tag', { url: data.url, tags: data.tags }); |
|
237 } |
|
238 else |
|
239 return resolve(); |
|
240 } |
|
241 |