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