Wed, 31 Dec 2014 06:09:35 +0100
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 | } |