toolkit/components/places/nsNavBookmarks.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial