Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nsNavBookmarks.h"
7 :
8 : #include "nsNavHistory.h"
9 : #include "nsAnnotationService.h"
10 : #include "nsPlacesMacros.h"
11 : #include "Helpers.h"
12 :
13 : #include "nsAppDirectoryServiceDefs.h"
14 : #include "nsNetUtil.h"
15 : #include "nsUnicharUtils.h"
16 : #include "nsPrintfCString.h"
17 : #include "mozilla/Preferences.h"
18 : #include "mozilla/storage.h"
19 :
20 : #include "GeckoProfiler.h"
21 :
22 : using namespace mozilla;
23 :
24 : // These columns sit to the right of the kGetInfoIndex_* columns.
25 : const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
26 : const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
27 : const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
28 : const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
29 : const int32_t nsNavBookmarks::kGetChildrenIndex_SyncStatus = 22;
30 :
31 : using namespace mozilla::places;
32 :
33 2 : PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
34 :
35 : #define BOOKMARKS_ANNO_PREFIX "bookmarks/"
36 : #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
37 : #define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
38 : #define SYNC_PARENT_ANNO "sync/parent"
39 : #define SQLITE_MAX_VARIABLE_NUMBER 999
40 :
41 :
42 : namespace {
43 :
44 : #define SKIP_TAGS(condition) ((condition) ? SkipTags : DontSkip)
45 :
46 0 : bool DontSkip(nsCOMPtr<nsINavBookmarkObserver> obs) { return false; }
47 0 : bool SkipTags(nsCOMPtr<nsINavBookmarkObserver> obs) {
48 0 : bool skipTags = false;
49 0 : (void) obs->GetSkipTags(&skipTags);
50 0 : return skipTags;
51 : }
52 0 : bool SkipDescendants(nsCOMPtr<nsINavBookmarkObserver> obs) {
53 0 : bool skipDescendantsOnItemRemoval = false;
54 0 : (void) obs->GetSkipTags(&skipDescendantsOnItemRemoval);
55 0 : return skipDescendantsOnItemRemoval;
56 : }
57 :
58 : template<typename Method, typename DataType>
59 3 : class AsyncGetBookmarksForURI : public AsyncStatementCallback
60 : {
61 : public:
62 1 : AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
63 : Method aCallback,
64 : const DataType& aData)
65 : : mBookmarksSvc(aBookmarksSvc)
66 : , mCallback(aCallback)
67 1 : , mData(aData)
68 : {
69 1 : }
70 :
71 1 : void Init()
72 : {
73 2 : RefPtr<Database> DB = Database::GetDatabase();
74 1 : if (DB) {
75 : nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
76 : "/* do not warn (bug 1175249) */ "
77 : "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
78 : "FROM moz_bookmarks b "
79 : "JOIN moz_bookmarks t on t.id = b.parent "
80 : "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
81 : "ORDER BY b.lastModified DESC, b.id DESC "
82 2 : );
83 1 : if (stmt) {
84 1 : (void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
85 : mData.bookmark.url);
86 2 : nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
87 1 : (void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
88 : }
89 : }
90 1 : }
91 :
92 0 : NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet)
93 : {
94 0 : nsCOMPtr<mozIStorageRow> row;
95 0 : while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
96 : // Skip tags, for the use-cases of this async getter they are useless.
97 0 : int64_t grandParentId = -1, tagsFolderId = -1;
98 0 : nsresult rv = row->GetInt64(5, &grandParentId);
99 0 : NS_ENSURE_SUCCESS(rv, rv);
100 0 : rv = mBookmarksSvc->GetTagsFolder(&tagsFolderId);
101 0 : NS_ENSURE_SUCCESS(rv, rv);
102 0 : if (grandParentId == tagsFolderId) {
103 0 : continue;
104 : }
105 :
106 0 : mData.bookmark.grandParentId = grandParentId;
107 0 : rv = row->GetInt64(0, &mData.bookmark.id);
108 0 : NS_ENSURE_SUCCESS(rv, rv);
109 0 : rv = row->GetUTF8String(1, mData.bookmark.guid);
110 0 : NS_ENSURE_SUCCESS(rv, rv);
111 0 : rv = row->GetInt64(2, &mData.bookmark.parentId);
112 0 : NS_ENSURE_SUCCESS(rv, rv);
113 : // lastModified (3) should not be set for the use-cases of this getter.
114 0 : rv = row->GetUTF8String(4, mData.bookmark.parentGuid);
115 0 : NS_ENSURE_SUCCESS(rv, rv);
116 :
117 0 : if (mCallback) {
118 0 : ((*mBookmarksSvc).*mCallback)(mData);
119 : }
120 : }
121 0 : return NS_OK;
122 : }
123 :
124 : private:
125 : RefPtr<nsNavBookmarks> mBookmarksSvc;
126 : Method mCallback;
127 : DataType mData;
128 : };
129 :
130 : // Returns the sync change counter increment for a change source constant.
131 : inline int64_t
132 0 : DetermineSyncChangeDelta(uint16_t aSource) {
133 0 : return aSource == nsINavBookmarksService::SOURCE_SYNC ? 0 : 1;
134 : }
135 :
136 : // Returns the sync status for a new item inserted by a change source.
137 : inline int32_t
138 0 : DetermineInitialSyncStatus(uint16_t aSource) {
139 0 : if (aSource == nsINavBookmarksService::SOURCE_SYNC) {
140 0 : return nsINavBookmarksService::SYNC_STATUS_NORMAL;
141 : }
142 0 : if (aSource == nsINavBookmarksService::SOURCE_IMPORT_REPLACE) {
143 0 : return nsINavBookmarksService::SYNC_STATUS_UNKNOWN;
144 : }
145 0 : return nsINavBookmarksService::SYNC_STATUS_NEW;
146 : }
147 :
148 : // Indicates whether an item has been uploaded to the server and
149 : // needs a tombstone on deletion.
150 : inline bool
151 0 : NeedsTombstone(const BookmarkData& aBookmark) {
152 0 : return aBookmark.syncStatus == nsINavBookmarksService::SYNC_STATUS_NORMAL;
153 : }
154 :
155 : } // namespace
156 :
157 :
158 1 : nsNavBookmarks::nsNavBookmarks()
159 : : mItemCount(0)
160 : , mRoot(0)
161 : , mMenuRoot(0)
162 : , mTagsRoot(0)
163 : , mUnfiledRoot(0)
164 : , mToolbarRoot(0)
165 : , mMobileRoot(0)
166 : , mCanNotify(false)
167 : , mCacheObservers("bookmark-observers")
168 1 : , mBatching(false)
169 : {
170 1 : NS_ASSERTION(!gBookmarksService,
171 : "Attempting to create two instances of the service!");
172 1 : gBookmarksService = this;
173 1 : }
174 :
175 :
176 0 : nsNavBookmarks::~nsNavBookmarks()
177 : {
178 0 : NS_ASSERTION(gBookmarksService == this,
179 : "Deleting a non-singleton instance of the service");
180 0 : if (gBookmarksService == this)
181 0 : gBookmarksService = nullptr;
182 0 : }
183 :
184 :
185 84 : NS_IMPL_ISUPPORTS(nsNavBookmarks
186 : , nsINavBookmarksService
187 : , nsINavHistoryObserver
188 : , nsIAnnotationObserver
189 : , nsIObserver
190 : , nsISupportsWeakReference
191 : )
192 :
193 :
194 : Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
195 :
196 :
197 : void // static
198 0 : nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
199 : const int64_t aLastInsertedId) {
200 0 : MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
201 0 : sLastInsertedItemId = aLastInsertedId;
202 0 : }
203 :
204 :
205 : nsresult
206 1 : nsNavBookmarks::Init()
207 : {
208 1 : mDB = Database::GetDatabase();
209 1 : NS_ENSURE_STATE(mDB);
210 :
211 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
212 1 : if (os) {
213 1 : (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
214 1 : (void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
215 : }
216 :
217 1 : mCanNotify = true;
218 :
219 : // Observe annotations.
220 1 : nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
221 1 : NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
222 1 : annosvc->AddObserver(this);
223 :
224 : // Allows us to notify on title changes. MUST BE LAST so it is impossible
225 : // to fail after this call, or the history service will have a reference to
226 : // us and we won't go away.
227 1 : nsNavHistory* history = nsNavHistory::GetHistoryService();
228 1 : NS_ENSURE_STATE(history);
229 1 : history->AddObserver(this, true);
230 :
231 : // DO NOT PUT STUFF HERE that can fail. See observer comment above.
232 :
233 1 : return NS_OK;
234 : }
235 :
236 : nsresult
237 0 : nsNavBookmarks::EnsureRoots()
238 : {
239 0 : if (mRoot)
240 0 : return NS_OK;
241 :
242 0 : nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
243 0 : if (!conn) {
244 0 : return NS_ERROR_UNEXPECTED;
245 : }
246 :
247 0 : nsCOMPtr<mozIStorageStatement> stmt;
248 0 : nsresult rv = conn->CreateStatement(NS_LITERAL_CSTRING(
249 : "SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
250 : "'root________', 'menu________', 'toolbar_____', "
251 : "'tags________', 'unfiled_____', 'mobile______' )"
252 0 : ), getter_AddRefs(stmt));
253 0 : NS_ENSURE_SUCCESS(rv, rv);
254 :
255 : bool hasResult;
256 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
257 0 : nsAutoCString guid;
258 0 : rv = stmt->GetUTF8String(0, guid);
259 0 : NS_ENSURE_SUCCESS(rv, rv);
260 : int64_t id;
261 0 : rv = stmt->GetInt64(1, &id);
262 0 : NS_ENSURE_SUCCESS(rv, rv);
263 :
264 0 : if (guid.EqualsLiteral("root________")) {
265 0 : mRoot = id;
266 : }
267 0 : else if (guid.EqualsLiteral("menu________")) {
268 0 : mMenuRoot = id;
269 : }
270 0 : else if (guid.EqualsLiteral("toolbar_____")) {
271 0 : mToolbarRoot = id;
272 : }
273 0 : else if (guid.EqualsLiteral("tags________")) {
274 0 : mTagsRoot = id;
275 : }
276 0 : else if (guid.EqualsLiteral("unfiled_____")) {
277 0 : mUnfiledRoot = id;
278 : }
279 0 : else if (guid.EqualsLiteral("mobile______")) {
280 0 : mMobileRoot = id;
281 : }
282 : }
283 :
284 0 : if (!mRoot || !mMenuRoot || !mToolbarRoot || !mTagsRoot || !mUnfiledRoot ||
285 0 : !mMobileRoot)
286 0 : return NS_ERROR_FAILURE;
287 :
288 0 : return NS_OK;
289 : }
290 :
291 : // nsNavBookmarks::IsBookmarkedInDatabase
292 : //
293 : // This checks to see if the specified place_id is actually bookmarked.
294 :
295 : nsresult
296 0 : nsNavBookmarks::IsBookmarkedInDatabase(int64_t aPlaceId,
297 : bool* aIsBookmarked)
298 : {
299 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
300 : "SELECT 1 FROM moz_bookmarks WHERE fk = :page_id"
301 0 : );
302 0 : NS_ENSURE_STATE(stmt);
303 0 : mozStorageStatementScoper scoper(stmt);
304 :
305 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
306 0 : NS_ENSURE_SUCCESS(rv, rv);
307 0 : rv = stmt->ExecuteStep(aIsBookmarked);
308 0 : NS_ENSURE_SUCCESS(rv, rv);
309 0 : return NS_OK;
310 : }
311 :
312 :
313 : nsresult
314 0 : nsNavBookmarks::AdjustIndices(int64_t aFolderId,
315 : int32_t aStartIndex,
316 : int32_t aEndIndex,
317 : int32_t aDelta)
318 : {
319 0 : NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= INT32_MAX &&
320 : aStartIndex <= aEndIndex, "Bad indices");
321 :
322 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
323 : "UPDATE moz_bookmarks SET position = position + :delta "
324 : "WHERE parent = :parent "
325 : "AND position BETWEEN :from_index AND :to_index"
326 0 : );
327 0 : NS_ENSURE_STATE(stmt);
328 0 : mozStorageStatementScoper scoper(stmt);
329 :
330 0 : nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
331 0 : NS_ENSURE_SUCCESS(rv, rv);
332 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
333 0 : NS_ENSURE_SUCCESS(rv, rv);
334 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
335 0 : NS_ENSURE_SUCCESS(rv, rv);
336 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
337 0 : NS_ENSURE_SUCCESS(rv, rv);
338 :
339 0 : rv = stmt->Execute();
340 0 : NS_ENSURE_SUCCESS(rv, rv);
341 :
342 0 : return NS_OK;
343 : }
344 :
345 :
346 : nsresult
347 0 : nsNavBookmarks::AdjustSeparatorsSyncCounter(int64_t aFolderId,
348 : int32_t aStartIndex,
349 : int64_t aSyncChangeDelta)
350 : {
351 0 : MOZ_ASSERT(aStartIndex >= 0, "Bad start position");
352 0 : if (!aSyncChangeDelta) {
353 0 : return NS_OK;
354 : }
355 :
356 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
357 : "UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + :delta "
358 : "WHERE parent = :parent AND position >= :start_index "
359 : "AND type = :item_type "
360 0 : );
361 0 : NS_ENSURE_STATE(stmt);
362 0 : mozStorageStatementScoper scoper(stmt);
363 :
364 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
365 0 : NS_ENSURE_SUCCESS(rv, rv);
366 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
367 0 : NS_ENSURE_SUCCESS(rv, rv);
368 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("start_index"), aStartIndex);
369 0 : NS_ENSURE_SUCCESS(rv, rv);
370 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_SEPARATOR);
371 0 : NS_ENSURE_SUCCESS(rv, rv);
372 :
373 0 : rv = stmt->Execute();
374 0 : NS_ENSURE_SUCCESS(rv, rv);
375 :
376 0 : return NS_OK;
377 : }
378 :
379 :
380 : NS_IMETHODIMP
381 0 : nsNavBookmarks::GetPlacesRoot(int64_t* aRoot)
382 : {
383 0 : nsresult rv = EnsureRoots();
384 0 : NS_ENSURE_SUCCESS(rv, rv);
385 0 : *aRoot = mRoot;
386 0 : return NS_OK;
387 : }
388 :
389 :
390 : NS_IMETHODIMP
391 0 : nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot)
392 : {
393 0 : nsresult rv = EnsureRoots();
394 0 : NS_ENSURE_SUCCESS(rv, rv);
395 0 : *aRoot = mMenuRoot;
396 0 : return NS_OK;
397 : }
398 :
399 :
400 : NS_IMETHODIMP
401 0 : nsNavBookmarks::GetToolbarFolder(int64_t* aFolderId)
402 : {
403 0 : nsresult rv = EnsureRoots();
404 0 : NS_ENSURE_SUCCESS(rv, rv);
405 0 : *aFolderId = mToolbarRoot;
406 0 : return NS_OK;
407 : }
408 :
409 :
410 : NS_IMETHODIMP
411 0 : nsNavBookmarks::GetTagsFolder(int64_t* aRoot)
412 : {
413 0 : nsresult rv = EnsureRoots();
414 0 : NS_ENSURE_SUCCESS(rv, rv);
415 0 : *aRoot = mTagsRoot;
416 0 : return NS_OK;
417 : }
418 :
419 :
420 : NS_IMETHODIMP
421 0 : nsNavBookmarks::GetUnfiledBookmarksFolder(int64_t* aRoot)
422 : {
423 0 : nsresult rv = EnsureRoots();
424 0 : NS_ENSURE_SUCCESS(rv, rv);
425 0 : *aRoot = mUnfiledRoot;
426 0 : return NS_OK;
427 : }
428 :
429 :
430 : NS_IMETHODIMP
431 0 : nsNavBookmarks::GetMobileFolder(int64_t* aRoot)
432 : {
433 0 : nsresult rv = EnsureRoots();
434 0 : NS_ENSURE_SUCCESS(rv, rv);
435 0 : *aRoot = mMobileRoot;
436 0 : return NS_OK;
437 : }
438 :
439 :
440 : nsresult
441 0 : nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
442 : enum ItemType aItemType,
443 : int64_t aParentId,
444 : int32_t aIndex,
445 : const nsACString& aTitle,
446 : PRTime aDateAdded,
447 : PRTime aLastModified,
448 : const nsACString& aParentGuid,
449 : int64_t aGrandParentId,
450 : nsIURI* aURI,
451 : uint16_t aSource,
452 : int64_t* _itemId,
453 : nsACString& _guid)
454 : {
455 : // Check for a valid itemId.
456 0 : MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
457 : // Check for a valid placeId.
458 0 : MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
459 :
460 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
461 : "INSERT INTO moz_bookmarks "
462 : "(id, fk, type, parent, position, title, "
463 : "dateAdded, lastModified, guid, syncStatus, syncChangeCounter) "
464 : "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
465 : ":item_title, :date_added, :last_modified, "
466 : ":item_guid, :sync_status, :change_counter)"
467 0 : );
468 0 : NS_ENSURE_STATE(stmt);
469 0 : mozStorageStatementScoper scoper(stmt);
470 :
471 : nsresult rv;
472 0 : if (*_itemId != -1)
473 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
474 : else
475 0 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
476 0 : NS_ENSURE_SUCCESS(rv, rv);
477 :
478 0 : if (aPlaceId != -1)
479 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
480 : else
481 0 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
482 0 : NS_ENSURE_SUCCESS(rv, rv);
483 :
484 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
485 0 : NS_ENSURE_SUCCESS(rv, rv);
486 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
487 0 : NS_ENSURE_SUCCESS(rv, rv);
488 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
489 0 : NS_ENSURE_SUCCESS(rv, rv);
490 :
491 0 : if (aTitle.IsEmpty())
492 0 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
493 : else
494 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
495 0 : NS_ENSURE_SUCCESS(rv, rv);
496 :
497 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
498 0 : NS_ENSURE_SUCCESS(rv, rv);
499 :
500 0 : if (aLastModified) {
501 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
502 0 : aLastModified);
503 : }
504 : else {
505 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
506 : }
507 0 : NS_ENSURE_SUCCESS(rv, rv);
508 :
509 : // Could use IsEmpty because our callers check for GUID validity,
510 : // but it doesn't hurt.
511 0 : bool hasExistingGuid = _guid.Length() == 12;
512 0 : if (hasExistingGuid) {
513 0 : MOZ_ASSERT(IsValidGUID(_guid));
514 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
515 0 : NS_ENSURE_SUCCESS(rv, rv);
516 : }
517 : else {
518 0 : nsAutoCString guid;
519 0 : rv = GenerateGUID(guid);
520 0 : NS_ENSURE_SUCCESS(rv, rv);
521 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), guid);
522 0 : NS_ENSURE_SUCCESS(rv, rv);
523 0 : _guid.Assign(guid);
524 : }
525 :
526 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
527 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("change_counter"),
528 0 : syncChangeDelta);
529 0 : NS_ENSURE_SUCCESS(rv, rv);
530 :
531 0 : uint16_t syncStatus = DetermineInitialSyncStatus(aSource);
532 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"),
533 0 : syncStatus);
534 0 : NS_ENSURE_SUCCESS(rv, rv);
535 :
536 0 : rv = stmt->Execute();
537 0 : NS_ENSURE_SUCCESS(rv, rv);
538 :
539 : // Remove stale tombstones if we're reinserting an item.
540 0 : if (hasExistingGuid) {
541 0 : rv = RemoveTombstone(_guid);
542 0 : NS_ENSURE_SUCCESS(rv, rv);
543 : }
544 :
545 0 : if (*_itemId == -1) {
546 0 : *_itemId = sLastInsertedItemId;
547 : }
548 :
549 0 : if (aParentId > 0) {
550 : // Update last modified date of the ancestors.
551 : // TODO (bug 408991): Doing this for all ancestors would be slow without a
552 : // nested tree, so for now update only the parent.
553 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, aParentId,
554 0 : aDateAdded);
555 0 : NS_ENSURE_SUCCESS(rv, rv);
556 : }
557 :
558 0 : int64_t tagsRootId = TagsRootId();
559 0 : bool isTagging = aGrandParentId == tagsRootId;
560 0 : if (isTagging) {
561 : // If we're tagging a bookmark, increment the change counter for all
562 : // bookmarks with the URI.
563 0 : rv = AddSyncChangesForBookmarksWithURI(aURI, syncChangeDelta);
564 0 : NS_ENSURE_SUCCESS(rv, rv);
565 : }
566 :
567 : // Mark all affected separators as changed
568 0 : rv = AdjustSeparatorsSyncCounter(aParentId, aIndex + 1, syncChangeDelta);
569 0 : NS_ENSURE_SUCCESS(rv, rv);
570 :
571 : // Add a cache entry since we know everything about this bookmark.
572 0 : BookmarkData bookmark;
573 0 : bookmark.id = *_itemId;
574 0 : bookmark.guid.Assign(_guid);
575 0 : if (!aTitle.IsEmpty()) {
576 0 : bookmark.title.Assign(aTitle);
577 : }
578 0 : bookmark.position = aIndex;
579 0 : bookmark.placeId = aPlaceId;
580 0 : bookmark.parentId = aParentId;
581 0 : bookmark.type = aItemType;
582 0 : bookmark.dateAdded = aDateAdded;
583 0 : if (aLastModified)
584 0 : bookmark.lastModified = aLastModified;
585 : else
586 0 : bookmark.lastModified = aDateAdded;
587 0 : if (aURI) {
588 0 : rv = aURI->GetSpec(bookmark.url);
589 0 : NS_ENSURE_SUCCESS(rv, rv);
590 : }
591 0 : bookmark.parentGuid = aParentGuid;
592 0 : bookmark.grandParentId = aGrandParentId;
593 0 : bookmark.syncStatus = syncStatus;
594 :
595 0 : return NS_OK;
596 : }
597 :
598 : NS_IMETHODIMP
599 0 : nsNavBookmarks::InsertBookmark(int64_t aFolder,
600 : nsIURI* aURI,
601 : int32_t aIndex,
602 : const nsACString& aTitle,
603 : const nsACString& aGUID,
604 : uint16_t aSource,
605 : int64_t* aNewBookmarkId)
606 : {
607 0 : NS_ENSURE_ARG(aURI);
608 0 : NS_ENSURE_ARG_POINTER(aNewBookmarkId);
609 0 : NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
610 :
611 0 : if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
612 0 : return NS_ERROR_INVALID_ARG;
613 :
614 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
615 :
616 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
617 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
618 : int64_t placeId;
619 0 : nsAutoCString placeGuid;
620 0 : nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
621 0 : NS_ENSURE_SUCCESS(rv, rv);
622 :
623 : // Get the correct index for insertion. This also ensures the parent exists.
624 : int32_t index, folderCount;
625 : int64_t grandParentId;
626 0 : nsAutoCString folderGuid;
627 0 : rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
628 0 : NS_ENSURE_SUCCESS(rv, rv);
629 0 : if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
630 0 : aIndex >= folderCount) {
631 0 : index = folderCount;
632 : }
633 : else {
634 0 : index = aIndex;
635 : // Create space for the insertion.
636 0 : rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
637 0 : NS_ENSURE_SUCCESS(rv, rv);
638 : }
639 :
640 0 : *aNewBookmarkId = -1;
641 0 : PRTime dateAdded = RoundedPRNow();
642 0 : nsAutoCString guid(aGUID);
643 0 : nsCString title;
644 0 : TruncateTitle(aTitle, title);
645 :
646 0 : rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
647 : 0, folderGuid, grandParentId, aURI, aSource,
648 0 : aNewBookmarkId, guid);
649 0 : NS_ENSURE_SUCCESS(rv, rv);
650 :
651 : // If not a tag, recalculate frecency for this entry, since it changed.
652 0 : int64_t tagsRootId = TagsRootId();
653 0 : if (grandParentId != tagsRootId) {
654 0 : rv = history->UpdateFrecency(placeId);
655 0 : NS_ENSURE_SUCCESS(rv, rv);
656 : }
657 :
658 0 : rv = transaction.Commit();
659 0 : NS_ENSURE_SUCCESS(rv, rv);
660 :
661 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
662 : SKIP_TAGS(grandParentId == mTagsRoot),
663 : OnItemAdded(*aNewBookmarkId, aFolder, index,
664 : TYPE_BOOKMARK, aURI, title, dateAdded,
665 : guid, folderGuid, aSource));
666 :
667 : // If the bookmark has been added to a tag container, notify all
668 : // bookmark-folder result nodes which contain a bookmark for the new
669 : // bookmark's url.
670 0 : if (grandParentId == tagsRootId) {
671 : // Notify a tags change to all bookmarks for this URI.
672 0 : nsTArray<BookmarkData> bookmarks;
673 0 : rv = GetBookmarksForURI(aURI, bookmarks);
674 0 : NS_ENSURE_SUCCESS(rv, rv);
675 :
676 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
677 : // Check that bookmarks doesn't include the current tag itemId.
678 0 : MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
679 :
680 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
681 : DontSkip,
682 : OnItemChanged(bookmarks[i].id,
683 : NS_LITERAL_CSTRING("tags"),
684 : false,
685 : EmptyCString(),
686 : bookmarks[i].lastModified,
687 : TYPE_BOOKMARK,
688 : bookmarks[i].parentId,
689 : bookmarks[i].guid,
690 : bookmarks[i].parentGuid,
691 : EmptyCString(),
692 : aSource));
693 : }
694 : }
695 :
696 0 : return NS_OK;
697 : }
698 :
699 :
700 : NS_IMETHODIMP
701 0 : nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource)
702 : {
703 0 : AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveItem", OTHER);
704 :
705 0 : NS_ENSURE_ARG(!IsRoot(aItemId));
706 :
707 0 : BookmarkData bookmark;
708 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
709 0 : NS_ENSURE_SUCCESS(rv, rv);
710 :
711 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
712 :
713 : // First, if not a tag, remove item annotations.
714 0 : int64_t tagsRootId = TagsRootId();
715 0 : bool isUntagging = bookmark.grandParentId == tagsRootId;
716 0 : if (bookmark.parentId != tagsRootId && !isUntagging) {
717 0 : nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
718 0 : NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
719 0 : rv = annosvc->RemoveItemAnnotations(bookmark.id, aSource);
720 0 : NS_ENSURE_SUCCESS(rv, rv);
721 : }
722 :
723 0 : if (bookmark.type == TYPE_FOLDER) {
724 : // Remove all of the folder's children.
725 0 : rv = RemoveFolderChildren(bookmark.id, aSource);
726 0 : NS_ENSURE_SUCCESS(rv, rv);
727 : }
728 :
729 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
730 : "DELETE FROM moz_bookmarks WHERE id = :item_id"
731 0 : );
732 0 : NS_ENSURE_STATE(stmt);
733 0 : mozStorageStatementScoper scoper(stmt);
734 :
735 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
736 0 : NS_ENSURE_SUCCESS(rv, rv);
737 0 : rv = stmt->Execute();
738 0 : NS_ENSURE_SUCCESS(rv, rv);
739 :
740 : // Fix indices in the parent.
741 0 : if (bookmark.position != DEFAULT_INDEX) {
742 0 : rv = AdjustIndices(bookmark.parentId,
743 0 : bookmark.position + 1, INT32_MAX, -1);
744 0 : NS_ENSURE_SUCCESS(rv, rv);
745 : }
746 :
747 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
748 :
749 : // Add a tombstone for synced items.
750 0 : if (syncChangeDelta) {
751 0 : rv = InsertTombstone(bookmark);
752 0 : NS_ENSURE_SUCCESS(rv, rv);
753 : }
754 :
755 0 : bookmark.lastModified = RoundedPRNow();
756 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta,
757 0 : bookmark.parentId, bookmark.lastModified);
758 0 : NS_ENSURE_SUCCESS(rv, rv);
759 :
760 : // Mark all affected separators as changed
761 0 : rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position, syncChangeDelta);
762 0 : NS_ENSURE_SUCCESS(rv, rv);
763 :
764 0 : if (isUntagging) {
765 : // If we're removing a tag, increment the change counter for all bookmarks
766 : // with the URI.
767 0 : rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
768 0 : NS_ENSURE_SUCCESS(rv, rv);
769 : }
770 :
771 0 : rv = transaction.Commit();
772 0 : NS_ENSURE_SUCCESS(rv, rv);
773 :
774 0 : nsCOMPtr<nsIURI> uri;
775 0 : if (bookmark.type == TYPE_BOOKMARK) {
776 : // If not a tag, recalculate frecency for this entry, since it changed.
777 0 : if (bookmark.grandParentId != tagsRootId) {
778 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
779 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
780 0 : rv = history->UpdateFrecency(bookmark.placeId);
781 0 : NS_ENSURE_SUCCESS(rv, rv);
782 : }
783 : // A broken url should not interrupt the removal process.
784 0 : (void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
785 : // We cannot assert since some automated tests are checking this path.
786 0 : NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
787 : }
788 :
789 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
790 : SKIP_TAGS(bookmark.parentId == tagsRootId ||
791 : bookmark.grandParentId == tagsRootId),
792 : OnItemRemoved(bookmark.id,
793 : bookmark.parentId,
794 : bookmark.position,
795 : bookmark.type,
796 : uri,
797 : bookmark.guid,
798 : bookmark.parentGuid,
799 : aSource));
800 :
801 0 : if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == tagsRootId &&
802 0 : uri) {
803 : // If the removed bookmark was child of a tag container, notify a tags
804 : // change to all bookmarks for this URI.
805 0 : nsTArray<BookmarkData> bookmarks;
806 0 : rv = GetBookmarksForURI(uri, bookmarks);
807 0 : NS_ENSURE_SUCCESS(rv, rv);
808 :
809 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
810 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
811 : DontSkip,
812 : OnItemChanged(bookmarks[i].id,
813 : NS_LITERAL_CSTRING("tags"),
814 : false,
815 : EmptyCString(),
816 : bookmarks[i].lastModified,
817 : TYPE_BOOKMARK,
818 : bookmarks[i].parentId,
819 : bookmarks[i].guid,
820 : bookmarks[i].parentGuid,
821 : EmptyCString(),
822 : aSource));
823 : }
824 :
825 : }
826 :
827 0 : return NS_OK;
828 : }
829 :
830 :
831 : NS_IMETHODIMP
832 0 : nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aName,
833 : int32_t aIndex, const nsACString& aGUID,
834 : uint16_t aSource, int64_t* aNewFolder)
835 : {
836 : // NOTE: aParent can be null for root creation, so not checked
837 0 : NS_ENSURE_ARG_POINTER(aNewFolder);
838 :
839 0 : if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
840 0 : return NS_ERROR_INVALID_ARG;
841 :
842 : // CreateContainerWithID returns the index of the new folder, but that's not
843 : // used here. To avoid any risk of corrupting data should this function
844 : // be changed, we'll use a local variable to hold it. The true argument
845 : // will cause notifications to be sent to bookmark observers.
846 0 : int32_t localIndex = aIndex;
847 0 : nsresult rv = CreateContainerWithID(-1, aParent, aName, true, &localIndex,
848 0 : aGUID, aSource, aNewFolder);
849 0 : NS_ENSURE_SUCCESS(rv, rv);
850 0 : return NS_OK;
851 : }
852 :
853 0 : bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
854 : {
855 0 : nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
856 0 : NS_ENSURE_TRUE(annosvc, false);
857 : bool isLivemark;
858 0 : nsresult rv = annosvc->ItemHasAnnotation(aFolderId,
859 0 : FEED_URI_ANNO,
860 0 : &isLivemark);
861 0 : NS_ENSURE_SUCCESS(rv, false);
862 0 : return isLivemark;
863 : }
864 :
865 : nsresult
866 0 : nsNavBookmarks::CreateContainerWithID(int64_t aItemId,
867 : int64_t aParent,
868 : const nsACString& aTitle,
869 : bool aIsBookmarkFolder,
870 : int32_t* aIndex,
871 : const nsACString& aGUID,
872 : uint16_t aSource,
873 : int64_t* aNewFolder)
874 : {
875 0 : NS_ENSURE_ARG_MIN(*aIndex, nsINavBookmarksService::DEFAULT_INDEX);
876 :
877 : // Get the correct index for insertion. This also ensures the parent exists.
878 : int32_t index, folderCount;
879 : int64_t grandParentId;
880 0 : nsAutoCString folderGuid;
881 0 : nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
882 0 : NS_ENSURE_SUCCESS(rv, rv);
883 :
884 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
885 :
886 0 : if (*aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
887 0 : *aIndex >= folderCount) {
888 0 : index = folderCount;
889 : } else {
890 0 : index = *aIndex;
891 : // Create space for the insertion.
892 0 : rv = AdjustIndices(aParent, index, INT32_MAX, 1);
893 0 : NS_ENSURE_SUCCESS(rv, rv);
894 : }
895 :
896 0 : *aNewFolder = aItemId;
897 0 : PRTime dateAdded = RoundedPRNow();
898 0 : nsAutoCString guid(aGUID);
899 0 : nsCString title;
900 0 : TruncateTitle(aTitle, title);
901 :
902 0 : rv = InsertBookmarkInDB(-1, FOLDER, aParent, index,
903 : title, dateAdded, 0, folderGuid, grandParentId,
904 0 : nullptr, aSource, aNewFolder, guid);
905 0 : NS_ENSURE_SUCCESS(rv, rv);
906 :
907 0 : rv = transaction.Commit();
908 0 : NS_ENSURE_SUCCESS(rv, rv);
909 :
910 0 : int64_t tagsRootId = TagsRootId();
911 :
912 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
913 : SKIP_TAGS(aParent == tagsRootId),
914 : OnItemAdded(*aNewFolder, aParent, index, FOLDER,
915 : nullptr, title, dateAdded, guid,
916 : folderGuid, aSource));
917 :
918 0 : *aIndex = index;
919 0 : return NS_OK;
920 : }
921 :
922 :
923 : NS_IMETHODIMP
924 0 : nsNavBookmarks::InsertSeparator(int64_t aParent,
925 : int32_t aIndex,
926 : const nsACString& aGUID,
927 : uint16_t aSource,
928 : int64_t* aNewItemId)
929 : {
930 0 : NS_ENSURE_ARG_MIN(aParent, 1);
931 0 : NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
932 0 : NS_ENSURE_ARG_POINTER(aNewItemId);
933 :
934 0 : if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
935 0 : return NS_ERROR_INVALID_ARG;
936 :
937 : // Get the correct index for insertion. This also ensures the parent exists.
938 : int32_t index, folderCount;
939 : int64_t grandParentId;
940 0 : nsAutoCString folderGuid;
941 0 : nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
942 0 : NS_ENSURE_SUCCESS(rv, rv);
943 :
944 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
945 :
946 0 : if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
947 0 : aIndex >= folderCount) {
948 0 : index = folderCount;
949 : }
950 : else {
951 0 : index = aIndex;
952 : // Create space for the insertion.
953 0 : rv = AdjustIndices(aParent, index, INT32_MAX, 1);
954 0 : NS_ENSURE_SUCCESS(rv, rv);
955 : }
956 :
957 0 : *aNewItemId = -1;
958 0 : nsAutoCString guid(aGUID);
959 0 : PRTime dateAdded = RoundedPRNow();
960 0 : rv = InsertBookmarkInDB(-1, SEPARATOR, aParent, index, EmptyCString(), dateAdded,
961 : 0, folderGuid, grandParentId, nullptr, aSource,
962 0 : aNewItemId, guid);
963 0 : NS_ENSURE_SUCCESS(rv, rv);
964 :
965 0 : rv = transaction.Commit();
966 0 : NS_ENSURE_SUCCESS(rv, rv);
967 :
968 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
969 : DontSkip,
970 : OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR,
971 : nullptr, EmptyCString(), dateAdded, guid,
972 : folderGuid, aSource));
973 :
974 0 : return NS_OK;
975 : }
976 :
977 :
978 : nsresult
979 0 : nsNavBookmarks::GetLastChildId(int64_t aFolderId, int64_t* aItemId)
980 : {
981 0 : NS_ASSERTION(aFolderId > 0, "Invalid folder id");
982 0 : *aItemId = -1;
983 :
984 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
985 : "SELECT id FROM moz_bookmarks WHERE parent = :parent "
986 : "ORDER BY position DESC LIMIT 1"
987 0 : );
988 0 : NS_ENSURE_STATE(stmt);
989 0 : mozStorageStatementScoper scoper(stmt);
990 :
991 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
992 0 : NS_ENSURE_SUCCESS(rv, rv);
993 : bool found;
994 0 : rv = stmt->ExecuteStep(&found);
995 0 : NS_ENSURE_SUCCESS(rv, rv);
996 0 : if (found) {
997 0 : rv = stmt->GetInt64(0, aItemId);
998 0 : NS_ENSURE_SUCCESS(rv, rv);
999 : }
1000 :
1001 0 : return NS_OK;
1002 : }
1003 :
1004 :
1005 : NS_IMETHODIMP
1006 0 : nsNavBookmarks::GetIdForItemAt(int64_t aFolder,
1007 : int32_t aIndex,
1008 : int64_t* aItemId)
1009 : {
1010 0 : NS_ENSURE_ARG_MIN(aFolder, 1);
1011 0 : NS_ENSURE_ARG_POINTER(aItemId);
1012 :
1013 0 : *aItemId = -1;
1014 :
1015 : nsresult rv;
1016 0 : if (aIndex == nsINavBookmarksService::DEFAULT_INDEX) {
1017 : // Get last item within aFolder.
1018 0 : rv = GetLastChildId(aFolder, aItemId);
1019 0 : NS_ENSURE_SUCCESS(rv, rv);
1020 : }
1021 : else {
1022 : // Get the item in aFolder with position aIndex.
1023 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1024 : "SELECT id, fk, type FROM moz_bookmarks "
1025 : "WHERE parent = :parent AND position = :item_index"
1026 0 : );
1027 0 : NS_ENSURE_STATE(stmt);
1028 0 : mozStorageStatementScoper scoper(stmt);
1029 :
1030 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolder);
1031 0 : NS_ENSURE_SUCCESS(rv, rv);
1032 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
1033 0 : NS_ENSURE_SUCCESS(rv, rv);
1034 :
1035 : bool found;
1036 0 : rv = stmt->ExecuteStep(&found);
1037 0 : NS_ENSURE_SUCCESS(rv, rv);
1038 0 : if (found) {
1039 0 : rv = stmt->GetInt64(0, aItemId);
1040 0 : NS_ENSURE_SUCCESS(rv, rv);
1041 : }
1042 : }
1043 0 : return NS_OK;
1044 : }
1045 :
1046 0 : NS_IMPL_ISUPPORTS(nsNavBookmarks::RemoveFolderTransaction, nsITransaction)
1047 :
1048 : NS_IMETHODIMP
1049 0 : nsNavBookmarks::GetRemoveFolderTransaction(int64_t aFolderId, uint16_t aSource,
1050 : nsITransaction** aResult)
1051 : {
1052 0 : NS_ENSURE_ARG_MIN(aFolderId, 1);
1053 0 : NS_ENSURE_ARG_POINTER(aResult);
1054 :
1055 : // Create and initialize a RemoveFolderTransaction object that can be used to
1056 : // recreate the folder safely later.
1057 :
1058 : RemoveFolderTransaction* rft =
1059 0 : new RemoveFolderTransaction(aFolderId, aSource);
1060 0 : if (!rft)
1061 0 : return NS_ERROR_OUT_OF_MEMORY;
1062 :
1063 0 : NS_ADDREF(*aResult = rft);
1064 0 : return NS_OK;
1065 : }
1066 :
1067 :
1068 : nsresult
1069 0 : nsNavBookmarks::GetDescendantFolders(int64_t aFolderId,
1070 : nsTArray<int64_t>& aDescendantFoldersArray) {
1071 : nsresult rv;
1072 : // New descendant folders will be added from this index on.
1073 0 : uint32_t startIndex = aDescendantFoldersArray.Length();
1074 : {
1075 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1076 : "SELECT id "
1077 : "FROM moz_bookmarks "
1078 : "WHERE parent = :parent "
1079 : "AND type = :item_type "
1080 0 : );
1081 0 : NS_ENSURE_STATE(stmt);
1082 0 : mozStorageStatementScoper scoper(stmt);
1083 :
1084 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1085 0 : NS_ENSURE_SUCCESS(rv, rv);
1086 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_FOLDER);
1087 0 : NS_ENSURE_SUCCESS(rv, rv);
1088 :
1089 0 : bool hasMore = false;
1090 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
1091 : int64_t itemId;
1092 0 : rv = stmt->GetInt64(0, &itemId);
1093 0 : NS_ENSURE_SUCCESS(rv, rv);
1094 0 : aDescendantFoldersArray.AppendElement(itemId);
1095 : }
1096 : }
1097 :
1098 : // Recursively call GetDescendantFolders for added folders.
1099 : // We start at startIndex since previous folders are checked
1100 : // by previous calls to this method.
1101 0 : uint32_t childCount = aDescendantFoldersArray.Length();
1102 0 : for (uint32_t i = startIndex; i < childCount; ++i) {
1103 0 : GetDescendantFolders(aDescendantFoldersArray[i], aDescendantFoldersArray);
1104 : }
1105 :
1106 0 : return NS_OK;
1107 : }
1108 :
1109 :
1110 : nsresult
1111 0 : nsNavBookmarks::GetDescendantChildren(int64_t aFolderId,
1112 : const nsACString& aFolderGuid,
1113 : int64_t aGrandParentId,
1114 : nsTArray<BookmarkData>& aFolderChildrenArray) {
1115 : // New children will be added from this index on.
1116 0 : uint32_t startIndex = aFolderChildrenArray.Length();
1117 : nsresult rv;
1118 : {
1119 : // Collect children informations.
1120 : // Select all children of a given folder, sorted by position.
1121 : // This is a LEFT JOIN because not all bookmarks types have a place.
1122 : // We construct a result where the first columns exactly match
1123 : // kGetInfoIndex_* order, and additionally contains columns for position,
1124 : // item_child, and folder_child from moz_bookmarks.
1125 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1126 : "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
1127 : "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
1128 : "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
1129 : "b.guid, b.position, b.type, b.fk, b.syncStatus "
1130 : "FROM moz_bookmarks b "
1131 : "LEFT JOIN moz_places h ON b.fk = h.id "
1132 : "WHERE b.parent = :parent "
1133 : "ORDER BY b.position ASC"
1134 0 : );
1135 0 : NS_ENSURE_STATE(stmt);
1136 0 : mozStorageStatementScoper scoper(stmt);
1137 :
1138 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1139 0 : NS_ENSURE_SUCCESS(rv, rv);
1140 :
1141 : bool hasMore;
1142 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
1143 0 : BookmarkData child;
1144 0 : rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
1145 0 : NS_ENSURE_SUCCESS(rv, rv);
1146 0 : child.parentId = aFolderId;
1147 0 : child.grandParentId = aGrandParentId;
1148 0 : child.parentGuid = aFolderGuid;
1149 0 : rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
1150 0 : NS_ENSURE_SUCCESS(rv, rv);
1151 0 : rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
1152 0 : NS_ENSURE_SUCCESS(rv, rv);
1153 0 : rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
1154 0 : NS_ENSURE_SUCCESS(rv, rv);
1155 0 : rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
1156 0 : NS_ENSURE_SUCCESS(rv, rv);
1157 0 : rv = stmt->GetInt32(kGetChildrenIndex_SyncStatus, &child.syncStatus);
1158 0 : NS_ENSURE_SUCCESS(rv, rv);
1159 :
1160 0 : if (child.type == TYPE_BOOKMARK) {
1161 0 : rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
1162 0 : NS_ENSURE_SUCCESS(rv, rv);
1163 : }
1164 :
1165 : // Append item to children's array.
1166 0 : aFolderChildrenArray.AppendElement(child);
1167 : }
1168 : }
1169 :
1170 : // Recursively call GetDescendantChildren for added folders.
1171 : // We start at startIndex since previous folders are checked
1172 : // by previous calls to this method.
1173 0 : uint32_t childCount = aFolderChildrenArray.Length();
1174 0 : for (uint32_t i = startIndex; i < childCount; ++i) {
1175 0 : if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
1176 : // nsTarray assumes that all children can be memmove()d, thus we can't
1177 : // just pass aFolderChildrenArray[i].guid to a method that will change
1178 : // the array itself. Otherwise, since it's passed by reference, after a
1179 : // memmove() it could point to garbage and cause intermittent crashes.
1180 0 : nsCString guid = aFolderChildrenArray[i].guid;
1181 0 : GetDescendantChildren(aFolderChildrenArray[i].id,
1182 : guid,
1183 : aFolderId,
1184 0 : aFolderChildrenArray);
1185 : }
1186 : }
1187 :
1188 0 : return NS_OK;
1189 : }
1190 :
1191 :
1192 : NS_IMETHODIMP
1193 0 : nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId, uint16_t aSource)
1194 : {
1195 0 : AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveFolderChilder", OTHER);
1196 :
1197 0 : NS_ENSURE_ARG_MIN(aFolderId, 1);
1198 0 : int64_t rootId = -1;
1199 0 : nsresult rv = GetPlacesRoot(&rootId);
1200 0 : NS_ENSURE_SUCCESS(rv, rv);
1201 0 : NS_ENSURE_ARG(aFolderId != rootId);
1202 :
1203 0 : BookmarkData folder;
1204 0 : rv = FetchItemInfo(aFolderId, folder);
1205 0 : NS_ENSURE_SUCCESS(rv, rv);
1206 0 : NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
1207 :
1208 : // Fill folder children array recursively.
1209 0 : nsTArray<BookmarkData> folderChildrenArray;
1210 0 : rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
1211 0 : folderChildrenArray);
1212 0 : NS_ENSURE_SUCCESS(rv, rv);
1213 :
1214 : // Build a string of folders whose children will be removed.
1215 0 : nsCString foldersToRemove;
1216 0 : for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
1217 0 : BookmarkData& child = folderChildrenArray[i];
1218 :
1219 0 : if (child.type == TYPE_FOLDER) {
1220 0 : foldersToRemove.Append(',');
1221 0 : foldersToRemove.AppendInt(child.id);
1222 : }
1223 : }
1224 :
1225 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1226 :
1227 : // Delete items from the database now.
1228 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
1229 :
1230 0 : nsCOMPtr<mozIStorageStatement> deleteStatement = mDB->GetStatement(
1231 0 : NS_LITERAL_CSTRING(
1232 : "DELETE FROM moz_bookmarks "
1233 0 : "WHERE parent IN (:parent") + foldersToRemove + NS_LITERAL_CSTRING(")")
1234 0 : );
1235 0 : NS_ENSURE_STATE(deleteStatement);
1236 0 : mozStorageStatementScoper deleteStatementScoper(deleteStatement);
1237 :
1238 0 : rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
1239 0 : NS_ENSURE_SUCCESS(rv, rv);
1240 0 : rv = deleteStatement->Execute();
1241 0 : NS_ENSURE_SUCCESS(rv, rv);
1242 :
1243 : // Clean up orphan items annotations.
1244 0 : nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
1245 0 : if (!conn) {
1246 0 : return NS_ERROR_UNEXPECTED;
1247 : }
1248 0 : rv = conn->ExecuteSimpleSQL(
1249 0 : NS_LITERAL_CSTRING(
1250 : "DELETE FROM moz_items_annos "
1251 : "WHERE id IN ("
1252 : "SELECT a.id from moz_items_annos a "
1253 : "LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
1254 0 : "WHERE b.id ISNULL)"));
1255 0 : NS_ENSURE_SUCCESS(rv, rv);
1256 :
1257 : // Set the lastModified date.
1258 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, folder.id,
1259 0 : RoundedPRNow());
1260 0 : NS_ENSURE_SUCCESS(rv, rv);
1261 :
1262 0 : int64_t tagsRootId = TagsRootId();
1263 :
1264 0 : if (syncChangeDelta) {
1265 0 : nsTArray<TombstoneData> tombstones(folderChildrenArray.Length());
1266 0 : PRTime dateRemoved = RoundedPRNow();
1267 :
1268 0 : for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
1269 0 : BookmarkData& child = folderChildrenArray[i];
1270 0 : if (NeedsTombstone(child)) {
1271 : // Write tombstones for synced children.
1272 0 : TombstoneData childTombstone = {child.guid, dateRemoved};
1273 0 : tombstones.AppendElement(childTombstone);
1274 : }
1275 0 : bool isUntagging = child.grandParentId == tagsRootId;
1276 0 : if (isUntagging) {
1277 : // Bump the change counter for all tagged bookmarks when removing a tag
1278 : // folder.
1279 0 : rv = AddSyncChangesForBookmarksWithURL(child.url, syncChangeDelta);
1280 0 : NS_ENSURE_SUCCESS(rv, rv);
1281 : }
1282 : }
1283 :
1284 0 : rv = InsertTombstones(tombstones);
1285 0 : NS_ENSURE_SUCCESS(rv, rv);
1286 : }
1287 :
1288 0 : rv = transaction.Commit();
1289 0 : NS_ENSURE_SUCCESS(rv, rv);
1290 :
1291 : // Call observers in reverse order to serve children before their parent.
1292 0 : for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
1293 0 : BookmarkData& child = folderChildrenArray[i];
1294 :
1295 0 : nsCOMPtr<nsIURI> uri;
1296 0 : if (child.type == TYPE_BOOKMARK) {
1297 : // If not a tag, recalculate frecency for this entry, since it changed.
1298 0 : if (child.grandParentId != tagsRootId) {
1299 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
1300 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1301 0 : rv = history->UpdateFrecency(child.placeId);
1302 0 : NS_ENSURE_SUCCESS(rv, rv);
1303 : }
1304 : // A broken url should not interrupt the removal process.
1305 0 : (void)NS_NewURI(getter_AddRefs(uri), child.url);
1306 : // We cannot assert since some automated tests are checking this path.
1307 0 : NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
1308 : }
1309 :
1310 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1311 : ((child.grandParentId == tagsRootId) ? SkipTags : SkipDescendants),
1312 : OnItemRemoved(child.id,
1313 : child.parentId,
1314 : child.position,
1315 : child.type,
1316 : uri,
1317 : child.guid,
1318 : child.parentGuid,
1319 : aSource));
1320 :
1321 0 : if (child.type == TYPE_BOOKMARK && child.grandParentId == tagsRootId &&
1322 0 : uri) {
1323 : // If the removed bookmark was a child of a tag container, notify all
1324 : // bookmark-folder result nodes which contain a bookmark for the removed
1325 : // bookmark's url.
1326 0 : nsTArray<BookmarkData> bookmarks;
1327 0 : rv = GetBookmarksForURI(uri, bookmarks);
1328 0 : NS_ENSURE_SUCCESS(rv, rv);
1329 :
1330 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
1331 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1332 : DontSkip,
1333 : OnItemChanged(bookmarks[i].id,
1334 : NS_LITERAL_CSTRING("tags"),
1335 : false,
1336 : EmptyCString(),
1337 : bookmarks[i].lastModified,
1338 : TYPE_BOOKMARK,
1339 : bookmarks[i].parentId,
1340 : bookmarks[i].guid,
1341 : bookmarks[i].parentGuid,
1342 : EmptyCString(),
1343 : aSource));
1344 : }
1345 : }
1346 : }
1347 :
1348 0 : return NS_OK;
1349 : }
1350 :
1351 :
1352 : NS_IMETHODIMP
1353 0 : nsNavBookmarks::MoveItem(int64_t aItemId,
1354 : int64_t aNewParent,
1355 : int32_t aIndex,
1356 : uint16_t aSource)
1357 : {
1358 0 : NS_ENSURE_ARG(!IsRoot(aItemId));
1359 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1360 0 : NS_ENSURE_ARG_MIN(aNewParent, 1);
1361 : // -1 is append, but no other negative number is allowed.
1362 0 : NS_ENSURE_ARG_MIN(aIndex, -1);
1363 : // Disallow making an item its own parent.
1364 0 : NS_ENSURE_ARG(aItemId != aNewParent);
1365 :
1366 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
1367 :
1368 0 : BookmarkData bookmark;
1369 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1370 0 : NS_ENSURE_SUCCESS(rv, rv);
1371 :
1372 : // if parent and index are the same, nothing to do
1373 0 : if (bookmark.parentId == aNewParent && bookmark.position == aIndex)
1374 0 : return NS_OK;
1375 :
1376 : // Make sure aNewParent is not aFolder or a subfolder of aFolder.
1377 : // TODO: make this performant, maybe with a nested tree (bug 408991).
1378 0 : if (bookmark.type == TYPE_FOLDER) {
1379 0 : int64_t ancestorId = aNewParent;
1380 :
1381 0 : while (ancestorId) {
1382 0 : if (ancestorId == bookmark.id) {
1383 0 : return NS_ERROR_INVALID_ARG;
1384 : }
1385 0 : rv = GetFolderIdForItem(ancestorId, &ancestorId);
1386 0 : if (NS_FAILED(rv)) {
1387 0 : break;
1388 : }
1389 : }
1390 : }
1391 :
1392 : // calculate new index
1393 : int32_t newIndex, folderCount;
1394 : int64_t grandParentId;
1395 0 : nsAutoCString newParentGuid;
1396 0 : rv = FetchFolderInfo(aNewParent, &folderCount, newParentGuid, &grandParentId);
1397 0 : NS_ENSURE_SUCCESS(rv, rv);
1398 0 : if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
1399 0 : aIndex >= folderCount) {
1400 0 : newIndex = folderCount;
1401 : // If the parent remains the same, then the folder is really being moved
1402 : // to count - 1 (since it's being removed from the old position)
1403 0 : if (bookmark.parentId == aNewParent) {
1404 0 : --newIndex;
1405 : }
1406 : } else {
1407 0 : newIndex = aIndex;
1408 :
1409 0 : if (bookmark.parentId == aNewParent && newIndex > bookmark.position) {
1410 : // when an item is being moved lower in the same folder, the new index
1411 : // refers to the index before it was removed. Removal causes everything
1412 : // to shift up.
1413 0 : --newIndex;
1414 : }
1415 : }
1416 :
1417 : // this is like the previous check, except this covers if
1418 : // the specified index was -1 (append), and the calculated
1419 : // new index is the same as the existing index
1420 0 : if (aNewParent == bookmark.parentId && newIndex == bookmark.position) {
1421 : // Nothing to do!
1422 0 : return NS_OK;
1423 : }
1424 :
1425 : // adjust indices to account for the move
1426 : // do this before we update the parent/index fields
1427 : // or we'll re-adjust the index for the item we are moving
1428 0 : bool sameParent = bookmark.parentId == aNewParent;
1429 0 : if (sameParent) {
1430 : // We can optimize the updates if moving within the same container.
1431 : // We only shift the items between the old and new positions, since the
1432 : // insertion will offset the deletion.
1433 0 : if (bookmark.position > newIndex) {
1434 0 : rv = AdjustIndices(bookmark.parentId, newIndex, bookmark.position - 1, 1);
1435 : }
1436 : else {
1437 0 : rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, newIndex, -1);
1438 : }
1439 0 : NS_ENSURE_SUCCESS(rv, rv);
1440 : }
1441 : else {
1442 : // We're moving between containers, so this happens in two steps.
1443 : // First, fill the hole from the removal from the old parent.
1444 0 : rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
1445 0 : NS_ENSURE_SUCCESS(rv, rv);
1446 : // Now, make room in the new parent for the insertion.
1447 0 : rv = AdjustIndices(aNewParent, newIndex, INT32_MAX, 1);
1448 0 : NS_ENSURE_SUCCESS(rv, rv);
1449 : }
1450 :
1451 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1452 :
1453 : {
1454 : // Update parent and position.
1455 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1456 : "UPDATE moz_bookmarks SET parent = :parent, position = :item_index "
1457 : "WHERE id = :item_id "
1458 0 : );
1459 0 : NS_ENSURE_STATE(stmt);
1460 0 : mozStorageStatementScoper scoper(stmt);
1461 :
1462 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aNewParent);
1463 0 : NS_ENSURE_SUCCESS(rv, rv);
1464 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), newIndex);
1465 0 : NS_ENSURE_SUCCESS(rv, rv);
1466 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
1467 0 : NS_ENSURE_SUCCESS(rv, rv);
1468 0 : rv = stmt->Execute();
1469 0 : NS_ENSURE_SUCCESS(rv, rv);
1470 : }
1471 :
1472 0 : PRTime now = RoundedPRNow();
1473 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta,
1474 0 : bookmark.parentId, now);
1475 0 : NS_ENSURE_SUCCESS(rv, rv);
1476 0 : if (sameParent) {
1477 : // If we're moving within the same container, only the parent needs a sync
1478 : // change. Update the item's last modified date without bumping its counter.
1479 0 : rv = SetItemDateInternal(LAST_MODIFIED, 0, bookmark.id, now);
1480 0 : NS_ENSURE_SUCCESS(rv, rv);
1481 :
1482 : // Mark all affected separators as changed
1483 0 : int32_t startIndex = std::min(bookmark.position, newIndex);
1484 0 : rv = AdjustSeparatorsSyncCounter(bookmark.parentId, startIndex, syncChangeDelta);
1485 0 : NS_ENSURE_SUCCESS(rv, rv);
1486 : } else {
1487 : // Otherwise, if we're moving between containers, both parents and the child
1488 : // need sync changes.
1489 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, aNewParent, now);
1490 0 : NS_ENSURE_SUCCESS(rv, rv);
1491 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id, now);
1492 0 : NS_ENSURE_SUCCESS(rv, rv);
1493 :
1494 : // Mark all affected separators as changed
1495 0 : rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position, syncChangeDelta);
1496 0 : NS_ENSURE_SUCCESS(rv, rv);
1497 0 : rv = AdjustSeparatorsSyncCounter(aNewParent, newIndex, syncChangeDelta);
1498 0 : NS_ENSURE_SUCCESS(rv, rv);
1499 : }
1500 :
1501 0 : int64_t tagsRootId = TagsRootId();
1502 0 : bool isChangingTagFolder = bookmark.parentId == tagsRootId;
1503 0 : if (isChangingTagFolder) {
1504 : // Moving a tag folder out of the tags root untags all its bookmarks. This
1505 : // is an odd case, but the tagging service adds an observer to handle it,
1506 : // so we bump the change counter for each untagged item for consistency.
1507 0 : rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
1508 0 : NS_ENSURE_SUCCESS(rv, rv);
1509 : }
1510 :
1511 0 : rv = PreventSyncReparenting(bookmark);
1512 0 : NS_ENSURE_SUCCESS(rv, rv);
1513 :
1514 0 : rv = transaction.Commit();
1515 0 : NS_ENSURE_SUCCESS(rv, rv);
1516 :
1517 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1518 : nsINavBookmarkObserver,
1519 : OnItemMoved(bookmark.id,
1520 : bookmark.parentId,
1521 : bookmark.position,
1522 : aNewParent,
1523 : newIndex,
1524 : bookmark.type,
1525 : bookmark.guid,
1526 : bookmark.parentGuid,
1527 : newParentGuid,
1528 : aSource));
1529 0 : return NS_OK;
1530 : }
1531 :
1532 : nsresult
1533 0 : nsNavBookmarks::FetchItemInfo(int64_t aItemId,
1534 : BookmarkData& _bookmark)
1535 : {
1536 : // LEFT JOIN since not all bookmarks have an associated place.
1537 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1538 : "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
1539 : "b.dateAdded, b.lastModified, b.guid, t.guid, t.parent, "
1540 : "b.syncStatus "
1541 : "FROM moz_bookmarks b "
1542 : "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
1543 : "LEFT JOIN moz_places h ON h.id = b.fk "
1544 : "WHERE b.id = :item_id"
1545 0 : );
1546 0 : NS_ENSURE_STATE(stmt);
1547 0 : mozStorageStatementScoper scoper(stmt);
1548 :
1549 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1550 0 : NS_ENSURE_SUCCESS(rv, rv);
1551 :
1552 : bool hasResult;
1553 0 : rv = stmt->ExecuteStep(&hasResult);
1554 0 : NS_ENSURE_SUCCESS(rv, rv);
1555 0 : if (!hasResult) {
1556 0 : return NS_ERROR_INVALID_ARG;
1557 : }
1558 :
1559 0 : _bookmark.id = aItemId;
1560 0 : rv = stmt->GetUTF8String(1, _bookmark.url);
1561 0 : NS_ENSURE_SUCCESS(rv, rv);
1562 :
1563 : bool isNull;
1564 0 : rv = stmt->GetIsNull(2, &isNull);
1565 0 : NS_ENSURE_SUCCESS(rv, rv);
1566 0 : if (!isNull) {
1567 0 : rv = stmt->GetUTF8String(2, _bookmark.title);
1568 0 : NS_ENSURE_SUCCESS(rv, rv);
1569 : }
1570 0 : rv = stmt->GetInt32(3, &_bookmark.position);
1571 0 : NS_ENSURE_SUCCESS(rv, rv);
1572 0 : rv = stmt->GetInt64(4, &_bookmark.placeId);
1573 0 : NS_ENSURE_SUCCESS(rv, rv);
1574 0 : rv = stmt->GetInt64(5, &_bookmark.parentId);
1575 0 : NS_ENSURE_SUCCESS(rv, rv);
1576 0 : rv = stmt->GetInt32(6, &_bookmark.type);
1577 0 : NS_ENSURE_SUCCESS(rv, rv);
1578 0 : rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
1579 0 : NS_ENSURE_SUCCESS(rv, rv);
1580 0 : rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
1581 0 : NS_ENSURE_SUCCESS(rv, rv);
1582 0 : rv = stmt->GetUTF8String(9, _bookmark.guid);
1583 0 : NS_ENSURE_SUCCESS(rv, rv);
1584 : // Getting properties of the root would show no parent.
1585 0 : rv = stmt->GetIsNull(10, &isNull);
1586 0 : NS_ENSURE_SUCCESS(rv, rv);
1587 0 : if (!isNull) {
1588 0 : rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
1589 0 : NS_ENSURE_SUCCESS(rv, rv);
1590 0 : rv = stmt->GetInt64(11, &_bookmark.grandParentId);
1591 0 : NS_ENSURE_SUCCESS(rv, rv);
1592 : }
1593 : else {
1594 0 : _bookmark.grandParentId = -1;
1595 : }
1596 0 : rv = stmt->GetInt32(12, &_bookmark.syncStatus);
1597 0 : NS_ENSURE_SUCCESS(rv, rv);
1598 :
1599 0 : return NS_OK;
1600 : }
1601 :
1602 : nsresult
1603 0 : nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
1604 : int64_t aSyncChangeDelta,
1605 : int64_t aItemId,
1606 : PRTime aValue)
1607 : {
1608 0 : aValue = RoundToMilliseconds(aValue);
1609 :
1610 0 : nsCOMPtr<mozIStorageStatement> stmt;
1611 0 : if (aDateType == DATE_ADDED) {
1612 : // lastModified is set to the same value as dateAdded. We do this for
1613 : // performance reasons, since it will allow us to use an index to sort items
1614 : // by date.
1615 0 : stmt = mDB->GetStatement(
1616 : "UPDATE moz_bookmarks SET dateAdded = :date, lastModified = :date, "
1617 : "syncChangeCounter = syncChangeCounter + :delta "
1618 : "WHERE id = :item_id"
1619 0 : );
1620 : }
1621 : else {
1622 0 : stmt = mDB->GetStatement(
1623 : "UPDATE moz_bookmarks SET lastModified = :date, "
1624 : "syncChangeCounter = syncChangeCounter + :delta "
1625 : "WHERE id = :item_id"
1626 0 : );
1627 : }
1628 0 : NS_ENSURE_STATE(stmt);
1629 0 : mozStorageStatementScoper scoper(stmt);
1630 :
1631 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
1632 0 : NS_ENSURE_SUCCESS(rv, rv);
1633 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1634 0 : NS_ENSURE_SUCCESS(rv, rv);
1635 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1636 0 : NS_ENSURE_SUCCESS(rv, rv);
1637 :
1638 0 : rv = stmt->Execute();
1639 0 : NS_ENSURE_SUCCESS(rv, rv);
1640 :
1641 : // note, we are not notifying the observers
1642 : // that the item has changed.
1643 :
1644 0 : return NS_OK;
1645 : }
1646 :
1647 :
1648 : NS_IMETHODIMP
1649 0 : nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded,
1650 : uint16_t aSource)
1651 : {
1652 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1653 :
1654 0 : BookmarkData bookmark;
1655 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1656 0 : NS_ENSURE_SUCCESS(rv, rv);
1657 0 : int64_t tagsRootId = TagsRootId();
1658 0 : bool isTagging = bookmark.grandParentId == tagsRootId;
1659 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1660 :
1661 : // Round here so that we notify with the right value.
1662 0 : bookmark.dateAdded = RoundToMilliseconds(aDateAdded);
1663 :
1664 0 : if (isTagging) {
1665 : // If we're changing a tag, bump the change counter for all tagged
1666 : // bookmarks. We use a separate code path to avoid a transaction for
1667 : // non-tags.
1668 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
1669 :
1670 0 : rv = SetItemDateInternal(DATE_ADDED, syncChangeDelta, bookmark.id,
1671 0 : bookmark.dateAdded);
1672 0 : NS_ENSURE_SUCCESS(rv, rv);
1673 :
1674 0 : rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
1675 0 : NS_ENSURE_SUCCESS(rv, rv);
1676 :
1677 0 : rv = transaction.Commit();
1678 0 : NS_ENSURE_SUCCESS(rv, rv);
1679 : } else {
1680 0 : rv = SetItemDateInternal(DATE_ADDED, syncChangeDelta, bookmark.id,
1681 0 : bookmark.dateAdded);
1682 0 : NS_ENSURE_SUCCESS(rv, rv);
1683 : }
1684 :
1685 : // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
1686 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1687 : nsINavBookmarkObserver,
1688 : OnItemChanged(bookmark.id,
1689 : NS_LITERAL_CSTRING("dateAdded"),
1690 : false,
1691 : nsPrintfCString("%" PRId64, bookmark.dateAdded),
1692 : bookmark.dateAdded,
1693 : bookmark.type,
1694 : bookmark.parentId,
1695 : bookmark.guid,
1696 : bookmark.parentGuid,
1697 : EmptyCString(),
1698 : aSource));
1699 0 : return NS_OK;
1700 : }
1701 :
1702 :
1703 : NS_IMETHODIMP
1704 0 : nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
1705 : {
1706 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1707 0 : NS_ENSURE_ARG_POINTER(_dateAdded);
1708 :
1709 0 : BookmarkData bookmark;
1710 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1711 0 : NS_ENSURE_SUCCESS(rv, rv);
1712 :
1713 0 : *_dateAdded = bookmark.dateAdded;
1714 0 : return NS_OK;
1715 : }
1716 :
1717 :
1718 : NS_IMETHODIMP
1719 0 : nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
1720 : uint16_t aSource)
1721 : {
1722 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1723 :
1724 0 : BookmarkData bookmark;
1725 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1726 0 : NS_ENSURE_SUCCESS(rv, rv);
1727 :
1728 0 : int64_t tagsRootId = TagsRootId();
1729 0 : bool isTagging = bookmark.grandParentId == tagsRootId;
1730 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1731 :
1732 : // Round here so that we notify with the right value.
1733 0 : bookmark.lastModified = RoundToMilliseconds(aLastModified);
1734 :
1735 0 : if (isTagging) {
1736 : // If we're changing a tag, bump the change counter for all tagged
1737 : // bookmarks. We use a separate code path to avoid a transaction for
1738 : // non-tags.
1739 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
1740 :
1741 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1742 0 : bookmark.lastModified);
1743 0 : NS_ENSURE_SUCCESS(rv, rv);
1744 :
1745 0 : rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
1746 0 : NS_ENSURE_SUCCESS(rv, rv);
1747 :
1748 0 : rv = transaction.Commit();
1749 0 : NS_ENSURE_SUCCESS(rv, rv);
1750 : } else {
1751 0 : rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1752 0 : bookmark.lastModified);
1753 0 : NS_ENSURE_SUCCESS(rv, rv);
1754 : }
1755 :
1756 : // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
1757 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1758 : nsINavBookmarkObserver,
1759 : OnItemChanged(bookmark.id,
1760 : NS_LITERAL_CSTRING("lastModified"),
1761 : false,
1762 : nsPrintfCString("%" PRId64, bookmark.lastModified),
1763 : bookmark.lastModified,
1764 : bookmark.type,
1765 : bookmark.parentId,
1766 : bookmark.guid,
1767 : bookmark.parentGuid,
1768 : EmptyCString(),
1769 : aSource));
1770 0 : return NS_OK;
1771 : }
1772 :
1773 :
1774 : NS_IMETHODIMP
1775 0 : nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
1776 : {
1777 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1778 0 : NS_ENSURE_ARG_POINTER(_lastModified);
1779 :
1780 0 : BookmarkData bookmark;
1781 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1782 0 : NS_ENSURE_SUCCESS(rv, rv);
1783 :
1784 0 : *_lastModified = bookmark.lastModified;
1785 0 : return NS_OK;
1786 : }
1787 :
1788 :
1789 : nsresult
1790 0 : nsNavBookmarks::AddSyncChangesForBookmarksWithURL(const nsACString& aURL,
1791 : int64_t aSyncChangeDelta)
1792 : {
1793 0 : if (!aSyncChangeDelta) {
1794 0 : return NS_OK;
1795 : }
1796 0 : nsCOMPtr<nsIURI> uri;
1797 0 : nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
1798 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1799 : // Ignore sync changes for invalid URLs.
1800 0 : return NS_OK;
1801 : }
1802 0 : return AddSyncChangesForBookmarksWithURI(uri, aSyncChangeDelta);
1803 : }
1804 :
1805 :
1806 : nsresult
1807 0 : nsNavBookmarks::AddSyncChangesForBookmarksWithURI(nsIURI* aURI,
1808 : int64_t aSyncChangeDelta)
1809 : {
1810 0 : if (NS_WARN_IF(!aURI) || !aSyncChangeDelta) {
1811 : // Ignore sync changes for invalid URIs.
1812 0 : return NS_OK;
1813 : }
1814 :
1815 0 : nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1816 : "UPDATE moz_bookmarks SET "
1817 : "syncChangeCounter = syncChangeCounter + :delta "
1818 : "WHERE type = :type AND "
1819 : "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND "
1820 : "url = :url)"
1821 0 : );
1822 0 : NS_ENSURE_STATE(statement);
1823 0 : mozStorageStatementScoper scoper(statement);
1824 :
1825 0 : nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
1826 0 : aSyncChangeDelta);
1827 0 : NS_ENSURE_SUCCESS(rv, rv);
1828 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1829 0 : nsINavBookmarksService::TYPE_BOOKMARK);
1830 0 : NS_ENSURE_SUCCESS(rv, rv);
1831 0 : rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("url"), aURI);
1832 0 : NS_ENSURE_SUCCESS(rv, rv);
1833 :
1834 0 : return statement->Execute();
1835 : }
1836 :
1837 :
1838 : nsresult
1839 0 : nsNavBookmarks::AddSyncChangesForBookmarksInFolder(int64_t aFolderId,
1840 : int64_t aSyncChangeDelta)
1841 : {
1842 0 : if (!aSyncChangeDelta) {
1843 0 : return NS_OK;
1844 : }
1845 :
1846 0 : nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1847 : "UPDATE moz_bookmarks SET "
1848 : "syncChangeCounter = syncChangeCounter + :delta "
1849 : "WHERE type = :type AND "
1850 : "fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)"
1851 0 : );
1852 0 : NS_ENSURE_STATE(statement);
1853 0 : mozStorageStatementScoper scoper(statement);
1854 :
1855 0 : nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
1856 0 : aSyncChangeDelta);
1857 0 : NS_ENSURE_SUCCESS(rv, rv);
1858 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1859 0 : nsINavBookmarksService::TYPE_BOOKMARK);
1860 0 : NS_ENSURE_SUCCESS(rv, rv);
1861 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1862 0 : NS_ENSURE_SUCCESS(rv, rv);
1863 :
1864 0 : rv = statement->Execute();
1865 0 : NS_ENSURE_SUCCESS(rv, rv);
1866 :
1867 0 : return NS_OK;
1868 : }
1869 :
1870 :
1871 : nsresult
1872 0 : nsNavBookmarks::InsertTombstone(const BookmarkData& aBookmark)
1873 : {
1874 0 : if (!NeedsTombstone(aBookmark)) {
1875 0 : return NS_OK;
1876 : }
1877 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1878 : "INSERT INTO moz_bookmarks_deleted (guid, dateRemoved) "
1879 : "VALUES (:guid, :date_removed)"
1880 0 : );
1881 0 : NS_ENSURE_STATE(stmt);
1882 0 : mozStorageStatementScoper scoper(stmt);
1883 :
1884 0 : nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
1885 0 : aBookmark.guid);
1886 0 : NS_ENSURE_SUCCESS(rv, rv);
1887 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_removed"),
1888 0 : RoundedPRNow());
1889 0 : NS_ENSURE_SUCCESS(rv, rv);
1890 :
1891 0 : rv = stmt->Execute();
1892 0 : NS_ENSURE_SUCCESS(rv, rv);
1893 :
1894 0 : return NS_OK;
1895 : }
1896 :
1897 :
1898 : nsresult
1899 0 : nsNavBookmarks::InsertTombstones(const nsTArray<TombstoneData>& aTombstones)
1900 : {
1901 0 : if (aTombstones.IsEmpty()) {
1902 0 : return NS_OK;
1903 : }
1904 :
1905 0 : size_t maxRowsPerChunk = SQLITE_MAX_VARIABLE_NUMBER / 2;
1906 0 : for (uint32_t startIndex = 0; startIndex < aTombstones.Length(); startIndex += maxRowsPerChunk) {
1907 0 : size_t rowsPerChunk = std::min(maxRowsPerChunk, aTombstones.Length() - startIndex);
1908 :
1909 : // Build a query to insert all tombstones in a single statement, chunking to
1910 : // avoid the SQLite bound parameter limit.
1911 0 : nsAutoCString tombstonesToInsert;
1912 0 : tombstonesToInsert.AppendLiteral("VALUES (?, ?)");
1913 0 : for (uint32_t i = 1; i < rowsPerChunk; ++i) {
1914 0 : tombstonesToInsert.AppendLiteral(", (?, ?)");
1915 : }
1916 : #ifdef DEBUG
1917 0 : MOZ_ASSERT(tombstonesToInsert.CountChar('?') == rowsPerChunk * 2,
1918 : "Expected one binding param per column for each tombstone");
1919 : #endif
1920 :
1921 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1922 0 : NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks_deleted "
1923 0 : "(guid, dateRemoved) ") +
1924 0 : tombstonesToInsert
1925 0 : );
1926 0 : NS_ENSURE_STATE(stmt);
1927 0 : mozStorageStatementScoper scoper(stmt);
1928 :
1929 0 : uint32_t paramIndex = 0;
1930 : nsresult rv;
1931 0 : for (uint32_t i = 0; i < rowsPerChunk; ++i) {
1932 0 : const TombstoneData& tombstone = aTombstones[startIndex + i];
1933 0 : rv = stmt->BindUTF8StringByIndex(paramIndex++, tombstone.guid);
1934 0 : NS_ENSURE_SUCCESS(rv, rv);
1935 0 : rv = stmt->BindInt64ByIndex(paramIndex++, tombstone.dateRemoved);
1936 0 : NS_ENSURE_SUCCESS(rv, rv);
1937 : }
1938 :
1939 0 : rv = stmt->Execute();
1940 0 : NS_ENSURE_SUCCESS(rv, rv);
1941 : }
1942 :
1943 0 : return NS_OK;
1944 : }
1945 :
1946 :
1947 : nsresult
1948 0 : nsNavBookmarks::RemoveTombstone(const nsACString& aGUID)
1949 : {
1950 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1951 : "DELETE FROM moz_bookmarks_deleted WHERE guid = :guid"
1952 0 : );
1953 0 : NS_ENSURE_STATE(stmt);
1954 0 : mozStorageStatementScoper scoper(stmt);
1955 :
1956 0 : nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1957 0 : NS_ENSURE_SUCCESS(rv, rv);
1958 :
1959 0 : return stmt->Execute();
1960 : }
1961 :
1962 :
1963 : nsresult
1964 0 : nsNavBookmarks::PreventSyncReparenting(const BookmarkData& aBookmark)
1965 : {
1966 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1967 : "DELETE FROM moz_items_annos WHERE "
1968 : "item_id = :item_id AND "
1969 : "anno_attribute_id = (SELECT id FROM moz_anno_attributes "
1970 : "WHERE name = :orphan_anno)"
1971 0 : );
1972 0 : NS_ENSURE_STATE(stmt);
1973 0 : mozStorageStatementScoper scoper(stmt);
1974 :
1975 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmark.id);
1976 0 : NS_ENSURE_SUCCESS(rv, rv);
1977 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("orphan_anno"),
1978 0 : NS_LITERAL_CSTRING(SYNC_PARENT_ANNO));
1979 0 : NS_ENSURE_SUCCESS(rv, rv);
1980 :
1981 0 : rv = stmt->Execute();
1982 0 : NS_ENSURE_SUCCESS(rv, rv);
1983 :
1984 0 : return NS_OK;
1985 : }
1986 :
1987 :
1988 : NS_IMETHODIMP
1989 0 : nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
1990 : uint16_t aSource)
1991 : {
1992 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
1993 :
1994 0 : BookmarkData bookmark;
1995 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
1996 0 : NS_ENSURE_SUCCESS(rv, rv);
1997 :
1998 0 : int64_t tagsRootId = TagsRootId();
1999 0 : bool isChangingTagFolder = bookmark.parentId == tagsRootId;
2000 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
2001 :
2002 0 : nsAutoCString title;
2003 0 : TruncateTitle(aTitle, title);
2004 :
2005 0 : if (isChangingTagFolder) {
2006 : // If we're changing the title of a tag folder, bump the change counter
2007 : // for all tagged bookmarks. We use a separate code path to avoid a
2008 : // transaction for non-tags.
2009 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
2010 :
2011 0 : rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
2012 0 : NS_ENSURE_SUCCESS(rv, rv);
2013 :
2014 0 : rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
2015 0 : NS_ENSURE_SUCCESS(rv, rv);
2016 :
2017 0 : rv = transaction.Commit();
2018 0 : NS_ENSURE_SUCCESS(rv, rv);
2019 : } else {
2020 0 : rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
2021 0 : NS_ENSURE_SUCCESS(rv, rv);
2022 : }
2023 :
2024 0 : NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2025 : SKIP_TAGS(isChangingTagFolder),
2026 : OnItemChanged(bookmark.id,
2027 : NS_LITERAL_CSTRING("title"),
2028 : false,
2029 : title,
2030 : bookmark.lastModified,
2031 : bookmark.type,
2032 : bookmark.parentId,
2033 : bookmark.guid,
2034 : bookmark.parentGuid,
2035 : EmptyCString(),
2036 : aSource));
2037 0 : return NS_OK;
2038 : }
2039 :
2040 :
2041 : nsresult
2042 0 : nsNavBookmarks::SetItemTitleInternal(BookmarkData& aBookmark,
2043 : const nsACString& aTitle,
2044 : int64_t aSyncChangeDelta)
2045 : {
2046 0 : nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
2047 : "UPDATE moz_bookmarks SET "
2048 : "title = :item_title, lastModified = :date, "
2049 : "syncChangeCounter = syncChangeCounter + :delta "
2050 : "WHERE id = :item_id"
2051 0 : );
2052 0 : NS_ENSURE_STATE(statement);
2053 0 : mozStorageStatementScoper scoper(statement);
2054 :
2055 : nsresult rv;
2056 0 : if (aTitle.IsEmpty()) {
2057 0 : rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
2058 : }
2059 : else {
2060 0 : rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
2061 0 : aTitle);
2062 : }
2063 0 : NS_ENSURE_SUCCESS(rv, rv);
2064 0 : aBookmark.lastModified = RoundToMilliseconds(RoundedPRNow());
2065 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
2066 0 : aBookmark.lastModified);
2067 0 : NS_ENSURE_SUCCESS(rv, rv);
2068 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmark.id);
2069 0 : NS_ENSURE_SUCCESS(rv, rv);
2070 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
2071 0 : aSyncChangeDelta);
2072 0 : NS_ENSURE_SUCCESS(rv, rv);
2073 :
2074 0 : rv = statement->Execute();
2075 0 : NS_ENSURE_SUCCESS(rv, rv);
2076 :
2077 0 : return NS_OK;
2078 : }
2079 :
2080 :
2081 : NS_IMETHODIMP
2082 0 : nsNavBookmarks::GetItemTitle(int64_t aItemId,
2083 : nsACString& _title)
2084 : {
2085 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2086 :
2087 0 : BookmarkData bookmark;
2088 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2089 0 : NS_ENSURE_SUCCESS(rv, rv);
2090 :
2091 0 : _title = bookmark.title;
2092 0 : return NS_OK;
2093 : }
2094 :
2095 :
2096 : NS_IMETHODIMP
2097 0 : nsNavBookmarks::GetBookmarkURI(int64_t aItemId,
2098 : nsIURI** _URI)
2099 : {
2100 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2101 0 : NS_ENSURE_ARG_POINTER(_URI);
2102 :
2103 0 : BookmarkData bookmark;
2104 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2105 0 : NS_ENSURE_SUCCESS(rv, rv);
2106 :
2107 0 : rv = NS_NewURI(_URI, bookmark.url);
2108 0 : NS_ENSURE_SUCCESS(rv, rv);
2109 :
2110 0 : return NS_OK;
2111 : }
2112 :
2113 :
2114 : NS_IMETHODIMP
2115 0 : nsNavBookmarks::GetItemType(int64_t aItemId, uint16_t* _type)
2116 : {
2117 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2118 0 : NS_ENSURE_ARG_POINTER(_type);
2119 :
2120 0 : BookmarkData bookmark;
2121 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2122 0 : NS_ENSURE_SUCCESS(rv, rv);
2123 :
2124 0 : *_type = static_cast<uint16_t>(bookmark.type);
2125 0 : return NS_OK;
2126 : }
2127 :
2128 :
2129 : nsresult
2130 0 : nsNavBookmarks::ResultNodeForContainer(int64_t aItemId,
2131 : nsNavHistoryQueryOptions* aOptions,
2132 : nsNavHistoryResultNode** aNode)
2133 : {
2134 0 : BookmarkData bookmark;
2135 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2136 0 : NS_ENSURE_SUCCESS(rv, rv);
2137 :
2138 0 : if (bookmark.type == TYPE_FOLDER) { // TYPE_FOLDER
2139 0 : *aNode = new nsNavHistoryFolderResultNode(bookmark.title,
2140 : aOptions,
2141 0 : bookmark.id);
2142 : }
2143 : else {
2144 0 : return NS_ERROR_INVALID_ARG;
2145 : }
2146 :
2147 0 : (*aNode)->mDateAdded = bookmark.dateAdded;
2148 0 : (*aNode)->mLastModified = bookmark.lastModified;
2149 0 : (*aNode)->mBookmarkGuid = bookmark.guid;
2150 0 : (*aNode)->GetAsFolder()->mTargetFolderGuid = bookmark.guid;
2151 :
2152 0 : NS_ADDREF(*aNode);
2153 0 : return NS_OK;
2154 : }
2155 :
2156 :
2157 : nsresult
2158 0 : nsNavBookmarks::QueryFolderChildren(
2159 : int64_t aFolderId,
2160 : nsNavHistoryQueryOptions* aOptions,
2161 : nsCOMArray<nsNavHistoryResultNode>* aChildren)
2162 : {
2163 0 : NS_ENSURE_ARG_POINTER(aOptions);
2164 0 : NS_ENSURE_ARG_POINTER(aChildren);
2165 :
2166 : // Select all children of a given folder, sorted by position.
2167 : // This is a LEFT JOIN because not all bookmarks types have a place.
2168 : // We construct a result where the first columns exactly match those returned
2169 : // by mDBGetURLPageInfo, and additionally contains columns for position,
2170 : // item_child, and folder_child from moz_bookmarks.
2171 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2172 : "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
2173 : "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
2174 : "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
2175 : "b.guid, b.position, b.type, b.fk "
2176 : "FROM moz_bookmarks b "
2177 : "LEFT JOIN moz_places h ON b.fk = h.id "
2178 : "WHERE b.parent = :parent "
2179 : "ORDER BY b.position ASC"
2180 0 : );
2181 0 : NS_ENSURE_STATE(stmt);
2182 0 : mozStorageStatementScoper scoper(stmt);
2183 :
2184 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
2185 0 : NS_ENSURE_SUCCESS(rv, rv);
2186 :
2187 0 : nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
2188 0 : NS_ENSURE_SUCCESS(rv, rv);
2189 :
2190 0 : int32_t index = -1;
2191 : bool hasResult;
2192 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2193 0 : rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
2194 0 : NS_ENSURE_SUCCESS(rv, rv);
2195 : }
2196 :
2197 0 : return NS_OK;
2198 : }
2199 :
2200 :
2201 : nsresult
2202 0 : nsNavBookmarks::ProcessFolderNodeRow(
2203 : mozIStorageValueArray* aRow,
2204 : nsNavHistoryQueryOptions* aOptions,
2205 : nsCOMArray<nsNavHistoryResultNode>* aChildren,
2206 : int32_t& aCurrentIndex)
2207 : {
2208 0 : NS_ENSURE_ARG_POINTER(aRow);
2209 0 : NS_ENSURE_ARG_POINTER(aOptions);
2210 0 : NS_ENSURE_ARG_POINTER(aChildren);
2211 :
2212 : // The results will be in order of aCurrentIndex. Even if we don't add a node
2213 : // because it was excluded, we need to count its index, so do that before
2214 : // doing anything else.
2215 0 : aCurrentIndex++;
2216 :
2217 : int32_t itemType;
2218 0 : nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
2219 0 : NS_ENSURE_SUCCESS(rv, rv);
2220 : int64_t id;
2221 0 : rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
2222 0 : NS_ENSURE_SUCCESS(rv, rv);
2223 :
2224 0 : RefPtr<nsNavHistoryResultNode> node;
2225 :
2226 0 : if (itemType == TYPE_BOOKMARK) {
2227 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
2228 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2229 0 : rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
2230 0 : NS_ENSURE_SUCCESS(rv, rv);
2231 :
2232 : uint32_t nodeType;
2233 0 : node->GetType(&nodeType);
2234 0 : if ((nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
2235 0 : aOptions->ExcludeQueries()) ||
2236 0 : (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
2237 0 : nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT &&
2238 0 : aOptions->ExcludeItems())) {
2239 0 : return NS_OK;
2240 : }
2241 : }
2242 0 : else if (itemType == TYPE_FOLDER) {
2243 : // ExcludeReadOnlyFolders currently means "ExcludeLivemarks" (to be fixed in
2244 : // bug 1072833)
2245 0 : if (aOptions->ExcludeReadOnlyFolders()) {
2246 0 : if (IsLivemark(id))
2247 0 : return NS_OK;
2248 : }
2249 :
2250 0 : nsAutoCString title;
2251 : bool isNull;
2252 0 : rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
2253 0 : NS_ENSURE_SUCCESS(rv, rv);
2254 0 : if (!isNull) {
2255 0 : rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
2256 0 : NS_ENSURE_SUCCESS(rv, rv);
2257 : }
2258 :
2259 0 : node = new nsNavHistoryFolderResultNode(title, aOptions, id);
2260 :
2261 0 : rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
2262 0 : NS_ENSURE_SUCCESS(rv, rv);
2263 0 : node->GetAsFolder()->mTargetFolderGuid = node->mBookmarkGuid;
2264 :
2265 0 : rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
2266 0 : reinterpret_cast<int64_t*>(&node->mDateAdded));
2267 0 : NS_ENSURE_SUCCESS(rv, rv);
2268 0 : rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
2269 0 : reinterpret_cast<int64_t*>(&node->mLastModified));
2270 0 : NS_ENSURE_SUCCESS(rv, rv);
2271 : }
2272 : else {
2273 : // This is a separator.
2274 0 : if (aOptions->ExcludeItems()) {
2275 0 : return NS_OK;
2276 : }
2277 0 : node = new nsNavHistorySeparatorResultNode();
2278 :
2279 0 : node->mItemId = id;
2280 0 : rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
2281 0 : NS_ENSURE_SUCCESS(rv, rv);
2282 0 : rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
2283 0 : reinterpret_cast<int64_t*>(&node->mDateAdded));
2284 0 : NS_ENSURE_SUCCESS(rv, rv);
2285 0 : rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
2286 0 : reinterpret_cast<int64_t*>(&node->mLastModified));
2287 0 : NS_ENSURE_SUCCESS(rv, rv);
2288 : }
2289 :
2290 : // Store the index of the node within this container. Note that this is not
2291 : // moz_bookmarks.position.
2292 0 : node->mBookmarkIndex = aCurrentIndex;
2293 :
2294 0 : NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
2295 0 : return NS_OK;
2296 : }
2297 :
2298 :
2299 : nsresult
2300 0 : nsNavBookmarks::QueryFolderChildrenAsync(
2301 : nsNavHistoryFolderResultNode* aNode,
2302 : int64_t aFolderId,
2303 : mozIStoragePendingStatement** _pendingStmt)
2304 : {
2305 0 : NS_ENSURE_ARG_POINTER(aNode);
2306 0 : NS_ENSURE_ARG_POINTER(_pendingStmt);
2307 :
2308 : // Select all children of a given folder, sorted by position.
2309 : // This is a LEFT JOIN because not all bookmarks types have a place.
2310 : // We construct a result where the first columns exactly match those returned
2311 : // by mDBGetURLPageInfo, and additionally contains columns for position,
2312 : // item_child, and folder_child from moz_bookmarks.
2313 0 : nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
2314 : "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
2315 : "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
2316 : "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
2317 : "b.guid, b.position, b.type, b.fk "
2318 : "FROM moz_bookmarks b "
2319 : "LEFT JOIN moz_places h ON b.fk = h.id "
2320 : "WHERE b.parent = :parent "
2321 : "ORDER BY b.position ASC"
2322 0 : );
2323 0 : NS_ENSURE_STATE(stmt);
2324 :
2325 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
2326 0 : NS_ENSURE_SUCCESS(rv, rv);
2327 :
2328 0 : nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
2329 0 : rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
2330 0 : NS_ENSURE_SUCCESS(rv, rv);
2331 :
2332 0 : NS_IF_ADDREF(*_pendingStmt = pendingStmt);
2333 0 : return NS_OK;
2334 : }
2335 :
2336 :
2337 : nsresult
2338 0 : nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
2339 : int32_t* _folderCount,
2340 : nsACString& _guid,
2341 : int64_t* _parentId)
2342 : {
2343 0 : *_folderCount = 0;
2344 0 : *_parentId = -1;
2345 :
2346 : // This query has to always return results, so it can't be written as a join,
2347 : // though a left join of 2 subqueries would have the same cost.
2348 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2349 : "SELECT count(*), "
2350 : "(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
2351 : "(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
2352 : "FROM moz_bookmarks "
2353 : "WHERE parent = :parent"
2354 0 : );
2355 0 : NS_ENSURE_STATE(stmt);
2356 0 : mozStorageStatementScoper scoper(stmt);
2357 :
2358 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
2359 0 : NS_ENSURE_SUCCESS(rv, rv);
2360 :
2361 : bool hasResult;
2362 0 : rv = stmt->ExecuteStep(&hasResult);
2363 0 : NS_ENSURE_SUCCESS(rv, rv);
2364 0 : NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
2365 :
2366 : // Ensure that the folder we are looking for exists.
2367 : // Can't rely only on parent, since the root has parent 0, that doesn't exist.
2368 : bool isNull;
2369 0 : rv = stmt->GetIsNull(2, &isNull);
2370 0 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
2371 : NS_ERROR_INVALID_ARG);
2372 :
2373 0 : rv = stmt->GetInt32(0, _folderCount);
2374 0 : NS_ENSURE_SUCCESS(rv, rv);
2375 0 : if (!isNull) {
2376 0 : rv = stmt->GetUTF8String(1, _guid);
2377 0 : NS_ENSURE_SUCCESS(rv, rv);
2378 0 : rv = stmt->GetInt64(2, _parentId);
2379 0 : NS_ENSURE_SUCCESS(rv, rv);
2380 : }
2381 :
2382 0 : return NS_OK;
2383 : }
2384 :
2385 :
2386 : NS_IMETHODIMP
2387 0 : nsNavBookmarks::IsBookmarked(nsIURI* aURI, bool* aBookmarked)
2388 : {
2389 0 : NS_ENSURE_ARG(aURI);
2390 0 : NS_ENSURE_ARG_POINTER(aBookmarked);
2391 :
2392 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2393 : "SELECT 1 FROM moz_bookmarks b "
2394 : "JOIN moz_places h ON b.fk = h.id "
2395 : "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
2396 0 : );
2397 0 : NS_ENSURE_STATE(stmt);
2398 0 : mozStorageStatementScoper scoper(stmt);
2399 :
2400 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2401 0 : NS_ENSURE_SUCCESS(rv, rv);
2402 0 : rv = stmt->ExecuteStep(aBookmarked);
2403 0 : NS_ENSURE_SUCCESS(rv, rv);
2404 :
2405 0 : return NS_OK;
2406 : }
2407 :
2408 :
2409 : NS_IMETHODIMP
2410 0 : nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
2411 : {
2412 0 : NS_ENSURE_ARG(aURI);
2413 0 : NS_ENSURE_ARG_POINTER(_retval);
2414 :
2415 0 : *_retval = nullptr;
2416 :
2417 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
2418 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2419 : int64_t placeId;
2420 0 : nsAutoCString placeGuid;
2421 0 : nsresult rv = history->GetIdForPage(aURI, &placeId, placeGuid);
2422 0 : NS_ENSURE_SUCCESS(rv, rv);
2423 0 : if (!placeId) {
2424 : // This URI is unknown, just return null.
2425 0 : return NS_OK;
2426 : }
2427 :
2428 : // Check if a bookmark exists in the redirects chain for this URI.
2429 : // The query will also check if the page is directly bookmarked, and return
2430 : // the first found bookmark in case. The check is directly on moz_bookmarks
2431 : // without special filtering.
2432 : // The next query finds the bookmarked ancestors in a redirects chain.
2433 : // It won't go further than 3 levels of redirects (a->b->c->your_place_id).
2434 : // To make this path 100% correct (up to any level) we would need either:
2435 : // - A separate hash, build through recursive querying of the database.
2436 : // This solution was previously implemented, but it had a negative effect
2437 : // on startup since at each startup we have to recursively query the
2438 : // database to rebuild a hash that is always the same across sessions.
2439 : // It must be updated at each visit and bookmarks change too. The code to
2440 : // manage it is complex and prone to errors, sometimes causing incorrect
2441 : // data fetches (for example wrong favicon for a redirected bookmark).
2442 : // - A better way to track redirects for a visit.
2443 : // We would need a separate table to track redirects, in the table we would
2444 : // have visit_id, redirect_session. To get all sources for
2445 : // a visit then we could just join this table and get all visit_id that
2446 : // are in the same redirect_session as our visit. This has the drawback
2447 : // that we can't ensure data integrity in the downgrade -> upgrade path,
2448 : // since an old version would not update the table on new visits.
2449 : //
2450 : // For most cases these levels of redirects should be fine though, it's hard
2451 : // to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
2452 : //
2453 : // As a bonus the query also checks first if place_id is already a bookmark,
2454 : // so you don't have to check that apart.
2455 :
2456 0 : nsCString query = nsPrintfCString(
2457 : "SELECT url FROM moz_places WHERE id = ( "
2458 : "SELECT :page_id FROM moz_bookmarks WHERE fk = :page_id "
2459 : "UNION ALL "
2460 : "SELECT COALESCE(grandparent.place_id, parent.place_id) AS r_place_id "
2461 : "FROM moz_historyvisits dest "
2462 : "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
2463 : "AND dest.visit_type IN (%d, %d) "
2464 : "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
2465 : "AND parent.visit_type IN (%d, %d) "
2466 : "WHERE dest.place_id = :page_id "
2467 : "AND EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = r_place_id) "
2468 : "LIMIT 1 "
2469 : ")",
2470 : nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
2471 : nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
2472 : nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
2473 : nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
2474 0 : );
2475 :
2476 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(query);
2477 0 : NS_ENSURE_STATE(stmt);
2478 0 : mozStorageStatementScoper scoper(stmt);
2479 :
2480 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), placeId);
2481 0 : NS_ENSURE_SUCCESS(rv, rv);
2482 : bool hasBookmarkedOrigin;
2483 0 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasBookmarkedOrigin)) &&
2484 : hasBookmarkedOrigin) {
2485 0 : nsAutoCString spec;
2486 0 : rv = stmt->GetUTF8String(0, spec);
2487 0 : NS_ENSURE_SUCCESS(rv, rv);
2488 0 : rv = NS_NewURI(_retval, spec);
2489 0 : NS_ENSURE_SUCCESS(rv, rv);
2490 : }
2491 :
2492 : // If there is no bookmarked origin, we will just return null.
2493 0 : return NS_OK;
2494 : }
2495 :
2496 :
2497 : NS_IMETHODIMP
2498 0 : nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI,
2499 : uint16_t aSource)
2500 : {
2501 0 : NS_ENSURE_ARG_MIN(aBookmarkId, 1);
2502 0 : NS_ENSURE_ARG(aNewURI);
2503 :
2504 0 : BookmarkData bookmark;
2505 0 : nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
2506 0 : NS_ENSURE_SUCCESS(rv, rv);
2507 0 : NS_ENSURE_ARG(bookmark.type == TYPE_BOOKMARK);
2508 :
2509 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
2510 :
2511 0 : int64_t tagsRootId = TagsRootId();
2512 0 : bool isTagging = bookmark.grandParentId == tagsRootId;
2513 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
2514 :
2515 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
2516 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2517 : int64_t newPlaceId;
2518 0 : nsAutoCString newPlaceGuid;
2519 0 : rv = history->GetOrCreateIdForPage(aNewURI, &newPlaceId, newPlaceGuid);
2520 0 : NS_ENSURE_SUCCESS(rv, rv);
2521 0 : if (!newPlaceId)
2522 0 : return NS_ERROR_INVALID_ARG;
2523 :
2524 0 : nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
2525 : "UPDATE moz_bookmarks SET "
2526 : "fk = :page_id, lastModified = :date, "
2527 : "syncChangeCounter = syncChangeCounter + :delta "
2528 : "WHERE id = :item_id "
2529 0 : );
2530 0 : NS_ENSURE_STATE(statement);
2531 0 : mozStorageStatementScoper scoper(statement);
2532 :
2533 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), newPlaceId);
2534 0 : NS_ENSURE_SUCCESS(rv, rv);
2535 0 : bookmark.lastModified = RoundedPRNow();
2536 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
2537 0 : bookmark.lastModified);
2538 0 : NS_ENSURE_SUCCESS(rv, rv);
2539 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
2540 0 : NS_ENSURE_SUCCESS(rv, rv);
2541 0 : rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"), syncChangeDelta);
2542 0 : NS_ENSURE_SUCCESS(rv, rv);
2543 0 : rv = statement->Execute();
2544 0 : NS_ENSURE_SUCCESS(rv, rv);
2545 :
2546 0 : if (isTagging) {
2547 : // For consistency with the tagging service behavior, changing a tag entry's
2548 : // URL bumps the change counter for bookmarks with the old and new URIs.
2549 0 : rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
2550 0 : NS_ENSURE_SUCCESS(rv, rv);
2551 0 : rv = AddSyncChangesForBookmarksWithURI(aNewURI, syncChangeDelta);
2552 0 : NS_ENSURE_SUCCESS(rv, rv);
2553 : }
2554 :
2555 0 : rv = transaction.Commit();
2556 0 : NS_ENSURE_SUCCESS(rv, rv);
2557 :
2558 0 : rv = history->UpdateFrecency(newPlaceId);
2559 0 : NS_ENSURE_SUCCESS(rv, rv);
2560 :
2561 : // Upon changing the URI for a bookmark, update the frecency for the old
2562 : // place as well.
2563 0 : rv = history->UpdateFrecency(bookmark.placeId);
2564 0 : NS_ENSURE_SUCCESS(rv, rv);
2565 :
2566 0 : nsAutoCString spec;
2567 0 : rv = aNewURI->GetSpec(spec);
2568 0 : NS_ENSURE_SUCCESS(rv, rv);
2569 :
2570 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2571 : nsINavBookmarkObserver,
2572 : OnItemChanged(bookmark.id,
2573 : NS_LITERAL_CSTRING("uri"),
2574 : false,
2575 : spec,
2576 : bookmark.lastModified,
2577 : bookmark.type,
2578 : bookmark.parentId,
2579 : bookmark.guid,
2580 : bookmark.parentGuid,
2581 : bookmark.url,
2582 : aSource));
2583 0 : return NS_OK;
2584 : }
2585 :
2586 :
2587 : NS_IMETHODIMP
2588 0 : nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
2589 : {
2590 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2591 0 : NS_ENSURE_ARG_POINTER(_parentId);
2592 :
2593 0 : BookmarkData bookmark;
2594 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2595 0 : NS_ENSURE_SUCCESS(rv, rv);
2596 :
2597 : // this should not happen, but see bug #400448 for details
2598 0 : NS_ENSURE_TRUE(bookmark.id != bookmark.parentId, NS_ERROR_UNEXPECTED);
2599 :
2600 0 : *_parentId = bookmark.parentId;
2601 0 : return NS_OK;
2602 : }
2603 :
2604 :
2605 : nsresult
2606 0 : nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
2607 : nsTArray<int64_t>& aResult)
2608 : {
2609 0 : NS_ENSURE_ARG(aURI);
2610 :
2611 : // Double ordering covers possible lastModified ties, that could happen when
2612 : // importing, syncing or due to extensions.
2613 : // Note: not using a JOIN is cheaper in this case.
2614 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2615 : "/* do not warn (bug 1175249) */ "
2616 : "SELECT b.id "
2617 : "FROM moz_bookmarks b "
2618 : "JOIN moz_bookmarks t on t.id = b.parent "
2619 : "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) AND "
2620 : "t.parent IS NOT :tags_root "
2621 : "ORDER BY b.lastModified DESC, b.id DESC "
2622 0 : );
2623 0 : NS_ENSURE_STATE(stmt);
2624 0 : mozStorageStatementScoper scoper(stmt);
2625 :
2626 0 : int64_t tagsRootId = TagsRootId();
2627 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2628 0 : NS_ENSURE_SUCCESS(rv, rv);
2629 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_root"), tagsRootId);
2630 0 : NS_ENSURE_SUCCESS(rv, rv);
2631 :
2632 : bool more;
2633 0 : while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
2634 : int64_t bookmarkId;
2635 0 : rv = stmt->GetInt64(0, &bookmarkId);
2636 0 : NS_ENSURE_SUCCESS(rv, rv);
2637 0 : NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
2638 : }
2639 0 : NS_ENSURE_SUCCESS(rv, rv);
2640 :
2641 0 : return NS_OK;
2642 : }
2643 :
2644 : nsresult
2645 0 : nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
2646 : nsTArray<BookmarkData>& aBookmarks)
2647 : {
2648 0 : NS_ENSURE_ARG(aURI);
2649 :
2650 : // Double ordering covers possible lastModified ties, that could happen when
2651 : // importing, syncing or due to extensions.
2652 : // Note: not using a JOIN is cheaper in this case.
2653 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2654 : "/* do not warn (bug 1175249) */ "
2655 : "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent, b.syncStatus "
2656 : "FROM moz_bookmarks b "
2657 : "JOIN moz_bookmarks t on t.id = b.parent "
2658 : "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
2659 : "ORDER BY b.lastModified DESC, b.id DESC "
2660 0 : );
2661 0 : NS_ENSURE_STATE(stmt);
2662 0 : mozStorageStatementScoper scoper(stmt);
2663 :
2664 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2665 0 : NS_ENSURE_SUCCESS(rv, rv);
2666 :
2667 0 : int64_t tagsRootId = TagsRootId();
2668 :
2669 : bool more;
2670 0 : nsAutoString tags;
2671 0 : while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
2672 : // Skip tags.
2673 : int64_t grandParentId;
2674 0 : nsresult rv = stmt->GetInt64(5, &grandParentId);
2675 0 : NS_ENSURE_SUCCESS(rv, rv);
2676 0 : if (grandParentId == tagsRootId) {
2677 0 : continue;
2678 : }
2679 :
2680 0 : BookmarkData bookmark;
2681 0 : bookmark.grandParentId = grandParentId;
2682 0 : rv = stmt->GetInt64(0, &bookmark.id);
2683 0 : NS_ENSURE_SUCCESS(rv, rv);
2684 0 : rv = stmt->GetUTF8String(1, bookmark.guid);
2685 0 : NS_ENSURE_SUCCESS(rv, rv);
2686 0 : rv = stmt->GetInt64(2, &bookmark.parentId);
2687 0 : NS_ENSURE_SUCCESS(rv, rv);
2688 0 : rv = stmt->GetInt64(3, reinterpret_cast<int64_t*>(&bookmark.lastModified));
2689 0 : NS_ENSURE_SUCCESS(rv, rv);
2690 0 : rv = stmt->GetUTF8String(4, bookmark.parentGuid);
2691 0 : NS_ENSURE_SUCCESS(rv, rv);
2692 0 : rv = stmt->GetInt32(6, &bookmark.syncStatus);
2693 0 : NS_ENSURE_SUCCESS(rv, rv);
2694 :
2695 0 : NS_ENSURE_TRUE(aBookmarks.AppendElement(bookmark), NS_ERROR_OUT_OF_MEMORY);
2696 : }
2697 :
2698 0 : return NS_OK;
2699 : }
2700 :
2701 : NS_IMETHODIMP
2702 0 : nsNavBookmarks::GetBookmarkIdsForURI(nsIURI* aURI, uint32_t* aCount,
2703 : int64_t** aBookmarks)
2704 : {
2705 0 : NS_ENSURE_ARG(aURI);
2706 0 : NS_ENSURE_ARG_POINTER(aCount);
2707 0 : NS_ENSURE_ARG_POINTER(aBookmarks);
2708 :
2709 0 : *aCount = 0;
2710 0 : *aBookmarks = nullptr;
2711 0 : nsTArray<int64_t> bookmarks;
2712 :
2713 : // Get the information from the DB as a TArray
2714 0 : nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
2715 0 : NS_ENSURE_SUCCESS(rv, rv);
2716 :
2717 : // Copy the results into a new array for output
2718 0 : if (bookmarks.Length()) {
2719 0 : *aBookmarks =
2720 0 : static_cast<int64_t*>(moz_xmalloc(sizeof(int64_t) * bookmarks.Length()));
2721 0 : if (!*aBookmarks)
2722 0 : return NS_ERROR_OUT_OF_MEMORY;
2723 0 : for (uint32_t i = 0; i < bookmarks.Length(); i ++)
2724 0 : (*aBookmarks)[i] = bookmarks[i];
2725 : }
2726 :
2727 0 : *aCount = bookmarks.Length();
2728 0 : return NS_OK;
2729 : }
2730 :
2731 :
2732 : NS_IMETHODIMP
2733 0 : nsNavBookmarks::GetItemIndex(int64_t aItemId, int32_t* _index)
2734 : {
2735 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2736 0 : NS_ENSURE_ARG_POINTER(_index);
2737 :
2738 0 : BookmarkData bookmark;
2739 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2740 : // With respect to the API.
2741 0 : if (NS_FAILED(rv)) {
2742 0 : *_index = -1;
2743 0 : return NS_OK;
2744 : }
2745 :
2746 0 : *_index = bookmark.position;
2747 0 : return NS_OK;
2748 : }
2749 :
2750 : NS_IMETHODIMP
2751 0 : nsNavBookmarks::SetItemIndex(int64_t aItemId,
2752 : int32_t aNewIndex,
2753 : uint16_t aSource)
2754 : {
2755 0 : NS_ENSURE_ARG_MIN(aItemId, 1);
2756 0 : NS_ENSURE_ARG_MIN(aNewIndex, 0);
2757 :
2758 0 : BookmarkData bookmark;
2759 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
2760 0 : NS_ENSURE_SUCCESS(rv, rv);
2761 :
2762 : // Ensure we are not going out of range.
2763 : int32_t folderCount;
2764 : int64_t grandParentId;
2765 0 : nsAutoCString folderGuid;
2766 0 : rv = FetchFolderInfo(bookmark.parentId, &folderCount, folderGuid, &grandParentId);
2767 0 : NS_ENSURE_SUCCESS(rv, rv);
2768 0 : NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG);
2769 : // Check the parent's guid is the expected one.
2770 0 : MOZ_ASSERT(bookmark.parentGuid == folderGuid);
2771 :
2772 0 : mozStorageTransaction transaction(mDB->MainConn(), false);
2773 :
2774 : {
2775 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2776 : "UPDATE moz_bookmarks SET "
2777 : "position = :item_index "
2778 : "WHERE id = :item_id"
2779 0 : );
2780 0 : NS_ENSURE_STATE(stmt);
2781 0 : mozStorageStatementScoper scoper(stmt);
2782 :
2783 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2784 0 : NS_ENSURE_SUCCESS(rv, rv);
2785 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aNewIndex);
2786 0 : NS_ENSURE_SUCCESS(rv, rv);
2787 :
2788 0 : rv = stmt->Execute();
2789 0 : NS_ENSURE_SUCCESS(rv, rv);
2790 : }
2791 :
2792 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
2793 :
2794 : {
2795 : // Sync stores child indices in the parent's record, so we only need to
2796 : // bump the parent's change counter.
2797 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2798 : "UPDATE moz_bookmarks SET "
2799 : "syncChangeCounter = syncChangeCounter + :delta "
2800 : "WHERE id = :parent_id"
2801 0 : );
2802 0 : NS_ENSURE_STATE(stmt);
2803 0 : mozStorageStatementScoper scoper(stmt);
2804 :
2805 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent_id"),
2806 0 : bookmark.parentId);
2807 0 : NS_ENSURE_SUCCESS(rv, rv);
2808 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
2809 0 : syncChangeDelta);
2810 0 : NS_ENSURE_SUCCESS(rv, rv);
2811 :
2812 0 : rv = stmt->Execute();
2813 0 : NS_ENSURE_SUCCESS(rv, rv);
2814 : }
2815 :
2816 0 : rv = AdjustSeparatorsSyncCounter(bookmark.parentId, aNewIndex, syncChangeDelta);
2817 0 : NS_ENSURE_SUCCESS(rv, rv);
2818 :
2819 0 : rv = PreventSyncReparenting(bookmark);
2820 0 : NS_ENSURE_SUCCESS(rv, rv);
2821 :
2822 0 : rv = transaction.Commit();
2823 0 : NS_ENSURE_SUCCESS(rv, rv);
2824 :
2825 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2826 : nsINavBookmarkObserver,
2827 : OnItemMoved(bookmark.id,
2828 : bookmark.parentId,
2829 : bookmark.position,
2830 : bookmark.parentId,
2831 : aNewIndex,
2832 : bookmark.type,
2833 : bookmark.guid,
2834 : bookmark.parentGuid,
2835 : bookmark.parentGuid,
2836 : aSource));
2837 :
2838 0 : return NS_OK;
2839 : }
2840 :
2841 :
2842 : NS_IMETHODIMP
2843 0 : nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
2844 : const nsAString& aUserCasedKeyword,
2845 : uint16_t aSource)
2846 : {
2847 0 : NS_ENSURE_ARG_MIN(aBookmarkId, 1);
2848 :
2849 : // This also ensures the bookmark is valid.
2850 0 : BookmarkData bookmark;
2851 0 : nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
2852 0 : NS_ENSURE_SUCCESS(rv, rv);
2853 0 : nsCOMPtr<nsIURI> uri;
2854 0 : rv = NS_NewURI(getter_AddRefs(uri), bookmark.url);
2855 0 : NS_ENSURE_SUCCESS(rv, rv);
2856 :
2857 : // Shortcuts are always lowercased internally.
2858 0 : nsAutoString keyword(aUserCasedKeyword);
2859 0 : ToLowerCase(keyword);
2860 :
2861 : // The same URI can be associated to more than one keyword, provided the post
2862 : // data differs. Check if there are already keywords associated to this uri.
2863 0 : nsTArray<nsString> oldKeywords;
2864 : {
2865 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2866 : "SELECT keyword FROM moz_keywords WHERE place_id = :place_id"
2867 0 : );
2868 0 : NS_ENSURE_STATE(stmt);
2869 0 : mozStorageStatementScoper scoper(stmt);
2870 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
2871 0 : NS_ENSURE_SUCCESS(rv, rv);
2872 :
2873 : bool hasMore;
2874 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2875 0 : nsString oldKeyword;
2876 0 : rv = stmt->GetString(0, oldKeyword);
2877 0 : NS_ENSURE_SUCCESS(rv, rv);
2878 0 : oldKeywords.AppendElement(oldKeyword);
2879 : }
2880 : }
2881 :
2882 : // Trying to remove a non-existent keyword is a no-op.
2883 0 : if (keyword.IsEmpty() && oldKeywords.Length() == 0) {
2884 0 : return NS_OK;
2885 : }
2886 :
2887 0 : int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
2888 :
2889 0 : if (keyword.IsEmpty()) {
2890 0 : mozStorageTransaction removeTxn(mDB->MainConn(), false);
2891 :
2892 : // We are removing the existing keywords.
2893 0 : for (uint32_t i = 0; i < oldKeywords.Length(); ++i) {
2894 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2895 : "DELETE FROM moz_keywords WHERE keyword = :old_keyword"
2896 0 : );
2897 0 : NS_ENSURE_STATE(stmt);
2898 0 : mozStorageStatementScoper scoper(stmt);
2899 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("old_keyword"),
2900 0 : oldKeywords[i]);
2901 0 : NS_ENSURE_SUCCESS(rv, rv);
2902 0 : rv = stmt->Execute();
2903 0 : NS_ENSURE_SUCCESS(rv, rv);
2904 : }
2905 :
2906 0 : nsTArray<BookmarkData> bookmarks;
2907 0 : rv = GetBookmarksForURI(uri, bookmarks);
2908 0 : NS_ENSURE_SUCCESS(rv, rv);
2909 :
2910 0 : if (syncChangeDelta && !bookmarks.IsEmpty()) {
2911 : // Build a query to update all bookmarks in a single statement.
2912 0 : nsAutoCString changedIds;
2913 0 : changedIds.AppendInt(bookmarks[0].id);
2914 0 : for (uint32_t i = 1; i < bookmarks.Length(); ++i) {
2915 0 : changedIds.Append(',');
2916 0 : changedIds.AppendInt(bookmarks[i].id);
2917 : }
2918 : // Update the sync change counter for all bookmarks with the removed
2919 : // keyword.
2920 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2921 0 : NS_LITERAL_CSTRING(
2922 : "UPDATE moz_bookmarks SET "
2923 : "syncChangeCounter = syncChangeCounter + :delta "
2924 0 : "WHERE id IN (") + changedIds + NS_LITERAL_CSTRING(")")
2925 0 : );
2926 0 : NS_ENSURE_STATE(stmt);
2927 0 : mozStorageStatementScoper scoper(stmt);
2928 :
2929 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
2930 0 : syncChangeDelta);
2931 0 : NS_ENSURE_SUCCESS(rv, rv);
2932 :
2933 0 : rv = stmt->Execute();
2934 0 : NS_ENSURE_SUCCESS(rv, rv);
2935 : }
2936 :
2937 0 : rv = removeTxn.Commit();
2938 0 : NS_ENSURE_SUCCESS(rv, rv);
2939 :
2940 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
2941 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2942 : nsINavBookmarkObserver,
2943 : OnItemChanged(bookmarks[i].id,
2944 : NS_LITERAL_CSTRING("keyword"),
2945 : false,
2946 : EmptyCString(),
2947 : bookmarks[i].lastModified,
2948 : TYPE_BOOKMARK,
2949 : bookmarks[i].parentId,
2950 : bookmarks[i].guid,
2951 : bookmarks[i].parentGuid,
2952 : // Abusing oldVal to pass out the url.
2953 : bookmark.url,
2954 : aSource));
2955 : }
2956 :
2957 0 : return NS_OK;
2958 : }
2959 :
2960 : // A keyword can only be associated to a single URI. Check if the requested
2961 : // keyword was already associated, in such a case we will need to notify about
2962 : // the change.
2963 0 : nsAutoCString oldSpec;
2964 0 : nsCOMPtr<nsIURI> oldUri;
2965 : {
2966 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
2967 : "SELECT url "
2968 : "FROM moz_keywords "
2969 : "JOIN moz_places h ON h.id = place_id "
2970 : "WHERE keyword = :keyword"
2971 0 : );
2972 0 : NS_ENSURE_STATE(stmt);
2973 0 : mozStorageStatementScoper scoper(stmt);
2974 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
2975 0 : NS_ENSURE_SUCCESS(rv, rv);
2976 :
2977 : bool hasMore;
2978 0 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2979 0 : rv = stmt->GetUTF8String(0, oldSpec);
2980 0 : NS_ENSURE_SUCCESS(rv, rv);
2981 0 : rv = NS_NewURI(getter_AddRefs(oldUri), oldSpec);
2982 0 : NS_ENSURE_SUCCESS(rv, rv);
2983 : }
2984 : }
2985 :
2986 : // If another uri is using the new keyword, we must update the keyword entry.
2987 : // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
2988 : // trigger.
2989 0 : mozStorageTransaction updateTxn(mDB->MainConn(), false);
2990 :
2991 0 : nsCOMPtr<mozIStorageStatement> stmt;
2992 0 : if (oldUri) {
2993 : // In both cases, notify about the change.
2994 0 : nsTArray<BookmarkData> bookmarks;
2995 0 : rv = GetBookmarksForURI(oldUri, bookmarks);
2996 0 : NS_ENSURE_SUCCESS(rv, rv);
2997 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
2998 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2999 : nsINavBookmarkObserver,
3000 : OnItemChanged(bookmarks[i].id,
3001 : NS_LITERAL_CSTRING("keyword"),
3002 : false,
3003 : EmptyCString(),
3004 : bookmarks[i].lastModified,
3005 : TYPE_BOOKMARK,
3006 : bookmarks[i].parentId,
3007 : bookmarks[i].guid,
3008 : bookmarks[i].parentGuid,
3009 : // Abusing oldVal to pass out the url.
3010 : oldSpec,
3011 : aSource));
3012 : }
3013 :
3014 0 : stmt = mDB->GetStatement(
3015 : "UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
3016 0 : );
3017 0 : NS_ENSURE_STATE(stmt);
3018 : }
3019 : else {
3020 0 : stmt = mDB->GetStatement(
3021 : "INSERT INTO moz_keywords (keyword, place_id) "
3022 : "VALUES (:keyword, :place_id)"
3023 0 : );
3024 : }
3025 0 : NS_ENSURE_STATE(stmt);
3026 0 : mozStorageStatementScoper scoper(stmt);
3027 :
3028 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
3029 0 : NS_ENSURE_SUCCESS(rv, rv);
3030 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
3031 0 : NS_ENSURE_SUCCESS(rv, rv);
3032 0 : rv = stmt->Execute();
3033 0 : NS_ENSURE_SUCCESS(rv, rv);
3034 :
3035 0 : nsTArray<BookmarkData> bookmarks;
3036 0 : rv = GetBookmarksForURI(uri, bookmarks);
3037 0 : NS_ENSURE_SUCCESS(rv, rv);
3038 :
3039 0 : if (syncChangeDelta && !bookmarks.IsEmpty()) {
3040 : // Build a query to update all bookmarks in a single statement.
3041 0 : nsAutoCString changedIds;
3042 0 : changedIds.AppendInt(bookmarks[0].id);
3043 0 : for (uint32_t i = 1; i < bookmarks.Length(); ++i) {
3044 0 : changedIds.Append(',');
3045 0 : changedIds.AppendInt(bookmarks[i].id);
3046 : }
3047 : // Update the sync change counter for all bookmarks with the new keyword.
3048 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
3049 0 : NS_LITERAL_CSTRING(
3050 : "UPDATE moz_bookmarks SET "
3051 : "syncChangeCounter = syncChangeCounter + :delta "
3052 0 : "WHERE id IN (") + changedIds + NS_LITERAL_CSTRING(")")
3053 0 : );
3054 0 : NS_ENSURE_STATE(stmt);
3055 0 : mozStorageStatementScoper scoper(stmt);
3056 :
3057 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), syncChangeDelta);
3058 0 : NS_ENSURE_SUCCESS(rv, rv);
3059 :
3060 0 : rv = stmt->Execute();
3061 0 : NS_ENSURE_SUCCESS(rv, rv);
3062 : }
3063 :
3064 0 : rv = updateTxn.Commit();
3065 0 : NS_ENSURE_SUCCESS(rv, rv);
3066 :
3067 : // In both cases, notify about the change.
3068 0 : for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
3069 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3070 : nsINavBookmarkObserver,
3071 : OnItemChanged(bookmarks[i].id,
3072 : NS_LITERAL_CSTRING("keyword"),
3073 : false,
3074 : NS_ConvertUTF16toUTF8(keyword),
3075 : bookmarks[i].lastModified,
3076 : TYPE_BOOKMARK,
3077 : bookmarks[i].parentId,
3078 : bookmarks[i].guid,
3079 : bookmarks[i].parentGuid,
3080 : // Abusing oldVal to pass out the url.
3081 : bookmark.url,
3082 : aSource));
3083 : }
3084 :
3085 0 : return NS_OK;
3086 : }
3087 :
3088 :
3089 : NS_IMETHODIMP
3090 0 : nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
3091 : {
3092 0 : NS_ENSURE_ARG_MIN(aBookmarkId, 1);
3093 0 : aKeyword.Truncate(0);
3094 :
3095 : // We can have multiple keywords for the same uri, here we'll just return the
3096 : // last created one.
3097 0 : nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
3098 : "SELECT k.keyword "
3099 : "FROM moz_bookmarks b "
3100 : "JOIN moz_keywords k ON k.place_id = b.fk "
3101 : "WHERE b.id = :item_id "
3102 : "ORDER BY k.ROWID DESC "
3103 : "LIMIT 1"
3104 0 : ));
3105 0 : NS_ENSURE_STATE(stmt);
3106 0 : mozStorageStatementScoper scoper(stmt);
3107 :
3108 0 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
3109 0 : aBookmarkId);
3110 0 : NS_ENSURE_SUCCESS(rv, rv);
3111 :
3112 : bool hasMore;
3113 0 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
3114 0 : nsAutoString keyword;
3115 0 : rv = stmt->GetString(0, keyword);
3116 0 : NS_ENSURE_SUCCESS(rv, rv);
3117 0 : aKeyword = keyword;
3118 0 : return NS_OK;
3119 : }
3120 :
3121 0 : aKeyword.SetIsVoid(true);
3122 :
3123 0 : return NS_OK;
3124 : }
3125 :
3126 :
3127 : NS_IMETHODIMP
3128 0 : nsNavBookmarks::RunInBatchMode(nsINavHistoryBatchCallback* aCallback,
3129 : nsISupports* aUserData) {
3130 0 : AUTO_PROFILER_LABEL("nsNavBookmarks::RunInBatchMode", OTHER);
3131 :
3132 0 : NS_ENSURE_ARG(aCallback);
3133 :
3134 0 : mBatching = true;
3135 :
3136 : // Just forward the request to history. History service must exist for
3137 : // bookmarks to work and we are observing it, thus batch notifications will be
3138 : // forwarded to bookmarks observers.
3139 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
3140 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
3141 0 : nsresult rv = history->RunInBatchMode(aCallback, aUserData);
3142 0 : NS_ENSURE_SUCCESS(rv, rv);
3143 :
3144 0 : return NS_OK;
3145 : }
3146 :
3147 :
3148 : NS_IMETHODIMP
3149 2 : nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
3150 : bool aOwnsWeak)
3151 : {
3152 2 : NS_ENSURE_ARG(aObserver);
3153 :
3154 2 : if (NS_WARN_IF(!mCanNotify))
3155 0 : return NS_ERROR_UNEXPECTED;
3156 :
3157 2 : return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
3158 : }
3159 :
3160 :
3161 : NS_IMETHODIMP
3162 0 : nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
3163 : {
3164 0 : return mObservers.RemoveWeakElement(aObserver);
3165 : }
3166 :
3167 : NS_IMETHODIMP
3168 0 : nsNavBookmarks::GetObservers(uint32_t* _count,
3169 : nsINavBookmarkObserver*** _observers)
3170 : {
3171 0 : NS_ENSURE_ARG_POINTER(_count);
3172 0 : NS_ENSURE_ARG_POINTER(_observers);
3173 :
3174 0 : *_count = 0;
3175 0 : *_observers = nullptr;
3176 :
3177 0 : if (!mCanNotify)
3178 0 : return NS_OK;
3179 :
3180 0 : nsCOMArray<nsINavBookmarkObserver> observers;
3181 :
3182 : // First add the category cache observers.
3183 0 : mCacheObservers.GetEntries(observers);
3184 :
3185 : // Then add the other observers.
3186 0 : for (uint32_t i = 0; i < mObservers.Length(); ++i) {
3187 0 : const nsCOMPtr<nsINavBookmarkObserver> &observer = mObservers.ElementAt(i).GetValue();
3188 : // Skip nullified weak observers.
3189 0 : if (observer)
3190 0 : observers.AppendElement(observer);
3191 : }
3192 :
3193 0 : if (observers.Count() == 0)
3194 0 : return NS_OK;
3195 :
3196 0 : *_count = observers.Count();
3197 0 : observers.Forget(_observers);
3198 :
3199 0 : return NS_OK;
3200 : }
3201 :
3202 : void
3203 0 : nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
3204 : {
3205 0 : nsCOMPtr<nsIURI> uri;
3206 0 : MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aData.bookmark.url));
3207 : // Notify the visit only if we have a valid uri, otherwise the observer
3208 : // couldn't gather enough data from the notification.
3209 : // This should be false only if there's a bug in the code preceding us.
3210 0 : if (uri) {
3211 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3212 : nsINavBookmarkObserver,
3213 : OnItemVisited(aData.bookmark.id,
3214 : aData.visitId,
3215 : aData.time,
3216 : aData.transitionType,
3217 : uri,
3218 : aData.bookmark.parentId,
3219 : aData.bookmark.guid,
3220 : aData.bookmark.parentGuid));
3221 : }
3222 0 : }
3223 :
3224 : void
3225 0 : nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
3226 : {
3227 : // A guid must always be defined.
3228 0 : MOZ_ASSERT(!aData.bookmark.guid.IsEmpty());
3229 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3230 : nsINavBookmarkObserver,
3231 : OnItemChanged(aData.bookmark.id,
3232 : aData.property,
3233 : aData.isAnnotation,
3234 : aData.newValue,
3235 : aData.bookmark.lastModified,
3236 : aData.bookmark.type,
3237 : aData.bookmark.parentId,
3238 : aData.bookmark.guid,
3239 : aData.bookmark.parentGuid,
3240 : aData.oldValue,
3241 : // We specify the default source here because
3242 : // this method is only called for history
3243 : // visits, and we don't track sources in
3244 : // history.
3245 : SOURCE_DEFAULT));
3246 0 : }
3247 :
3248 : ////////////////////////////////////////////////////////////////////////////////
3249 : //// nsIObserver
3250 :
3251 : NS_IMETHODIMP
3252 0 : nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
3253 : const char16_t *aData)
3254 : {
3255 0 : NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
3256 :
3257 0 : if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
3258 : // Stop Observing annotations.
3259 0 : nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
3260 0 : if (annosvc) {
3261 0 : annosvc->RemoveObserver(this);
3262 : }
3263 : }
3264 0 : else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
3265 : // Don't even try to notify observers from this point on, the category
3266 : // cache would init services that could try to use our APIs.
3267 0 : mCanNotify = false;
3268 0 : mObservers.Clear();
3269 : }
3270 :
3271 0 : return NS_OK;
3272 : }
3273 :
3274 : ////////////////////////////////////////////////////////////////////////////////
3275 : //// nsINavHistoryObserver
3276 :
3277 : NS_IMETHODIMP
3278 0 : nsNavBookmarks::OnBeginUpdateBatch()
3279 : {
3280 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3281 : nsINavBookmarkObserver, OnBeginUpdateBatch());
3282 0 : return NS_OK;
3283 : }
3284 :
3285 :
3286 : NS_IMETHODIMP
3287 0 : nsNavBookmarks::OnEndUpdateBatch()
3288 : {
3289 0 : if (mBatching) {
3290 0 : mBatching = false;
3291 : }
3292 :
3293 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3294 : nsINavBookmarkObserver, OnEndUpdateBatch());
3295 0 : return NS_OK;
3296 : }
3297 :
3298 :
3299 : NS_IMETHODIMP
3300 1 : nsNavBookmarks::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
3301 : int64_t aSessionID, int64_t aReferringID,
3302 : uint32_t aTransitionType, const nsACString& aGUID,
3303 : bool aHidden, uint32_t aVisitCount, uint32_t aTyped,
3304 : const nsAString& aLastKnownTitle)
3305 : {
3306 1 : NS_ENSURE_ARG(aURI);
3307 :
3308 : // If the page is bookmarked, notify observers for each associated bookmark.
3309 2 : ItemVisitData visitData;
3310 1 : nsresult rv = aURI->GetSpec(visitData.bookmark.url);
3311 1 : NS_ENSURE_SUCCESS(rv, rv);
3312 1 : visitData.visitId = aVisitId;
3313 1 : visitData.time = aTime;
3314 1 : visitData.transitionType = aTransitionType;
3315 :
3316 : RefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
3317 2 : new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
3318 1 : notifier->Init();
3319 1 : return NS_OK;
3320 : }
3321 :
3322 :
3323 : NS_IMETHODIMP
3324 0 : nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
3325 : const nsACString& aGUID,
3326 : uint16_t aReason)
3327 : {
3328 0 : return NS_OK;
3329 : }
3330 :
3331 :
3332 : NS_IMETHODIMP
3333 0 : nsNavBookmarks::OnClearHistory()
3334 : {
3335 : // TODO(bryner): we should notify on visited-time change for all URIs
3336 0 : return NS_OK;
3337 : }
3338 :
3339 :
3340 : NS_IMETHODIMP
3341 0 : nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
3342 : const nsAString& aPageTitle,
3343 : const nsACString& aGUID)
3344 : {
3345 : // NOOP. We don't consume page titles from moz_places anymore.
3346 : // Title-change notifications are sent from SetItemTitle.
3347 0 : return NS_OK;
3348 : }
3349 :
3350 :
3351 : NS_IMETHODIMP
3352 2 : nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
3353 : int32_t aNewFrecency,
3354 : const nsACString& aGUID,
3355 : bool aHidden,
3356 : PRTime aLastVisitDate)
3357 : {
3358 2 : return NS_OK;
3359 : }
3360 :
3361 :
3362 : NS_IMETHODIMP
3363 0 : nsNavBookmarks::OnManyFrecenciesChanged()
3364 : {
3365 0 : return NS_OK;
3366 : }
3367 :
3368 :
3369 : NS_IMETHODIMP
3370 0 : nsNavBookmarks::OnPageChanged(nsIURI* aURI,
3371 : uint32_t aChangedAttribute,
3372 : const nsAString& aNewValue,
3373 : const nsACString& aGUID)
3374 : {
3375 0 : NS_ENSURE_ARG(aURI);
3376 :
3377 : nsresult rv;
3378 0 : if (aChangedAttribute == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
3379 0 : ItemChangeData changeData;
3380 0 : rv = aURI->GetSpec(changeData.bookmark.url);
3381 0 : NS_ENSURE_SUCCESS(rv, rv);
3382 0 : changeData.property = NS_LITERAL_CSTRING("favicon");
3383 0 : changeData.isAnnotation = false;
3384 0 : changeData.newValue = NS_ConvertUTF16toUTF8(aNewValue);
3385 0 : changeData.bookmark.lastModified = 0;
3386 0 : changeData.bookmark.type = TYPE_BOOKMARK;
3387 :
3388 : // Favicons may be set to either pure URIs or to folder URIs
3389 : bool isPlaceURI;
3390 0 : rv = aURI->SchemeIs("place", &isPlaceURI);
3391 0 : NS_ENSURE_SUCCESS(rv, rv);
3392 0 : if (isPlaceURI) {
3393 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
3394 0 : NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
3395 :
3396 0 : nsCOMArray<nsNavHistoryQuery> queries;
3397 0 : nsCOMPtr<nsNavHistoryQueryOptions> options;
3398 0 : rv = history->QueryStringToQueryArray(changeData.bookmark.url,
3399 0 : &queries, getter_AddRefs(options));
3400 0 : NS_ENSURE_SUCCESS(rv, rv);
3401 :
3402 0 : if (queries.Count() == 1 && queries[0]->Folders().Length() == 1) {
3403 : // Fetch missing data.
3404 0 : rv = FetchItemInfo(queries[0]->Folders()[0], changeData.bookmark);
3405 0 : NS_ENSURE_SUCCESS(rv, rv);
3406 0 : NotifyItemChanged(changeData);
3407 : }
3408 : }
3409 : else {
3410 : RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
3411 0 : new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
3412 0 : notifier->Init();
3413 : }
3414 : }
3415 0 : return NS_OK;
3416 : }
3417 :
3418 :
3419 : NS_IMETHODIMP
3420 0 : nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime,
3421 : const nsACString& aGUID,
3422 : uint16_t aReason, uint32_t aTransitionType)
3423 : {
3424 0 : NS_ENSURE_ARG(aURI);
3425 :
3426 : // Notify "cleartime" only if all visits to the page have been removed.
3427 0 : if (!aVisitTime) {
3428 : // If the page is bookmarked, notify observers for each associated bookmark.
3429 0 : ItemChangeData changeData;
3430 0 : nsresult rv = aURI->GetSpec(changeData.bookmark.url);
3431 0 : NS_ENSURE_SUCCESS(rv, rv);
3432 0 : changeData.property = NS_LITERAL_CSTRING("cleartime");
3433 0 : changeData.isAnnotation = false;
3434 0 : changeData.bookmark.lastModified = 0;
3435 0 : changeData.bookmark.type = TYPE_BOOKMARK;
3436 :
3437 : RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
3438 0 : new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
3439 0 : notifier->Init();
3440 : }
3441 0 : return NS_OK;
3442 : }
3443 :
3444 :
3445 : // nsIAnnotationObserver
3446 :
3447 : NS_IMETHODIMP
3448 0 : nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
3449 : {
3450 0 : return NS_OK;
3451 : }
3452 :
3453 :
3454 : NS_IMETHODIMP
3455 0 : nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName,
3456 : uint16_t aSource)
3457 : {
3458 0 : BookmarkData bookmark;
3459 0 : nsresult rv = FetchItemInfo(aItemId, bookmark);
3460 0 : NS_ENSURE_SUCCESS(rv, rv);
3461 :
3462 0 : bookmark.lastModified = RoundedPRNow();
3463 0 : rv = SetItemDateInternal(LAST_MODIFIED, DetermineSyncChangeDelta(aSource),
3464 0 : bookmark.id, bookmark.lastModified);
3465 0 : NS_ENSURE_SUCCESS(rv, rv);
3466 :
3467 0 : NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3468 : nsINavBookmarkObserver,
3469 : OnItemChanged(bookmark.id,
3470 : aName,
3471 : true,
3472 : EmptyCString(),
3473 : bookmark.lastModified,
3474 : bookmark.type,
3475 : bookmark.parentId,
3476 : bookmark.guid,
3477 : bookmark.parentGuid,
3478 : EmptyCString(),
3479 : aSource));
3480 0 : return NS_OK;
3481 : }
3482 :
3483 :
3484 : NS_IMETHODIMP
3485 0 : nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
3486 : {
3487 0 : return NS_OK;
3488 : }
3489 :
3490 :
3491 : NS_IMETHODIMP
3492 0 : nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName,
3493 : uint16_t aSource)
3494 : {
3495 : // As of now this is doing the same as OnItemAnnotationSet, so just forward
3496 : // the call.
3497 0 : nsresult rv = OnItemAnnotationSet(aItemId, aName, aSource);
3498 0 : NS_ENSURE_SUCCESS(rv, rv);
3499 0 : return NS_OK;
3500 : }
|