bug 313956: expand installer .exe contents to make complete mar. r=ted.
[gecko.git] / toolkit / components / places / nsNavBookmarks.cpp
blob54a0ed70025f5d7f27dd264e37ad4ccf435b5a12
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Places.
17 * The Initial Developer of the Original Code is
18 * Google Inc.
19 * Portions created by the Initial Developer are Copyright (C) 2005
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Brian Ryner <bryner@brianryner.com> (original author)
24 * Dietrich Ayala <dietrich@mozilla.com>
25 * Drew Willcoxon <adw@mozilla.com>
26 * Marco Bonardo <mak77@bonardo.net>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include "nsAppDirectoryServiceDefs.h"
43 #include "nsNavBookmarks.h"
44 #include "nsNavHistory.h"
45 #include "mozStorageHelper.h"
46 #include "nsIServiceManager.h"
47 #include "nsNetUtil.h"
48 #include "nsIDynamicContainer.h"
49 #include "nsUnicharUtils.h"
50 #include "nsFaviconService.h"
51 #include "nsAnnotationService.h"
52 #include "nsPrintfCString.h"
53 #include "nsIUUIDGenerator.h"
54 #include "prprf.h"
55 #include "nsILivemarkService.h"
56 #include "nsPlacesTriggers.h"
57 #include "nsPlacesTables.h"
58 #include "nsPlacesIndexes.h"
59 #include "nsPlacesMacros.h"
60 #include "Helpers.h"
62 #include "mozilla/FunctionTimer.h"
64 #define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_SIZE 64
66 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_ID = 0;
67 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Type = 1;
68 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_PlaceID = 2;
69 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Parent = 3;
70 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Position = 4;
71 const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Title = 5;
73 // These columns sit to the right of the kGetInfoIndex_* columns.
74 const PRInt32 nsNavBookmarks::kGetChildrenIndex_Position = 13;
75 const PRInt32 nsNavBookmarks::kGetChildrenIndex_Type = 14;
76 const PRInt32 nsNavBookmarks::kGetChildrenIndex_PlaceID = 15;
77 const PRInt32 nsNavBookmarks::kGetChildrenIndex_ServiceContractId = 16;
79 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_ID = 0;
80 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_URI = 1;
81 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Title = 2;
82 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Position = 3;
83 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_PlaceID = 4;
84 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Parent = 5;
85 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Type = 6;
86 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_ServiceContractId = 7;
87 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_DateAdded = 8;
88 const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_LastModified = 9;
90 using namespace mozilla::places;
92 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
94 #define BOOKMARKS_ANNO_PREFIX "bookmarks/"
95 #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
96 #define GUID_ANNO NS_LITERAL_CSTRING("placesInternal/GUID")
97 #define READ_ONLY_ANNO NS_LITERAL_CSTRING("placesInternal/READ_ONLY")
100 namespace {
102 struct keywordSearchData
104 PRInt64 itemId;
105 nsString keyword;
108 PLDHashOperator
109 SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey,
110 const nsString aValue,
111 void* aUserArg)
113 keywordSearchData* data = reinterpret_cast<keywordSearchData*>(aUserArg);
114 if (data->keyword.Equals(aValue)) {
115 data->itemId = aKey;
116 return PL_DHASH_STOP;
118 return PL_DHASH_NEXT;
121 template<typename Method, typename DataType>
122 class AsyncGetBookmarksForURI : public AsyncStatementCallback
124 public:
125 AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
126 Method aCallback,
127 const DataType& aData)
128 : mBookmarksSvc(aBookmarksSvc)
129 , mCallback(aCallback)
130 , mData(aData)
134 void Init()
136 nsCOMPtr<mozIStorageStatement> stmt =
137 mBookmarksSvc->GetStatementById(DB_GET_BOOKMARKS_FOR_URI);
138 if (stmt) {
139 (void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData.uri);
140 nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
141 (void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
145 NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet)
147 nsCOMPtr<mozIStorageRow> row;
148 while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
149 nsresult rv = row->GetInt64(0, &mData.itemId);
150 NS_ENSURE_SUCCESS(rv, rv);
151 if (mCallback) {
152 ((*mBookmarksSvc).*mCallback)(mData);
155 return NS_OK;
158 private:
159 nsRefPtr<nsNavBookmarks> mBookmarksSvc;
160 Method mCallback;
161 DataType mData;
164 } // Anonymous namespace.
167 nsNavBookmarks::nsNavBookmarks() : mItemCount(0)
168 , mRoot(0)
169 , mMenuRoot(0)
170 , mTagsRoot(0)
171 , mUnfiledRoot(0)
172 , mToolbarRoot(0)
173 , mCanNotify(false)
174 , mCacheObservers("bookmark-observers")
175 , mShuttingDown(false)
176 , mBatching(false)
178 NS_ASSERTION(!gBookmarksService,
179 "Attempting to create two instances of the service!");
180 gBookmarksService = this;
184 nsNavBookmarks::~nsNavBookmarks()
186 NS_ASSERTION(gBookmarksService == this,
187 "Deleting a non-singleton instance of the service");
188 if (gBookmarksService == this)
189 gBookmarksService = nsnull;
193 NS_IMPL_ISUPPORTS3(nsNavBookmarks,
194 nsINavBookmarksService,
195 nsINavHistoryObserver,
196 nsIAnnotationObserver)
199 nsresult
200 nsNavBookmarks::Init()
202 NS_TIME_FUNCTION;
204 nsNavHistory* history = nsNavHistory::GetHistoryService();
205 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
206 mDBConn = history->GetStorageConnection();
207 NS_ENSURE_STATE(mDBConn);
209 // Get our read-only cloned connection.
210 nsresult rv = mDBConn->Clone(PR_TRUE, getter_AddRefs(mDBReadOnlyConn));
211 NS_ENSURE_SUCCESS(rv, rv);
213 PRUint16 dbStatus;
214 rv = history->GetDatabaseStatus(&dbStatus);
215 NS_ENSURE_SUCCESS(rv, rv);
216 rv = InitRoots(dbStatus != nsINavHistoryService::DATABASE_STATUS_OK);
217 NS_ENSURE_SUCCESS(rv, rv);
219 mCanNotify = true;
221 // Observe annotations.
222 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
223 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
224 annosvc->AddObserver(this);
226 // Allows us to notify on title changes. MUST BE LAST so it is impossible
227 // to fail after this call, or the history service will have a reference to
228 // us and we won't go away.
229 history->AddObserver(this, PR_FALSE);
231 // DO NOT PUT STUFF HERE that can fail. See observer comment above.
233 return NS_OK;
238 * All commands that initialize the database schema should be here.
239 * This is called from history init after database connection has been
240 * established.
242 nsresult // static
243 nsNavBookmarks::InitTables(mozIStorageConnection* aDBConn)
245 PRBool exists;
246 nsresult rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks"), &exists);
247 NS_ENSURE_SUCCESS(rv, rv);
248 if (!exists) {
249 rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
250 NS_ENSURE_SUCCESS(rv, rv);
252 // This index will make it faster to determine if a given item is
253 // bookmarked (used by history queries and vacuuming, for example).
254 // Making it compound with "type" speeds up type-differentiation
255 // queries, such as expiration and search.
256 rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
257 NS_ENSURE_SUCCESS(rv, rv);
259 // The most common operation is to find the children given a parent and position.
260 rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
261 NS_ENSURE_SUCCESS(rv, rv);
263 // fast access to lastModified is useful during sync and to get
264 // last modified bookmark title for tags container's children.
265 rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
266 NS_ENSURE_SUCCESS(rv, rv);
268 // Selecting by guid needs to be fast.
269 rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
270 NS_ENSURE_SUCCESS(rv, rv);
273 rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_roots"), &exists);
274 NS_ENSURE_SUCCESS(rv, rv);
275 if (!exists) {
276 rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_ROOTS);
277 NS_ENSURE_SUCCESS(rv, rv);
280 rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_keywords"), &exists);
281 NS_ENSURE_SUCCESS(rv, rv);
282 if (!exists) {
283 rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
284 NS_ENSURE_SUCCESS(rv, rv);
286 // Create trigger to update as well
287 rv = aDBConn->ExecuteSimpleSQL(CREATE_KEYWORD_VALIDITY_TRIGGER);
288 NS_ENSURE_SUCCESS(rv, rv);
291 return NS_OK;
295 mozIStorageStatement*
296 nsNavBookmarks::GetStatement(const nsCOMPtr<mozIStorageStatement>& aStmt)
298 if (mShuttingDown)
299 return nsnull;
301 // Double ordering covers possible lastModified ties, that could happen when
302 // importing, syncing or due to extensions.
303 // Note: not using a JOIN is cheaper in this case.
304 RETURN_IF_STMT(mDBFindURIBookmarks, NS_LITERAL_CSTRING(
305 "SELECT b.id "
306 "FROM moz_bookmarks b "
307 "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
308 "ORDER BY b.lastModified DESC, b.id DESC "));
310 // Select all children of a given folder, sorted by position.
311 // This is a LEFT JOIN because not all bookmarks types have a place.
312 // We construct a result where the first columns exactly match those returned
313 // by mDBGetURLPageInfo, and additionally contains columns for position,
314 // item_child, and folder_child from moz_bookmarks.
315 RETURN_IF_STMT(mDBGetChildren, NS_LITERAL_CSTRING(
316 "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
317 "h.last_visit_date, f.url, null, b.id, b.dateAdded, b.lastModified, "
318 "b.parent, null, b.position, b.type, b.fk, b.folder_type "
319 "FROM moz_bookmarks b "
320 "LEFT JOIN moz_places h ON b.fk = h.id "
321 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
322 "WHERE b.parent = :parent "
323 "ORDER BY b.position ASC"));
325 // Count all of the children of a given folder and checks that it exists.
326 RETURN_IF_STMT(mDBFolderCount, NS_LITERAL_CSTRING(
327 "SELECT COUNT(*), "
328 "(SELECT id FROM moz_bookmarks WHERE id = :parent) "
329 "FROM moz_bookmarks WHERE parent = :parent"));
331 RETURN_IF_STMT(mDBGetChildAt, NS_LITERAL_CSTRING(
332 "SELECT id, fk, type FROM moz_bookmarks "
333 "WHERE parent = :parent AND position = :item_index"));
335 // Get bookmark/folder/separator properties.
336 // This is a LEFT JOIN because not all bookmarks types have a place.
337 RETURN_IF_STMT(mDBGetItemProperties, NS_LITERAL_CSTRING(
338 "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
339 "b.folder_type, b.dateAdded, b.lastModified "
340 "FROM moz_bookmarks b "
341 "LEFT JOIN moz_places h ON h.id = b.fk "
342 "WHERE b.id = :item_id"));
344 RETURN_IF_STMT(mDBGetItemIdForGUID, NS_LITERAL_CSTRING(
345 "SELECT item_id FROM moz_items_annos "
346 "WHERE content = :guid "
347 "LIMIT 1"));
349 RETURN_IF_STMT(mDBInsertBookmark, NS_LITERAL_CSTRING(
350 "INSERT INTO moz_bookmarks "
351 "(id, fk, type, parent, position, title, folder_type, "
352 "dateAdded, lastModified, guid) "
353 "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
354 ":item_title, :folder_type, :date_added, :last_modified, "
355 "GENERATE_GUID())"));
357 // Just select position since it's just an int32 and may be faster.
358 // We don't actually care about the data, just whether there is any.
359 RETURN_IF_STMT(mDBIsBookmarkedInDatabase, NS_LITERAL_CSTRING(
360 "SELECT 1 FROM moz_bookmarks WHERE fk = :page_id"));
362 RETURN_IF_STMT(mDBIsURIBookmarkedInDatabase, NS_LITERAL_CSTRING(
363 "SELECT 1 FROM moz_bookmarks b "
364 "JOIN moz_places h ON b.fk = h.id "
365 "WHERE h.url = :page_url"));
367 // Checks to make sure a place id is a bookmark, and isn't a livemark.
368 RETURN_IF_STMT(mDBIsRealBookmark, NS_LITERAL_CSTRING(
369 "SELECT id "
370 "FROM moz_bookmarks "
371 "WHERE fk = :page_id "
372 "AND type = :item_type "
373 "AND parent NOT IN ("
374 "SELECT a.item_id "
375 "FROM moz_items_annos a "
376 "JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id "
377 "WHERE n.name = :anno_name"
378 ") "));
380 RETURN_IF_STMT(mDBGetLastBookmarkID, NS_LITERAL_CSTRING(
381 "SELECT id "
382 "FROM moz_bookmarks "
383 "ORDER BY ROWID DESC "
384 "LIMIT 1"));
386 // lastModified is set to the same value as dateAdded. We do this for
387 // performance reasons, since it will allow us to use an index to sort items
388 // by date.
389 RETURN_IF_STMT(mDBSetItemDateAdded, NS_LITERAL_CSTRING(
390 "UPDATE moz_bookmarks SET dateAdded = :date, lastModified = :date "
391 "WHERE id = :item_id"));
393 RETURN_IF_STMT(mDBSetItemLastModified, NS_LITERAL_CSTRING(
394 "UPDATE moz_bookmarks SET lastModified = :date WHERE id = :item_id"));
396 RETURN_IF_STMT(mDBSetItemIndex, NS_LITERAL_CSTRING(
397 "UPDATE moz_bookmarks SET position = :item_index WHERE id = :item_id"));
399 RETURN_IF_STMT(mDBGetKeywordForURI, NS_LITERAL_CSTRING(
400 "SELECT k.keyword "
401 "FROM moz_places h "
402 "JOIN moz_bookmarks b ON b.fk = h.id "
403 "JOIN moz_keywords k ON k.id = b.keyword_id "
404 "WHERE h.url = :page_url "));
406 RETURN_IF_STMT(mDBAdjustPosition, NS_LITERAL_CSTRING(
407 "UPDATE moz_bookmarks SET position = position + :delta "
408 "WHERE parent = :parent "
409 "AND position BETWEEN :from_index AND :to_index"));
411 RETURN_IF_STMT(mDBRemoveItem, NS_LITERAL_CSTRING(
412 "DELETE FROM moz_bookmarks WHERE id = :item_id"));
414 RETURN_IF_STMT(mDBGetLastChildId, NS_LITERAL_CSTRING(
415 "SELECT id FROM moz_bookmarks WHERE parent = :parent "
416 "ORDER BY position DESC LIMIT 1"));
418 RETURN_IF_STMT(mDBMoveItem, NS_LITERAL_CSTRING(
419 "UPDATE moz_bookmarks SET parent = :parent, position = :item_index "
420 "WHERE id = :item_id "));
422 RETURN_IF_STMT(mDBSetItemTitle, NS_LITERAL_CSTRING(
423 "UPDATE moz_bookmarks SET title = :item_title, lastModified = :date "
424 "WHERE id = :item_id "));
426 RETURN_IF_STMT(mDBChangeBookmarkURI, NS_LITERAL_CSTRING(
427 "UPDATE moz_bookmarks SET fk = :page_id, lastModified = :date "
428 "WHERE id = :item_id "));
430 // The next query finds the bookmarked ancestors in a redirects chain.
431 // It won't go further than 3 levels of redirects (a->b->c->your_place_id).
432 // To make this path 100% correct (up to any level) we would need either:
433 // - A separate hash, build through recursive querying of the database.
434 // This solution was previously implemented, but it had a negative effect
435 // on startup since at each startup we have to recursively query the
436 // database to rebuild a hash that is always the same across sessions.
437 // It must be updated at each visit and bookmarks change too. The code to
438 // manage it is complex and prone to errors, sometimes causing incorrect
439 // data fetches (for example wrong favicon for a redirected bookmark).
440 // - A better way to track redirects for a visit.
441 // We would need a separate table to track redirects, in the table we would
442 // have visit_id, redirect_session. To get all sources for
443 // a visit then we could just join this table and get all visit_id that
444 // are in the same redirect_session as our visit. This has the drawback
445 // that we can't ensure data integrity in the downgrade -> upgrade path,
446 // since an old version would not update the table on new visits.
448 // For most cases these levels of redirects should be fine though, it's hard
449 // to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
451 // As a bonus the query also checks first if place_id is already a bookmark,
452 // so you don't have to check that apart.
454 #define COALESCE_PLACEID \
455 "COALESCE(greatgrandparent.place_id, grandparent.place_id, parent.place_id) "
457 nsCString redirectsFragment =
458 nsPrintfCString(3, "%d,%d",
459 nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
460 nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
462 RETURN_IF_STMT(mDBFindRedirectedBookmark, NS_LITERAL_CSTRING(
463 "SELECT "
464 "(SELECT url FROM moz_places WHERE id = :page_id) "
465 "FROM moz_bookmarks b "
466 "WHERE b.fk = :page_id "
467 "UNION ALL " // Not directly bookmarked.
468 "SELECT "
469 "(SELECT url FROM moz_places WHERE id = " COALESCE_PLACEID ") "
470 "FROM moz_historyvisits self "
471 "JOIN moz_bookmarks b ON b.fk = " COALESCE_PLACEID
472 "LEFT JOIN moz_historyvisits parent ON parent.id = self.from_visit "
473 "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
474 "AND parent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
475 "LEFT JOIN moz_historyvisits greatgrandparent ON grandparent.from_visit = greatgrandparent.id "
476 "AND grandparent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
477 "WHERE self.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
478 "AND self.place_id = :page_id "
479 "LIMIT 1 " // Stop at the first result.
481 #undef COALESCE_PLACEID
483 return nsnull;
487 nsresult
488 nsNavBookmarks::FinalizeStatements() {
489 mShuttingDown = true;
491 mozIStorageStatement* stmts[] = {
492 mDBGetChildren,
493 mDBFindURIBookmarks,
494 mDBFolderCount,
495 mDBGetChildAt,
496 mDBGetItemProperties,
497 mDBGetItemIdForGUID,
498 mDBInsertBookmark,
499 mDBIsBookmarkedInDatabase,
500 mDBIsRealBookmark,
501 mDBGetLastBookmarkID,
502 mDBSetItemDateAdded,
503 mDBSetItemLastModified,
504 mDBSetItemIndex,
505 mDBGetKeywordForURI,
506 mDBAdjustPosition,
507 mDBRemoveItem,
508 mDBGetLastChildId,
509 mDBMoveItem,
510 mDBSetItemTitle,
511 mDBChangeBookmarkURI,
512 mDBIsURIBookmarkedInDatabase,
513 mDBFindRedirectedBookmark,
516 for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(stmts); i++) {
517 nsresult rv = nsNavHistory::FinalizeStatement(stmts[i]);
518 NS_ENSURE_SUCCESS(rv, rv);
521 // Since we are shutting down, close the read-only connection.
522 (void)mDBReadOnlyConn->AsyncClose(nsnull);
524 #ifdef DEBUG
525 // Sanity check that all bookmarks have guids.
526 nsCOMPtr<mozIStorageStatement> stmt;
527 nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
528 "SELECT * "
529 "FROM moz_bookmarks "
530 "WHERE guid IS NULL "
531 ), getter_AddRefs(stmt));
532 NS_ENSURE_SUCCESS(rv, rv);
534 PRBool haveNullGuids;
535 rv = stmt->ExecuteStep(&haveNullGuids);
536 NS_ENSURE_SUCCESS(rv, rv);
537 NS_ASSERTION(!haveNullGuids,
538 "Someone added a bookmark without adding a GUID!");
539 #endif
541 return NS_OK;
545 nsresult
546 nsNavBookmarks::InitRoots(bool aForceCreate)
548 nsCOMPtr<mozIStorageStatement> stmt;
549 nsresult rv = mDBReadOnlyConn->CreateStatement(NS_LITERAL_CSTRING(
550 "SELECT root_name, folder_id FROM moz_bookmarks_roots"
551 ), getter_AddRefs(stmt));
552 NS_ENSURE_SUCCESS(rv, rv);
554 PRBool hasResult;
555 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
556 nsCAutoString rootName;
557 rv = stmt->GetUTF8String(0, rootName);
558 NS_ENSURE_SUCCESS(rv, rv);
559 PRInt64 rootId;
560 rv = stmt->GetInt64(1, &rootId);
561 NS_ENSURE_SUCCESS(rv, rv);
562 NS_ABORT_IF_FALSE(rootId != 0, "Root id is 0, that is an invalid value.");
564 if (rootName.EqualsLiteral("places")) {
565 mRoot = rootId;
567 else if (rootName.EqualsLiteral("menu")) {
568 mMenuRoot = rootId;
570 else if (rootName.EqualsLiteral("toolbar")) {
571 mToolbarRoot = rootId;
573 else if (rootName.EqualsLiteral("tags")) {
574 mTagsRoot = rootId;
576 else if (rootName.EqualsLiteral("unfiled")) {
577 mUnfiledRoot = rootId;
581 if (aForceCreate) {
582 nsNavHistory* history = nsNavHistory::GetHistoryService();
583 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
584 nsIStringBundle* bundle = history->GetBundle();
585 NS_ENSURE_TRUE(bundle, NS_ERROR_OUT_OF_MEMORY);
587 mozStorageTransaction transaction(mDBConn, PR_FALSE);
589 rv = CreateRoot(NS_LITERAL_CSTRING("places"), &mRoot, 0,
590 nsnull, nsnull);
591 NS_ENSURE_SUCCESS(rv, rv);
593 rv = CreateRoot(NS_LITERAL_CSTRING("menu"), &mMenuRoot, mRoot, bundle,
594 NS_LITERAL_STRING("BookmarksMenuFolderTitle").get());
595 NS_ENSURE_SUCCESS(rv, rv);
597 rv = CreateRoot(NS_LITERAL_CSTRING("toolbar"), &mToolbarRoot, mRoot, bundle,
598 NS_LITERAL_STRING("BookmarksToolbarFolderTitle").get());
599 NS_ENSURE_SUCCESS(rv, rv);
601 rv = CreateRoot(NS_LITERAL_CSTRING("tags"), &mTagsRoot, mRoot, bundle,
602 NS_LITERAL_STRING("TagsFolderTitle").get());
603 NS_ENSURE_SUCCESS(rv, rv);
605 rv = CreateRoot(NS_LITERAL_CSTRING("unfiled"), &mUnfiledRoot, mRoot, bundle,
606 NS_LITERAL_STRING("UnsortedBookmarksFolderTitle").get());
607 NS_ENSURE_SUCCESS(rv, rv);
609 rv = transaction.Commit();
610 NS_ENSURE_SUCCESS(rv, rv);
612 if (!mBatching) {
613 ForceWALCheckpoint(mDBConn);
617 return NS_OK;
621 nsresult
622 nsNavBookmarks::CreateRoot(const nsCString& name,
623 PRInt64* _itemId,
624 PRInt64 aParentId,
625 nsIStringBundle* aBundle,
626 const PRUnichar* aTitleStringId)
628 nsresult rv;
630 if (*_itemId == 0) {
631 // The root does not exist. Create a new untitled folder for it.
632 rv = CreateFolder(aParentId, EmptyCString(), DEFAULT_INDEX, _itemId);
633 NS_ENSURE_SUCCESS(rv, rv);
635 // Create a entry in moz_bookmarks_roots to link the folder to the root.
636 nsCOMPtr<mozIStorageStatement> stmt;
637 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
638 "INSERT INTO moz_bookmarks_roots (root_name, folder_id) "
639 "VALUES (:root_name, :item_id)"
640 ), getter_AddRefs(stmt));
641 NS_ENSURE_SUCCESS(rv, rv);
642 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"), name);
643 NS_ENSURE_SUCCESS(rv, rv);
644 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
645 NS_ENSURE_SUCCESS(rv, rv);
646 rv = stmt->Execute();
647 NS_ENSURE_SUCCESS(rv, rv);
650 // Now set the title on the root. Notice we do this regardless, to take in
651 // could title changes when schema changes.
652 if (aTitleStringId) {
653 nsXPIDLString title;
654 rv = aBundle->GetStringFromName(aTitleStringId, getter_Copies(title));
655 NS_ENSURE_SUCCESS(rv, rv);
656 rv = SetItemTitle(*_itemId, NS_ConvertUTF16toUTF8(title));
657 NS_ENSURE_SUCCESS(rv, rv);
660 return NS_OK;
664 PRBool
665 nsNavBookmarks::IsRealBookmark(PRInt64 aPlaceId)
667 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsRealBookmark);
668 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
669 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
670 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_BOOKMARK);
671 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
672 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
673 NS_LITERAL_CSTRING(LMANNO_FEEDURI));
674 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
676 // If we get any rows, then there exists at least one bookmark corresponding
677 // to aPlaceId that is not a livemark item.
678 PRBool isBookmark;
679 rv = stmt->ExecuteStep(&isBookmark);
680 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExecuteStep failed");
681 if (NS_SUCCEEDED(rv))
682 return isBookmark;
684 return PR_FALSE;
688 // nsNavBookmarks::IsBookmarkedInDatabase
690 // This checks to see if the specified place_id is actually bookmarked.
692 nsresult
693 nsNavBookmarks::IsBookmarkedInDatabase(PRInt64 aPlaceId,
694 PRBool* aIsBookmarked)
696 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsBookmarkedInDatabase);
697 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
698 NS_ENSURE_SUCCESS(rv, rv);
699 rv = stmt->ExecuteStep(aIsBookmarked);
700 NS_ENSURE_SUCCESS(rv, rv);
701 return NS_OK;
705 nsresult
706 nsNavBookmarks::AdjustIndices(PRInt64 aFolderId,
707 PRInt32 aStartIndex,
708 PRInt32 aEndIndex,
709 PRInt32 aDelta)
711 NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= PR_INT32_MAX &&
712 aStartIndex <= aEndIndex, "Bad indices");
714 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBAdjustPosition);
715 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
716 NS_ENSURE_SUCCESS(rv, rv);
717 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
718 NS_ENSURE_SUCCESS(rv, rv);
719 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
720 NS_ENSURE_SUCCESS(rv, rv);
721 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
722 NS_ENSURE_SUCCESS(rv, rv);
724 rv = stmt->Execute();
725 NS_ENSURE_SUCCESS(rv, rv);
727 return NS_OK;
731 NS_IMETHODIMP
732 nsNavBookmarks::GetPlacesRoot(PRInt64* aRoot)
734 *aRoot = mRoot;
735 return NS_OK;
739 NS_IMETHODIMP
740 nsNavBookmarks::GetBookmarksMenuFolder(PRInt64* aRoot)
742 *aRoot = mMenuRoot;
743 return NS_OK;
747 NS_IMETHODIMP
748 nsNavBookmarks::GetToolbarFolder(PRInt64* aFolderId)
750 *aFolderId = mToolbarRoot;
751 return NS_OK;
755 NS_IMETHODIMP
756 nsNavBookmarks::GetTagsFolder(PRInt64* aRoot)
758 *aRoot = mTagsRoot;
759 return NS_OK;
763 NS_IMETHODIMP
764 nsNavBookmarks::GetUnfiledBookmarksFolder(PRInt64* aRoot)
766 *aRoot = mUnfiledRoot;
767 return NS_OK;
771 nsresult
772 nsNavBookmarks::InsertBookmarkInDB(PRInt64 aItemId,
773 PRInt64 aPlaceId,
774 enum ItemType aItemType,
775 PRInt64 aParentId,
776 PRInt32 aIndex,
777 const nsACString& aTitle,
778 PRTime aDateAdded,
779 PRTime aLastModified,
780 const nsAString& aServiceContractId,
781 PRInt64* _newItemId)
783 NS_ASSERTION(_newItemId, "Null pointer passed to InsertBookmarkInDB!");
785 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBInsertBookmark);
786 nsresult rv;
787 if (aItemId && aItemId != -1)
788 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
789 else
790 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
791 NS_ENSURE_SUCCESS(rv, rv);
793 if (aPlaceId && aPlaceId != -1)
794 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
795 else
796 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
797 NS_ENSURE_SUCCESS(rv, rv);
799 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
800 NS_ENSURE_SUCCESS(rv, rv);
801 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
802 NS_ENSURE_SUCCESS(rv, rv);
803 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
804 NS_ENSURE_SUCCESS(rv, rv);
806 // Support NULL titles.
807 if (aTitle.IsVoid())
808 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
809 else
810 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
811 NS_ENSURE_SUCCESS(rv, rv);
813 if (aServiceContractId.IsEmpty()) {
814 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("folder_type"));
816 else {
817 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("folder_type"),
818 aServiceContractId);
820 NS_ENSURE_SUCCESS(rv, rv);
822 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
823 NS_ENSURE_SUCCESS(rv, rv);
825 if (aLastModified) {
826 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
827 aLastModified);
829 else {
830 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
832 NS_ENSURE_SUCCESS(rv, rv);
834 rv = stmt->Execute();
835 NS_ENSURE_SUCCESS(rv, rv);
837 if (!aItemId || aItemId == -1) {
838 // Get the new inserted item id.
839 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(lastInsertIdStmt, mDBGetLastBookmarkID);
840 PRBool hasResult;
841 rv = lastInsertIdStmt->ExecuteStep(&hasResult);
842 NS_ENSURE_SUCCESS(rv, rv);
843 NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
844 rv = lastInsertIdStmt->GetInt64(0, _newItemId);
845 NS_ENSURE_SUCCESS(rv, rv);
847 else {
848 *_newItemId = aItemId;
851 // Update last modified date of the parent folder.
852 // XXX TODO: This should be done recursively for all ancestors, that would
853 // be slow without a nested tree though. See bug 408991.
854 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
855 aParentId, aDateAdded);
856 NS_ENSURE_SUCCESS(rv, rv);
858 return NS_OK;
862 NS_IMETHODIMP
863 nsNavBookmarks::InsertBookmark(PRInt64 aFolder,
864 nsIURI* aURI,
865 PRInt32 aIndex,
866 const nsACString& aTitle,
867 PRInt64* aNewBookmarkId)
869 NS_ENSURE_ARG(aURI);
870 NS_ENSURE_ARG_POINTER(aNewBookmarkId);
872 // You can pass -1 to indicate append, but no other negative number is allowed
873 if (aIndex < nsINavBookmarksService::DEFAULT_INDEX)
874 return NS_ERROR_INVALID_ARG;
876 mozStorageTransaction transaction(mDBConn, PR_FALSE);
878 nsNavHistory* history = nsNavHistory::GetHistoryService();
879 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
881 // This is really a place ID
882 PRInt64 childID;
883 nsresult rv = history->GetUrlIdFor(aURI, &childID, PR_TRUE);
884 NS_ENSURE_SUCCESS(rv, rv);
886 PRInt32 index;
887 PRInt32 folderCount;
888 rv = FolderCount(aFolder, &folderCount);
889 NS_ENSURE_SUCCESS(rv, rv);
890 if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
891 aIndex >= folderCount) {
892 index = folderCount;
894 else {
895 index = aIndex;
896 rv = AdjustIndices(aFolder, index, PR_INT32_MAX, 1);
897 NS_ENSURE_SUCCESS(rv, rv);
900 rv = InsertBookmarkInDB(-1, childID, BOOKMARK, aFolder, index,
901 aTitle, PR_Now(), nsnull, EmptyString(),
902 aNewBookmarkId);
903 NS_ENSURE_SUCCESS(rv, rv);
905 // XXX
906 // 0n import / fx 2 migration, is the frecency work going to slow us down?
907 // We might want to skip this stuff, as well as the frecency work
908 // caused by GetUrlIdFor() which calls InternalAddNewPage().
909 // If we do skip this, after import, we will
910 // need to call FixInvalidFrecenciesForExcludedPlaces().
911 // We might need to call it anyways, if items aren't properly annotated
912 // as livemarks feeds yet.
914 nsCAutoString url;
915 rv = aURI->GetSpec(url);
916 NS_ENSURE_SUCCESS(rv, rv);
918 // Re-calculate the frecency for this moz_place entry since it was set to -1.
919 rv = history->UpdateFrecency(childID);
920 NS_ENSURE_SUCCESS(rv, rv);
922 rv = transaction.Commit();
923 NS_ENSURE_SUCCESS(rv, rv);
925 if (!mBatching) {
926 ForceWALCheckpoint(mDBConn);
929 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
930 nsINavBookmarkObserver,
931 OnItemAdded(*aNewBookmarkId, aFolder, index, TYPE_BOOKMARK,
932 aURI));
934 // If the bookmark has been added to a tag container, notify all
935 // bookmark-folder result nodes which contain a bookmark for the new
936 // bookmark's url
937 PRInt64 grandParentId;
938 rv = GetFolderIdForItem(aFolder, &grandParentId);
939 NS_ENSURE_SUCCESS(rv, rv);
940 if (grandParentId == mTagsRoot) {
941 // query for all bookmarks for that URI, notify for each
942 nsTArray<PRInt64> bookmarks;
943 rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
944 NS_ENSURE_SUCCESS(rv, rv);
946 if (bookmarks.Length()) {
947 for (PRUint32 i = 0; i < bookmarks.Length(); i++) {
948 // Don't notify to the same tag entry we just added.
949 if (bookmarks[i] == *aNewBookmarkId) {
950 continue;
953 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
954 nsINavBookmarkObserver,
955 OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"),
956 PR_FALSE, EmptyCString(), 0,
957 TYPE_BOOKMARK));
961 return NS_OK;
965 NS_IMETHODIMP
966 nsNavBookmarks::RemoveItem(PRInt64 aItemId)
968 NS_ENSURE_TRUE(aItemId != mRoot, NS_ERROR_INVALID_ARG);
970 nsresult rv;
971 PRInt32 childIndex;
972 PRInt64 placeId, folderId;
973 PRInt32 itemType;
974 nsCAutoString buffer;
975 nsCAutoString spec;
977 { // scoping to ensure the statement gets reset
978 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(getInfoStmt, mDBGetItemProperties);
979 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
980 NS_ENSURE_SUCCESS(rv, rv);
982 PRBool hasResult;
983 rv = getInfoStmt->ExecuteStep(&hasResult);
984 NS_ENSURE_SUCCESS(rv, rv);
985 if (!hasResult)
986 return NS_ERROR_INVALID_ARG; // invalid bookmark id
988 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Position, &childIndex);
989 NS_ENSURE_SUCCESS(rv, rv);
990 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_PlaceID, &placeId);
991 NS_ENSURE_SUCCESS(rv, rv);
992 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &folderId);
993 NS_ENSURE_SUCCESS(rv, rv);
994 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
995 NS_ENSURE_SUCCESS(rv, rv);
996 if (itemType == TYPE_BOOKMARK) {
997 rv = getInfoStmt->GetUTF8String(kGetItemPropertiesIndex_URI, spec);
998 NS_ENSURE_SUCCESS(rv, rv);
1002 if (itemType == TYPE_FOLDER) {
1003 rv = RemoveFolder(aItemId);
1004 NS_ENSURE_SUCCESS(rv, rv);
1005 return NS_OK;
1008 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1009 nsINavBookmarkObserver,
1010 OnBeforeItemRemoved(aItemId, itemType));
1012 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1014 // First, remove item annotations
1015 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1016 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1017 rv = annosvc->RemoveItemAnnotations(aItemId);
1018 NS_ENSURE_SUCCESS(rv, rv);
1021 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBRemoveItem);
1022 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1023 NS_ENSURE_SUCCESS(rv, rv);
1024 rv = stmt->Execute();
1025 NS_ENSURE_SUCCESS(rv, rv);
1028 if (childIndex != -1) {
1029 rv = AdjustIndices(folderId, childIndex + 1, PR_INT32_MAX, -1);
1030 NS_ENSURE_SUCCESS(rv, rv);
1033 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1034 folderId, PR_Now());
1035 NS_ENSURE_SUCCESS(rv, rv);
1037 rv = transaction.Commit();
1038 NS_ENSURE_SUCCESS(rv, rv);
1040 if (!mBatching) {
1041 ForceWALCheckpoint(mDBConn);
1044 if (itemType == TYPE_BOOKMARK) {
1045 nsNavHistory* history = nsNavHistory::GetHistoryService();
1046 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1047 rv = history->UpdateFrecency(placeId);
1048 NS_ENSURE_SUCCESS(rv, rv);
1050 rv = UpdateKeywordsHashForRemovedBookmark(aItemId);
1051 NS_ENSURE_SUCCESS(rv, rv);
1054 bool isTagEntry = false;
1055 if (itemType == TYPE_BOOKMARK) {
1056 // Check if the removed bookmark was child of a tag container.
1057 // This is done before notifying since during the notification the parent
1058 // could be removed as well.
1059 PRInt64 grandParentId;
1060 rv = GetFolderIdForItem(folderId, &grandParentId);
1061 NS_ENSURE_SUCCESS(rv, rv);
1062 isTagEntry = grandParentId == mTagsRoot;
1065 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1066 nsINavBookmarkObserver,
1067 OnItemRemoved(aItemId, folderId, childIndex, itemType));
1069 if (isTagEntry) {
1070 // Get all bookmarks pointing to the same uri as this tag entry and
1071 // notify them that tags changed.
1072 nsCOMPtr<nsIURI> uri;
1073 rv = NS_NewURI(getter_AddRefs(uri), spec);
1074 NS_ENSURE_SUCCESS(rv, rv);
1075 nsTArray<PRInt64> bookmarks;
1076 rv = GetBookmarkIdsForURITArray(uri, bookmarks);
1077 NS_ENSURE_SUCCESS(rv, rv);
1079 for (PRUint32 i = 0; i < bookmarks.Length(); i++) {
1080 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1081 nsINavBookmarkObserver,
1082 OnItemChanged(bookmarks[i],
1083 NS_LITERAL_CSTRING("tags"), PR_FALSE,
1084 EmptyCString(), 0, TYPE_BOOKMARK));
1088 return NS_OK;
1092 NS_IMETHODIMP
1093 nsNavBookmarks::CreateFolder(PRInt64 aParent, const nsACString& aName,
1094 PRInt32 aIndex, PRInt64* aNewFolder)
1096 // NOTE: aParent can be null for root creation, so not checked
1097 NS_ENSURE_ARG_POINTER(aNewFolder);
1099 // CreateContainerWithID returns the index of the new folder, but that's not
1100 // used here. To avoid any risk of corrupting data should this function
1101 // be changed, we'll use a local variable to hold it. The PR_TRUE argument
1102 // will cause notifications to be sent to bookmark observers.
1103 PRInt32 localIndex = aIndex;
1104 nsresult rv = CreateContainerWithID(-1, aParent, aName, EmptyString(),
1105 PR_TRUE, &localIndex, aNewFolder);
1106 NS_ENSURE_SUCCESS(rv, rv);
1107 return NS_OK;
1111 NS_IMETHODIMP
1112 nsNavBookmarks::CreateDynamicContainer(PRInt64 aParent,
1113 const nsACString& aName,
1114 const nsAString& aContractId,
1115 PRInt32 aIndex,
1116 PRInt64* aNewFolder)
1118 NS_ENSURE_FALSE(aContractId.IsEmpty(), NS_ERROR_INVALID_ARG);
1120 nsresult rv = CreateContainerWithID(-1, aParent, aName, aContractId,
1121 PR_FALSE, &aIndex, aNewFolder);
1122 NS_ENSURE_SUCCESS(rv, rv);
1123 return NS_OK;
1127 NS_IMETHODIMP
1128 nsNavBookmarks::GetFolderReadonly(PRInt64 aFolder, PRBool* aResult)
1130 NS_ENSURE_ARG_MIN(aFolder, 1);
1131 NS_ENSURE_ARG_POINTER(aResult);
1133 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1134 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1135 nsresult rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, aResult);
1136 NS_ENSURE_SUCCESS(rv, rv);
1137 return NS_OK;
1141 NS_IMETHODIMP
1142 nsNavBookmarks::SetFolderReadonly(PRInt64 aFolder, PRBool aReadOnly)
1144 NS_ENSURE_ARG_MIN(aFolder, 1);
1146 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1147 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1148 nsresult rv;
1149 if (aReadOnly) {
1150 rv = annosvc->SetItemAnnotationInt32(aFolder, READ_ONLY_ANNO, 1, 0,
1151 nsAnnotationService::EXPIRE_NEVER);
1152 NS_ENSURE_SUCCESS(rv, rv);
1154 else {
1155 PRBool hasAnno;
1156 rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, &hasAnno);
1157 NS_ENSURE_SUCCESS(rv, rv);
1158 if (hasAnno) {
1159 rv = annosvc->RemoveItemAnnotation(aFolder, READ_ONLY_ANNO);
1160 NS_ENSURE_SUCCESS(rv, rv);
1163 return NS_OK;
1167 nsresult
1168 nsNavBookmarks::CreateContainerWithID(PRInt64 aItemId,
1169 PRInt64 aParent,
1170 const nsACString& aName,
1171 const nsAString& aContractId,
1172 PRBool aIsBookmarkFolder,
1173 PRInt32* aIndex,
1174 PRInt64* aNewFolder)
1176 // You can pass -1 to indicate append, but no other negative number is allowed
1177 if (*aIndex < -1)
1178 return NS_ERROR_INVALID_ARG;
1180 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1182 PRInt32 index;
1183 PRInt32 folderCount;
1184 nsresult rv = FolderCount(aParent, &folderCount);
1185 NS_ENSURE_SUCCESS(rv, rv);
1186 if (*aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
1187 *aIndex >= folderCount) {
1188 index = folderCount;
1189 } else {
1190 index = *aIndex;
1191 rv = AdjustIndices(aParent, index, PR_INT32_MAX, 1);
1192 NS_ENSURE_SUCCESS(rv, rv);
1195 ItemType containerType = aIsBookmarkFolder ? FOLDER
1196 : DYNAMIC_CONTAINER;
1197 rv = InsertBookmarkInDB(aItemId, nsnull, containerType, aParent, index,
1198 aName, PR_Now(), nsnull, aContractId, aNewFolder);
1199 NS_ENSURE_SUCCESS(rv, rv);
1201 rv = transaction.Commit();
1202 NS_ENSURE_SUCCESS(rv, rv);
1204 if (!mBatching) {
1205 ForceWALCheckpoint(mDBConn);
1208 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1209 nsINavBookmarkObserver,
1210 OnItemAdded(*aNewFolder, aParent, index, containerType,
1211 nsnull));
1213 *aIndex = index;
1214 return NS_OK;
1218 NS_IMETHODIMP
1219 nsNavBookmarks::InsertSeparator(PRInt64 aParent,
1220 PRInt32 aIndex,
1221 PRInt64* aNewItemId)
1223 NS_ENSURE_ARG_MIN(aParent, 1);
1224 // -1 means "append", but no other negative value is allowed.
1225 NS_ENSURE_ARG_MIN(aIndex, -1);
1226 NS_ENSURE_ARG_POINTER(aNewItemId);
1228 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1230 PRInt32 index;
1231 PRInt32 folderCount;
1232 nsresult rv = FolderCount(aParent, &folderCount);
1233 NS_ENSURE_SUCCESS(rv, rv);
1234 if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
1235 aIndex >= folderCount) {
1236 index = folderCount;
1238 else {
1239 index = aIndex;
1240 rv = AdjustIndices(aParent, index, PR_INT32_MAX, 1);
1241 NS_ENSURE_SUCCESS(rv, rv);
1244 // Set a NULL title, not an empty title.
1245 nsCString voidString;
1246 voidString.SetIsVoid(PR_TRUE);
1247 rv = InsertBookmarkInDB(-1, nsnull, SEPARATOR, aParent, index,
1248 voidString, PR_Now(), nsnull, EmptyString(),
1249 aNewItemId);
1250 NS_ENSURE_SUCCESS(rv, rv);
1252 rv = transaction.Commit();
1253 NS_ENSURE_SUCCESS(rv, rv);
1255 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1256 nsINavBookmarkObserver,
1257 OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR,
1258 nsnull));
1260 return NS_OK;
1264 nsresult
1265 nsNavBookmarks::GetLastChildId(PRInt64 aFolderId, PRInt64* aItemId)
1267 NS_ASSERTION(aFolderId > 0, "Invalid folder id");
1268 *aItemId = -1;
1270 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetLastChildId);
1271 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1272 NS_ENSURE_SUCCESS(rv, rv);
1273 PRBool found;
1274 rv = stmt->ExecuteStep(&found);
1275 NS_ENSURE_SUCCESS(rv, rv);
1276 if (found) {
1277 rv = stmt->GetInt64(0, aItemId);
1278 NS_ENSURE_SUCCESS(rv, rv);
1281 return NS_OK;
1285 NS_IMETHODIMP
1286 nsNavBookmarks::GetIdForItemAt(PRInt64 aFolder,
1287 PRInt32 aIndex,
1288 PRInt64* aItemId)
1290 NS_ENSURE_ARG_MIN(aFolder, 1);
1291 NS_ENSURE_ARG_POINTER(aItemId);
1293 *aItemId = -1;
1295 nsresult rv;
1296 if (aIndex == nsINavBookmarksService::DEFAULT_INDEX) {
1297 // Get last item within aFolder.
1298 rv = GetLastChildId(aFolder, aItemId);
1299 NS_ENSURE_SUCCESS(rv, rv);
1301 else {
1302 // Get the item in aFolder with position aIndex.
1303 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetChildAt);
1304 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolder);
1305 NS_ENSURE_SUCCESS(rv, rv);
1306 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
1307 NS_ENSURE_SUCCESS(rv, rv);
1309 PRBool found;
1310 rv = stmt->ExecuteStep(&found);
1311 NS_ENSURE_SUCCESS(rv, rv);
1312 if (found) {
1313 rv = stmt->GetInt64(0, aItemId);
1314 NS_ENSURE_SUCCESS(rv, rv);
1317 return NS_OK;
1321 nsresult
1322 nsNavBookmarks::GetParentAndIndexOfFolder(PRInt64 aFolderId,
1323 PRInt64* _aParent,
1324 PRInt32* _aIndex)
1326 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
1327 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aFolderId);
1328 NS_ENSURE_SUCCESS(rv, rv);
1330 PRBool hasResult;
1331 rv = stmt->ExecuteStep(&hasResult);
1332 NS_ENSURE_SUCCESS(rv, rv);
1333 NS_ENSURE_TRUE(hasResult, NS_ERROR_INVALID_ARG);
1335 rv = stmt->GetInt64(kGetItemPropertiesIndex_Parent, _aParent);
1336 NS_ENSURE_SUCCESS(rv, rv);
1337 rv = stmt->GetInt32(kGetItemPropertiesIndex_Position, _aIndex);
1338 NS_ENSURE_SUCCESS(rv, rv);
1340 return NS_OK;
1344 nsresult
1345 nsNavBookmarks::RemoveFolder(PRInt64 aFolderId)
1347 NS_ENSURE_TRUE(aFolderId != mRoot, NS_ERROR_INVALID_ARG);
1349 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1350 nsINavBookmarkObserver,
1351 OnBeforeItemRemoved(aFolderId, TYPE_FOLDER));
1353 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1355 nsresult rv;
1356 PRInt64 parent;
1357 PRInt32 index, type;
1358 nsCAutoString folderType;
1360 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(getInfoStmt, mDBGetItemProperties);
1361 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aFolderId);
1362 NS_ENSURE_SUCCESS(rv, rv);
1364 PRBool hasResult;
1365 rv = getInfoStmt->ExecuteStep(&hasResult);
1366 NS_ENSURE_SUCCESS(rv, rv);
1367 if (!hasResult) {
1368 return NS_ERROR_INVALID_ARG; // folder is not in the hierarchy
1371 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Type, &type);
1372 NS_ENSURE_SUCCESS(rv, rv);
1373 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &parent);
1374 NS_ENSURE_SUCCESS(rv, rv);
1375 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Position, &index);
1376 NS_ENSURE_SUCCESS(rv, rv);
1377 rv = getInfoStmt->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId,
1378 folderType);
1379 NS_ENSURE_SUCCESS(rv, rv);
1382 // Ensure this is really a folder.
1383 NS_ENSURE_TRUE(type == TYPE_FOLDER, NS_ERROR_INVALID_ARG);
1385 // First, remove item annotations
1386 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1387 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1388 rv = annosvc->RemoveItemAnnotations(aFolderId);
1389 NS_ENSURE_SUCCESS(rv, rv);
1391 // If this is a container bookmark, try to notify its service.
1392 if (folderType.Length() > 0) {
1393 // There is a type associated with this folder.
1394 nsCOMPtr<nsIDynamicContainer> bmcServ = do_GetService(folderType.get());
1395 if (bmcServ) {
1396 rv = bmcServ->OnContainerRemoving(aFolderId);
1397 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
1398 "Remove folder container notification failed.");
1402 // Remove all of the folder's children
1403 rv = RemoveFolderChildren(aFolderId);
1404 NS_ENSURE_SUCCESS(rv, rv);
1407 // Remove the folder from its parent.
1408 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBRemoveItem);
1409 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aFolderId);
1410 NS_ENSURE_SUCCESS(rv, rv);
1411 rv = stmt->Execute();
1412 NS_ENSURE_SUCCESS(rv, rv);
1415 rv = AdjustIndices(parent, index + 1, PR_INT32_MAX, -1);
1416 NS_ENSURE_SUCCESS(rv, rv);
1418 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1419 parent, PR_Now());
1420 NS_ENSURE_SUCCESS(rv, rv);
1422 rv = transaction.Commit();
1423 NS_ENSURE_SUCCESS(rv, rv);
1425 if (!mBatching) {
1426 ForceWALCheckpoint(mDBConn);
1429 if (aFolderId == mToolbarRoot) {
1430 mToolbarRoot = 0;
1433 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1434 nsINavBookmarkObserver,
1435 OnItemRemoved(aFolderId, parent, index, TYPE_FOLDER));
1437 return NS_OK;
1441 NS_IMPL_ISUPPORTS1(nsNavBookmarks::RemoveFolderTransaction, nsITransaction)
1443 NS_IMETHODIMP
1444 nsNavBookmarks::GetRemoveFolderTransaction(PRInt64 aFolderId, nsITransaction** aResult)
1446 NS_ENSURE_ARG_MIN(aFolderId, 1);
1447 NS_ENSURE_ARG_POINTER(aResult);
1449 // Create and initialize a RemoveFolderTransaction object that can be used to
1450 // recreate the folder safely later.
1452 RemoveFolderTransaction* rft =
1453 new RemoveFolderTransaction(aFolderId);
1454 if (!rft)
1455 return NS_ERROR_OUT_OF_MEMORY;
1457 NS_ADDREF(*aResult = rft);
1458 return NS_OK;
1462 nsresult
1463 nsNavBookmarks::GetDescendantChildren(PRInt64 aFolderId,
1464 PRInt64 aGrandParentId,
1465 nsTArray<folderChildrenInfo>& aFolderChildrenArray) {
1466 // New children will be added from this index on.
1467 PRUint32 startIndex = aFolderChildrenArray.Length();
1468 nsresult rv;
1470 // Collect children informations.
1471 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetChildren);
1472 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1473 NS_ENSURE_SUCCESS(rv, rv);
1475 PRBool hasMore;
1476 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
1477 folderChildrenInfo child;
1478 rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.itemId);
1479 NS_ENSURE_SUCCESS(rv, rv);
1480 child.parentId = aFolderId;
1481 child.grandParentId = aGrandParentId;
1482 PRInt32 itemType;
1483 rv = stmt->GetInt32(kGetChildrenIndex_Type, &itemType);
1484 child.itemType = (PRUint16)itemType;
1485 NS_ENSURE_SUCCESS(rv, rv);
1486 rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
1487 NS_ENSURE_SUCCESS(rv, rv);
1488 rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.index);
1489 NS_ENSURE_SUCCESS(rv, rv);
1491 if (child.itemType == TYPE_BOOKMARK) {
1492 nsCAutoString URIString;
1493 rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, URIString);
1494 NS_ENSURE_SUCCESS(rv, rv);
1495 child.url = URIString;
1497 else if (child.itemType == TYPE_FOLDER) {
1498 nsCAutoString folderType;
1499 rv = stmt->GetUTF8String(kGetChildrenIndex_ServiceContractId,
1500 folderType);
1501 NS_ENSURE_SUCCESS(rv, rv);
1502 child.folderType = folderType;
1504 // Append item to children's array.
1505 aFolderChildrenArray.AppendElement(child);
1509 // Recursively call GetDescendantChildren for added folders.
1510 // We start at startIndex since previous folders are checked
1511 // by previous calls to this method.
1512 PRUint32 childCount = aFolderChildrenArray.Length();
1513 for (PRUint32 i = startIndex; i < childCount; i++) {
1514 if (aFolderChildrenArray[i].itemType == TYPE_FOLDER) {
1515 GetDescendantChildren(aFolderChildrenArray[i].itemId,
1516 aFolderId,
1517 aFolderChildrenArray);
1521 return NS_OK;
1525 NS_IMETHODIMP
1526 nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId)
1528 NS_ENSURE_ARG_MIN(aFolderId, 1);
1530 nsresult rv;
1531 PRInt32 itemType;
1532 PRInt64 grandParentId;
1534 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(getInfoStmt, mDBGetItemProperties);
1535 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aFolderId);
1536 NS_ENSURE_SUCCESS(rv, rv);
1538 // Sanity check: ensure that item exists.
1539 PRBool folderExists;
1540 if (NS_FAILED(getInfoStmt->ExecuteStep(&folderExists)) || !folderExists)
1541 return NS_ERROR_INVALID_ARG;
1543 // Sanity check: ensure that this is a folder.
1544 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
1545 NS_ENSURE_SUCCESS(rv, rv);
1546 if (itemType != TYPE_FOLDER)
1547 return NS_ERROR_INVALID_ARG;
1549 // Get the grandParent.
1550 // We have to do this only once since recursion will give us other
1551 // grandParents without the need of additional queries.
1552 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &grandParentId);
1553 NS_ENSURE_SUCCESS(rv, rv);
1556 // Fill folder children array recursively.
1557 nsTArray<folderChildrenInfo> folderChildrenArray;
1558 rv = GetDescendantChildren(aFolderId, grandParentId, folderChildrenArray);
1559 NS_ENSURE_SUCCESS(rv, rv);
1561 // Build a string of folders whose children will be removed.
1562 nsCString foldersToRemove;
1563 for (PRUint32 i = 0; i < folderChildrenArray.Length(); i++) {
1564 folderChildrenInfo child = folderChildrenArray[i];
1566 // Notify observers that we are about to remove this child.
1567 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1568 nsINavBookmarkObserver,
1569 OnBeforeItemRemoved(child.itemId, child.itemType));
1571 if (child.itemType == TYPE_FOLDER) {
1572 foldersToRemove.AppendLiteral(",");
1573 foldersToRemove.AppendInt(child.itemId);
1575 // If this is a dynamic container, try to notify its service that we
1576 // are going to remove it.
1577 // XXX (bug 484094) this should use a bookmark observer!
1578 if (child.folderType.Length() > 0) {
1579 nsCOMPtr<nsIDynamicContainer> bmcServ =
1580 do_GetService(child.folderType.get());
1581 if (bmcServ) {
1582 rv = bmcServ->OnContainerRemoving(child.itemId);
1583 if (NS_FAILED(rv))
1584 NS_WARNING("Remove folder container notification failed.");
1590 // Delete items from the database now.
1591 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1593 nsCOMPtr<mozIStorageStatement> deleteStatement;
1594 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
1595 "DELETE FROM moz_bookmarks "
1596 "WHERE parent IN (:parent") +
1597 foldersToRemove +
1598 NS_LITERAL_CSTRING(")"),
1599 getter_AddRefs(deleteStatement));
1600 NS_ENSURE_SUCCESS(rv, rv);
1601 rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1602 NS_ENSURE_SUCCESS(rv, rv);
1603 rv = deleteStatement->Execute();
1604 NS_ENSURE_SUCCESS(rv, rv);
1606 // Clean up orphan items annotations.
1607 rv = mDBConn->ExecuteSimpleSQL(
1608 NS_LITERAL_CSTRING(
1609 "DELETE FROM moz_items_annos "
1610 "WHERE id IN ("
1611 "SELECT a.id from moz_items_annos a "
1612 "LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
1613 "WHERE b.id ISNULL)"));
1614 NS_ENSURE_SUCCESS(rv, rv);
1616 // Set the lastModified date.
1617 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1618 aFolderId, PR_Now());
1619 NS_ENSURE_SUCCESS(rv, rv);
1621 for (PRUint32 i = 0; i < folderChildrenArray.Length(); i++) {
1622 folderChildrenInfo child = folderChildrenArray[i];
1623 if (child.itemType == TYPE_BOOKMARK) {
1624 PRInt64 placeId = child.placeId;
1625 nsNavHistory* history = nsNavHistory::GetHistoryService();
1626 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1627 rv = history->UpdateFrecency(placeId);
1628 NS_ENSURE_SUCCESS(rv, rv);
1630 rv = UpdateKeywordsHashForRemovedBookmark(child.itemId);
1631 NS_ENSURE_SUCCESS(rv, rv);
1635 rv = transaction.Commit();
1636 NS_ENSURE_SUCCESS(rv, rv);
1638 if (!mBatching) {
1639 ForceWALCheckpoint(mDBConn);
1642 // Call observers in reverse order to serve children before their parent.
1643 for (PRInt32 i = folderChildrenArray.Length() - 1; i >= 0 ; i--) {
1644 folderChildrenInfo child = folderChildrenArray[i];
1646 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1647 nsINavBookmarkObserver,
1648 OnItemRemoved(child.itemId, child.parentId, child.index,
1649 child.itemType));
1651 if (child.itemType == TYPE_BOOKMARK) {
1652 // If the removed bookmark was a child of a tag container, notify all
1653 // bookmark-folder result nodes which contain a bookmark for the removed
1654 // bookmark's url.
1656 if (child.grandParentId == mTagsRoot) {
1657 nsCOMPtr<nsIURI> uri;
1658 rv = NS_NewURI(getter_AddRefs(uri), child.url);
1659 NS_ENSURE_SUCCESS(rv, rv);
1661 nsTArray<PRInt64> bookmarks;
1662 rv = GetBookmarkIdsForURITArray(uri, bookmarks);
1663 NS_ENSURE_SUCCESS(rv, rv);
1665 if (bookmarks.Length()) {
1666 for (PRUint32 i = 0; i < bookmarks.Length(); i++) {
1667 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1668 nsINavBookmarkObserver,
1669 OnItemChanged(bookmarks[i],
1670 NS_LITERAL_CSTRING("tags"), PR_FALSE,
1671 EmptyCString(), 0, TYPE_BOOKMARK));
1678 return NS_OK;
1682 NS_IMETHODIMP
1683 nsNavBookmarks::MoveItem(PRInt64 aItemId, PRInt64 aNewParent, PRInt32 aIndex)
1685 NS_ENSURE_TRUE(aItemId != mRoot, NS_ERROR_INVALID_ARG);
1686 NS_ENSURE_ARG_MIN(aItemId, 1);
1687 NS_ENSURE_ARG_MIN(aNewParent, 1);
1688 // -1 is append, but no other negative number is allowed.
1689 NS_ENSURE_ARG_MIN(aIndex, -1);
1690 // Disallow making an item its own parent.
1691 NS_ENSURE_TRUE(aItemId != aNewParent, NS_ERROR_INVALID_ARG);
1693 mozStorageTransaction transaction(mDBConn, PR_FALSE);
1695 // get item properties
1696 nsresult rv;
1697 PRInt64 oldParent;
1698 PRInt32 oldIndex;
1699 PRInt32 itemType;
1700 nsCAutoString folderType;
1702 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(getInfoStmt, mDBGetItemProperties);
1703 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1704 NS_ENSURE_SUCCESS(rv, rv);
1706 PRBool hasResult;
1707 rv = getInfoStmt->ExecuteStep(&hasResult);
1708 NS_ENSURE_SUCCESS(rv, rv);
1709 if (!hasResult) {
1710 return NS_ERROR_INVALID_ARG; // folder is not in the hierarchy
1713 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &oldParent);
1714 NS_ENSURE_SUCCESS(rv, rv);
1715 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Position, &oldIndex);
1716 NS_ENSURE_SUCCESS(rv, rv);
1717 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
1718 NS_ENSURE_SUCCESS(rv, rv);
1719 if (itemType == TYPE_FOLDER) {
1720 rv = getInfoStmt->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId,
1721 folderType);
1722 NS_ENSURE_SUCCESS(rv, rv);
1726 // if parent and index are the same, nothing to do
1727 if (oldParent == aNewParent && oldIndex == aIndex)
1728 return NS_OK;
1730 // Make sure aNewParent is not aFolder or a subfolder of aFolder
1731 if (itemType == TYPE_FOLDER) {
1732 PRInt64 ancestorId = aNewParent;
1734 while (ancestorId) {
1735 if (ancestorId == aItemId) {
1736 return NS_ERROR_INVALID_ARG;
1739 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(getInfoStmt, mDBGetItemProperties);
1740 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
1741 ancestorId);
1742 NS_ENSURE_SUCCESS(rv, rv);
1744 PRBool hasResult;
1745 rv = getInfoStmt->ExecuteStep(&hasResult);
1746 NS_ENSURE_SUCCESS(rv, rv);
1747 if (hasResult) {
1748 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &ancestorId);
1749 NS_ENSURE_SUCCESS(rv, rv);
1751 else {
1752 break;
1757 // calculate new index
1758 PRInt32 newIndex;
1759 PRInt32 folderCount;
1760 rv = FolderCount(aNewParent, &folderCount);
1761 NS_ENSURE_SUCCESS(rv, rv);
1762 if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
1763 aIndex >= folderCount) {
1764 newIndex = folderCount;
1765 // If the parent remains the same, then the folder is really being moved
1766 // to count - 1 (since it's being removed from the old position)
1767 if (oldParent == aNewParent) {
1768 --newIndex;
1770 } else {
1771 newIndex = aIndex;
1773 if (oldParent == aNewParent && newIndex > oldIndex) {
1774 // when an item is being moved lower in the same folder, the new index
1775 // refers to the index before it was removed. Removal causes everything
1776 // to shift up.
1777 --newIndex;
1781 // this is like the previous check, except this covers if
1782 // the specified index was -1 (append), and the calculated
1783 // new index is the same as the existing index
1784 if (aNewParent == oldParent && newIndex == oldIndex) {
1785 // Nothing to do!
1786 return NS_OK;
1789 // adjust indices to account for the move
1790 // do this before we update the parent/index fields
1791 // or we'll re-adjust the index for the item we are moving
1792 if (oldParent == aNewParent) {
1793 // We can optimize the updates if moving within the same container.
1794 // We only shift the items between the old and new positions, since the
1795 // insertion will offset the deletion.
1796 if (oldIndex > newIndex) {
1797 rv = AdjustIndices(oldParent, newIndex, oldIndex - 1, 1);
1799 else {
1800 rv = AdjustIndices(oldParent, oldIndex + 1, newIndex, -1);
1802 NS_ENSURE_SUCCESS(rv, rv);
1804 else {
1805 // We're moving between containers, so this happens in two steps.
1806 // First, fill the hole from the removal from the old parent.
1807 rv = AdjustIndices(oldParent, oldIndex + 1, PR_INT32_MAX, -1);
1808 NS_ENSURE_SUCCESS(rv, rv);
1809 // Now, make room in the new parent for the insertion.
1810 rv = AdjustIndices(aNewParent, newIndex, PR_INT32_MAX, 1);
1811 NS_ENSURE_SUCCESS(rv, rv);
1815 // Update parent and position.
1816 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBMoveItem);
1817 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aNewParent);
1818 NS_ENSURE_SUCCESS(rv, rv);
1819 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), newIndex);
1820 NS_ENSURE_SUCCESS(rv, rv);
1821 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1822 NS_ENSURE_SUCCESS(rv, rv);
1823 rv = stmt->Execute();
1824 NS_ENSURE_SUCCESS(rv, rv);
1827 PRTime now = PR_Now();
1828 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1829 oldParent, now);
1830 NS_ENSURE_SUCCESS(rv, rv);
1831 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1832 aNewParent, now);
1833 NS_ENSURE_SUCCESS(rv, rv);
1835 rv = transaction.Commit();
1836 NS_ENSURE_SUCCESS(rv, rv);
1838 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1839 nsINavBookmarkObserver,
1840 OnItemMoved(aItemId, oldParent, oldIndex, aNewParent,
1841 newIndex, itemType));
1843 // notify dynamic container provider if there is one
1844 if (!folderType.IsEmpty()) {
1845 nsCOMPtr<nsIDynamicContainer> container =
1846 do_GetService(folderType.get(), &rv);
1847 if (NS_SUCCEEDED(rv)) {
1848 rv = container->OnContainerMoved(aItemId, aNewParent, newIndex);
1849 NS_ENSURE_SUCCESS(rv, rv);
1852 return NS_OK;
1856 nsresult
1857 nsNavBookmarks::SetItemDateInternal(mozIStorageStatement* aStatement,
1858 PRInt64 aItemId,
1859 PRTime aValue)
1861 NS_ENSURE_STATE(aStatement);
1862 mozStorageStatementScoper scoper(aStatement);
1864 nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
1865 NS_ENSURE_SUCCESS(rv, rv);
1866 rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1867 NS_ENSURE_SUCCESS(rv, rv);
1869 rv = aStatement->Execute();
1870 NS_ENSURE_SUCCESS(rv, rv);
1872 // note, we are not notifying the observers
1873 // that the item has changed.
1875 return NS_OK;
1879 NS_IMETHODIMP
1880 nsNavBookmarks::SetItemDateAdded(PRInt64 aItemId, PRTime aDateAdded)
1882 // GetItemType also ensures that aItemId points to a valid item.
1883 PRUint16 itemType;
1884 nsresult rv = GetItemType(aItemId, &itemType);
1885 NS_ENSURE_SUCCESS(rv, rv);
1887 rv = SetItemDateInternal(GetStatement(mDBSetItemDateAdded),
1888 aItemId, aDateAdded);
1889 NS_ENSURE_SUCCESS(rv, rv);
1891 // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
1892 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1893 nsINavBookmarkObserver,
1894 OnItemChanged(aItemId, NS_LITERAL_CSTRING("dateAdded"),
1895 PR_FALSE,
1896 nsPrintfCString(16, "%lld", aDateAdded),
1897 aDateAdded, itemType));
1898 return NS_OK;
1902 NS_IMETHODIMP
1903 nsNavBookmarks::GetItemDateAdded(PRInt64 aItemId, PRTime* _dateAdded)
1905 NS_ENSURE_ARG_MIN(aItemId, 1);
1906 NS_ENSURE_ARG_POINTER(_dateAdded);
1908 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
1909 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1910 NS_ENSURE_SUCCESS(rv, rv);
1912 PRBool hasResult;
1913 rv = stmt->ExecuteStep(&hasResult);
1914 NS_ENSURE_SUCCESS(rv, rv);
1915 NS_ENSURE_TRUE(hasResult, NS_ERROR_INVALID_ARG); // Invalid itemId.
1917 rv = stmt->GetInt64(kGetItemPropertiesIndex_DateAdded, _dateAdded);
1918 NS_ENSURE_SUCCESS(rv, rv);
1920 return NS_OK;
1924 NS_IMETHODIMP
1925 nsNavBookmarks::SetItemLastModified(PRInt64 aItemId, PRTime aLastModified)
1927 // GetItemType also ensures that aItemId points to a valid item.
1928 PRUint16 itemType;
1929 nsresult rv = GetItemType(aItemId, &itemType);
1930 NS_ENSURE_SUCCESS(rv, rv);
1932 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
1933 aItemId, aLastModified);
1934 NS_ENSURE_SUCCESS(rv, rv);
1936 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
1937 nsINavBookmarkObserver,
1938 OnItemChanged(aItemId, NS_LITERAL_CSTRING("lastModified"),
1939 PR_FALSE,
1940 nsPrintfCString(16, "%lld", aLastModified),
1941 aLastModified, itemType));
1942 return NS_OK;
1946 NS_IMETHODIMP
1947 nsNavBookmarks::GetItemLastModified(PRInt64 aItemId, PRTime* aLastModified)
1949 NS_ENSURE_ARG_MIN(aItemId, 1);
1950 NS_ENSURE_ARG_POINTER(aLastModified);
1952 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
1953 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1954 NS_ENSURE_SUCCESS(rv, rv);
1956 PRBool hasResult;
1957 rv = stmt->ExecuteStep(&hasResult);
1958 NS_ENSURE_SUCCESS(rv, rv);
1960 if (!hasResult)
1961 return NS_ERROR_INVALID_ARG; // invalid item id
1963 rv = stmt->GetInt64(kGetItemPropertiesIndex_LastModified, aLastModified);
1964 NS_ENSURE_SUCCESS(rv, rv);
1965 return NS_OK;
1969 nsresult
1970 nsNavBookmarks::GetGUIDBase(nsAString &aGUIDBase)
1972 if (!mGUIDBase.IsEmpty()) {
1973 aGUIDBase = mGUIDBase;
1974 return NS_OK;
1977 // generate a new GUID base for this session
1978 nsCOMPtr<nsIUUIDGenerator> uuidgen =
1979 do_GetService("@mozilla.org/uuid-generator;1");
1980 NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY);
1981 nsID GUID;
1982 nsresult rv = uuidgen->GenerateUUIDInPlace(&GUID);
1983 NS_ENSURE_SUCCESS(rv, rv);
1984 char GUIDChars[NSID_LENGTH];
1985 GUID.ToProvidedString(GUIDChars);
1986 CopyASCIItoUTF16(GUIDChars, mGUIDBase);
1987 aGUIDBase = mGUIDBase;
1988 return NS_OK;
1992 NS_IMETHODIMP
1993 nsNavBookmarks::GetItemGUID(PRInt64 aItemId, nsAString& aGUID)
1995 NS_ENSURE_ARG_MIN(aItemId, 1);
1997 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1998 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1999 nsresult rv = annosvc->GetItemAnnotationString(aItemId, GUID_ANNO, aGUID);
2000 if (NS_SUCCEEDED(rv) || rv != NS_ERROR_NOT_AVAILABLE)
2001 return rv;
2003 nsAutoString tmp;
2004 tmp.AppendInt(mItemCount++);
2005 aGUID.SetCapacity(NSID_LENGTH - 1 + tmp.Length());
2006 nsString GUIDBase;
2007 rv = GetGUIDBase(GUIDBase);
2008 NS_ENSURE_SUCCESS(rv, rv);
2009 aGUID.Assign(GUIDBase);
2010 aGUID.Append(tmp);
2012 rv = SetItemGUID(aItemId, aGUID);
2013 NS_ENSURE_SUCCESS(rv, rv);
2014 return NS_OK;
2018 NS_IMETHODIMP
2019 nsNavBookmarks::SetItemGUID(PRInt64 aItemId, const nsAString& aGUID)
2021 NS_ENSURE_ARG_MIN(aItemId, 1);
2023 PRInt64 checkId;
2024 GetItemIdForGUID(aGUID, &checkId);
2025 if (checkId != -1)
2026 return NS_ERROR_INVALID_ARG; // invalid GUID, already exists
2028 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
2029 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
2030 nsresult rv = annosvc->SetItemAnnotationString(aItemId, GUID_ANNO, aGUID, 0,
2031 nsIAnnotationService::EXPIRE_NEVER);
2032 NS_ENSURE_SUCCESS(rv, rv);
2033 return NS_OK;
2037 NS_IMETHODIMP
2038 nsNavBookmarks::GetItemIdForGUID(const nsAString& aGUID, PRInt64* aItemId)
2040 NS_ENSURE_ARG_POINTER(aItemId);
2042 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemIdForGUID);
2043 nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("guid"), aGUID);
2044 NS_ENSURE_SUCCESS(rv, rv);
2046 PRBool hasMore = PR_FALSE;
2047 rv = stmt->ExecuteStep(&hasMore);
2048 if (NS_FAILED(rv) || ! hasMore) {
2049 *aItemId = -1;
2050 return NS_OK; // not found: return -1
2053 // found, get the itemId
2054 rv = stmt->GetInt64(0, aItemId);
2055 NS_ENSURE_SUCCESS(rv, rv);
2056 return NS_OK;
2060 NS_IMETHODIMP
2061 nsNavBookmarks::SetItemTitle(PRInt64 aItemId, const nsACString& aTitle)
2063 // GetItemType also ensures that aItemId points to a valid item.
2064 PRUint16 itemType;
2065 nsresult rv = GetItemType(aItemId, &itemType);
2066 NS_ENSURE_SUCCESS(rv, rv);
2068 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(statement, mDBSetItemTitle);
2069 // Support setting a null title, we support this in insertBookmark.
2070 if (aTitle.IsVoid())
2071 rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
2072 else
2073 rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
2074 NS_ENSURE_SUCCESS(rv, rv);
2075 PRTime lastModified = PR_Now();
2076 rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"), lastModified);
2077 NS_ENSURE_SUCCESS(rv, rv);
2078 rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2079 NS_ENSURE_SUCCESS(rv, rv);
2081 rv = statement->Execute();
2082 NS_ENSURE_SUCCESS(rv, rv);
2084 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2085 nsINavBookmarkObserver,
2086 OnItemChanged(aItemId, NS_LITERAL_CSTRING("title"), PR_FALSE,
2087 aTitle, lastModified, itemType));
2088 return NS_OK;
2092 NS_IMETHODIMP
2093 nsNavBookmarks::GetItemTitle(PRInt64 aItemId, nsACString& aTitle)
2095 NS_ENSURE_ARG_MIN(aItemId, 1);
2097 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2098 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2099 NS_ENSURE_SUCCESS(rv, rv);
2101 PRBool hasResult;
2102 rv = stmt->ExecuteStep(&hasResult);
2103 NS_ENSURE_SUCCESS(rv, rv);
2105 if (!hasResult)
2106 return NS_ERROR_INVALID_ARG; // invalid bookmark id
2108 rv = stmt->GetUTF8String(kGetItemPropertiesIndex_Title, aTitle);
2109 NS_ENSURE_SUCCESS(rv, rv);
2110 return NS_OK;
2114 NS_IMETHODIMP
2115 nsNavBookmarks::GetBookmarkURI(PRInt64 aItemId, nsIURI** aURI)
2117 NS_ENSURE_ARG_MIN(aItemId, 1);
2118 NS_ENSURE_ARG_POINTER(aURI);
2120 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2121 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2122 NS_ENSURE_SUCCESS(rv, rv);
2124 PRBool hasResult;
2125 rv = stmt->ExecuteStep(&hasResult);
2126 NS_ENSURE_SUCCESS(rv, rv);
2128 if (!hasResult)
2129 return NS_ERROR_INVALID_ARG; // invalid bookmark id
2131 PRInt32 type;
2132 rv = stmt->GetInt32(kGetItemPropertiesIndex_Type, &type);
2133 NS_ENSURE_SUCCESS(rv, rv);
2134 // Ensure this is a bookmark.
2135 NS_ENSURE_TRUE(type == TYPE_BOOKMARK, NS_ERROR_INVALID_ARG);
2137 nsCAutoString spec;
2138 rv = stmt->GetUTF8String(kGetItemPropertiesIndex_URI, spec);
2139 NS_ENSURE_SUCCESS(rv, rv);
2141 rv = NS_NewURI(aURI, spec);
2142 NS_ENSURE_SUCCESS(rv, rv);
2144 return NS_OK;
2148 NS_IMETHODIMP
2149 nsNavBookmarks::GetItemType(PRInt64 aItemId, PRUint16* _type)
2151 NS_ENSURE_ARG_MIN(aItemId, 1);
2152 NS_ENSURE_ARG_POINTER(_type);
2154 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2155 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2156 NS_ENSURE_SUCCESS(rv, rv);
2158 PRBool hasResult;
2159 rv = stmt->ExecuteStep(&hasResult);
2160 NS_ENSURE_SUCCESS(rv, rv);
2162 if (!hasResult) {
2163 return NS_ERROR_INVALID_ARG; // invalid bookmark id
2166 PRInt32 itemType;
2167 rv = stmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
2168 NS_ENSURE_SUCCESS(rv, rv);
2169 *_type = itemType;
2171 return NS_OK;
2175 nsresult
2176 nsNavBookmarks::GetFolderType(PRInt64 aFolder, nsACString& aType)
2178 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2179 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aFolder);
2180 NS_ENSURE_SUCCESS(rv, rv);
2182 PRBool hasResult;
2183 rv = stmt->ExecuteStep(&hasResult);
2184 NS_ENSURE_SUCCESS(rv, rv);
2186 if (!hasResult) {
2187 return NS_ERROR_INVALID_ARG;
2190 return stmt->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId, aType);
2194 nsresult
2195 nsNavBookmarks::ResultNodeForContainer(PRInt64 aID,
2196 nsNavHistoryQueryOptions* aOptions,
2197 nsNavHistoryResultNode** aNode)
2199 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2200 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aID);
2201 NS_ENSURE_SUCCESS(rv, rv);
2203 PRBool hasResult;
2204 rv = stmt->ExecuteStep(&hasResult);
2205 NS_ENSURE_SUCCESS(rv, rv);
2206 NS_ENSURE_TRUE(hasResult, NS_ERROR_INVALID_ARG);
2208 nsCAutoString title;
2209 rv = stmt->GetUTF8String(kGetItemPropertiesIndex_Title, title);
2210 NS_ENSURE_SUCCESS(rv, rv);
2212 PRInt32 itemType;
2213 rv = stmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
2214 NS_ENSURE_SUCCESS(rv, rv);
2215 if (itemType == TYPE_DYNAMIC_CONTAINER) {
2216 // contract id
2217 nsCAutoString contractId;
2218 rv = stmt->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId,
2219 contractId);
2220 NS_ENSURE_SUCCESS(rv, rv);
2221 *aNode = new nsNavHistoryContainerResultNode(EmptyCString(), title,
2222 EmptyCString(),
2223 nsINavHistoryResultNode::RESULT_TYPE_DYNAMIC_CONTAINER,
2224 PR_TRUE, contractId, aOptions);
2225 (*aNode)->mItemId = aID;
2227 else { // TYPE_FOLDER
2228 *aNode = new nsNavHistoryFolderResultNode(title, aOptions, aID, EmptyCString());
2230 if (!*aNode)
2231 return NS_ERROR_OUT_OF_MEMORY;
2233 rv = stmt->GetInt64(kGetItemPropertiesIndex_DateAdded,
2234 &(*aNode)->mDateAdded);
2235 NS_ENSURE_SUCCESS(rv, rv);
2236 rv = stmt->GetInt64(kGetItemPropertiesIndex_LastModified,
2237 &(*aNode)->mLastModified);
2238 NS_ENSURE_SUCCESS(rv, rv);
2240 NS_ADDREF(*aNode);
2241 return NS_OK;
2245 nsresult
2246 nsNavBookmarks::QueryFolderChildren(
2247 PRInt64 aFolderId,
2248 nsNavHistoryQueryOptions* aOptions,
2249 nsCOMArray<nsNavHistoryResultNode>* aChildren)
2251 NS_ENSURE_ARG_POINTER(aOptions);
2252 NS_ENSURE_ARG_POINTER(aChildren);
2254 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetChildren);
2255 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
2256 NS_ENSURE_SUCCESS(rv, rv);
2258 nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
2259 NS_ENSURE_SUCCESS(rv, rv);
2261 PRInt32 index = -1;
2262 PRBool hasResult;
2263 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2264 rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
2265 NS_ENSURE_SUCCESS(rv, rv);
2268 return NS_OK;
2272 nsresult
2273 nsNavBookmarks::ProcessFolderNodeRow(
2274 mozIStorageValueArray* aRow,
2275 nsNavHistoryQueryOptions* aOptions,
2276 nsCOMArray<nsNavHistoryResultNode>* aChildren,
2277 PRInt32& aCurrentIndex)
2279 NS_ENSURE_ARG_POINTER(aRow);
2280 NS_ENSURE_ARG_POINTER(aOptions);
2281 NS_ENSURE_ARG_POINTER(aChildren);
2283 // The results will be in order of aCurrentIndex. Even if we don't add a node
2284 // because it was excluded, we need to count its index, so do that before
2285 // doing anything else.
2286 aCurrentIndex++;
2288 PRInt32 itemType;
2289 nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
2290 NS_ENSURE_SUCCESS(rv, rv);
2291 PRInt64 id;
2292 rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
2293 NS_ENSURE_SUCCESS(rv, rv);
2294 nsRefPtr<nsNavHistoryResultNode> node;
2295 if (itemType == TYPE_BOOKMARK) {
2296 nsNavHistory* history = nsNavHistory::GetHistoryService();
2297 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2298 rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
2299 NS_ENSURE_SUCCESS(rv, rv);
2301 PRUint32 nodeType;
2302 node->GetType(&nodeType);
2303 if ((nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
2304 aOptions->ExcludeQueries()) ||
2305 (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
2306 nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT &&
2307 aOptions->ExcludeItems())) {
2308 return NS_OK;
2311 else if (itemType == TYPE_FOLDER || itemType == TYPE_DYNAMIC_CONTAINER) {
2312 if (aOptions->ExcludeReadOnlyFolders()) {
2313 // If the folder is read-only, skip it.
2314 PRBool readOnly;
2315 if (itemType == TYPE_DYNAMIC_CONTAINER) {
2316 readOnly = PR_TRUE;
2318 else {
2319 readOnly = PR_FALSE;
2320 GetFolderReadonly(id, &readOnly);
2322 if (readOnly)
2323 return NS_OK;
2325 rv = ResultNodeForContainer(id, aOptions, getter_AddRefs(node));
2326 NS_ENSURE_SUCCESS(rv, rv);
2328 else {
2329 // This is a separator.
2330 if (aOptions->ExcludeItems()) {
2331 return NS_OK;
2333 node = new nsNavHistorySeparatorResultNode();
2334 NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
2336 rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &node->mItemId);
2337 NS_ENSURE_SUCCESS(rv, rv);
2338 rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
2339 &node->mDateAdded);
2340 NS_ENSURE_SUCCESS(rv, rv);
2341 rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
2342 &node->mLastModified);
2343 NS_ENSURE_SUCCESS(rv, rv);
2346 // Store the index of the node within this container. Note that this is not
2347 // moz_bookmarks.position.
2348 node->mBookmarkIndex = aCurrentIndex;
2350 NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
2352 return NS_OK;
2356 nsresult
2357 nsNavBookmarks::QueryFolderChildrenAsync(
2358 nsNavHistoryFolderResultNode* aNode,
2359 PRInt64 aFolderId,
2360 mozIStoragePendingStatement** _pendingStmt)
2362 NS_ENSURE_ARG_POINTER(aNode);
2363 NS_ENSURE_ARG_POINTER(_pendingStmt);
2365 mozStorageStatementScoper scope(mDBGetChildren);
2367 nsresult rv = mDBGetChildren->BindInt64ByName(NS_LITERAL_CSTRING("parent"),
2368 aFolderId);
2369 NS_ENSURE_SUCCESS(rv, rv);
2371 nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
2372 rv = mDBGetChildren->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
2373 NS_ENSURE_SUCCESS(rv, rv);
2375 NS_IF_ADDREF(*_pendingStmt = pendingStmt);
2376 return NS_OK;
2380 nsresult
2381 nsNavBookmarks::FolderCount(PRInt64 aFolderId, PRInt32* _folderCount)
2383 *_folderCount = 0;
2384 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBFolderCount);
2385 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
2386 NS_ENSURE_SUCCESS(rv, rv);
2388 PRBool hasResult;
2389 rv = stmt->ExecuteStep(&hasResult);
2390 NS_ENSURE_SUCCESS(rv, rv);
2391 NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
2393 // Ensure that the folder we are looking for exists.
2394 PRInt64 confirmFolderId;
2395 rv = stmt->GetInt64(1, &confirmFolderId);
2396 NS_ENSURE_SUCCESS(rv, rv);
2397 NS_ENSURE_TRUE(confirmFolderId == aFolderId, NS_ERROR_INVALID_ARG);
2399 rv = stmt->GetInt32(0, _folderCount);
2400 NS_ENSURE_SUCCESS(rv, rv);
2402 return NS_OK;
2406 NS_IMETHODIMP
2407 nsNavBookmarks::IsBookmarked(nsIURI* aURI, PRBool* aBookmarked)
2409 NS_ENSURE_ARG(aURI);
2410 NS_ENSURE_ARG_POINTER(aBookmarked);
2412 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsURIBookmarkedInDatabase);
2413 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2414 NS_ENSURE_SUCCESS(rv, rv);
2415 rv = stmt->ExecuteStep(aBookmarked);
2416 NS_ENSURE_SUCCESS(rv, rv);
2418 return NS_OK;
2422 NS_IMETHODIMP
2423 nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
2425 NS_ENSURE_ARG(aURI);
2426 NS_ENSURE_ARG_POINTER(_retval);
2428 *_retval = nsnull;
2430 nsNavHistory* history = nsNavHistory::GetHistoryService();
2431 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2432 PRInt64 placeId;
2433 nsresult rv = history->GetUrlIdFor(aURI, &placeId, PR_FALSE);
2434 NS_ENSURE_SUCCESS(rv, rv);
2435 if (!placeId) {
2436 // This URI is unknown, just return null.
2437 return NS_OK;
2440 // Check if a bookmark exists in the redirects chain for this URI.
2441 // The query will also check if the page is directly bookmarked, and return
2442 // the first found bookmark in case. The check is directly on moz_bookmarks
2443 // without special filtering.
2444 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBFindRedirectedBookmark);
2445 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), placeId);
2446 NS_ENSURE_SUCCESS(rv, rv);
2447 PRBool hasBookmarkedOrigin;
2448 if (NS_SUCCEEDED(stmt->ExecuteStep(&hasBookmarkedOrigin)) &&
2449 hasBookmarkedOrigin) {
2450 nsCAutoString spec;
2451 rv = stmt->GetUTF8String(0, spec);
2452 NS_ENSURE_SUCCESS(rv, rv);
2453 rv = NS_NewURI(_retval, spec);
2454 NS_ENSURE_SUCCESS(rv, rv);
2457 // If there is no bookmarked origin, we will just return null.
2458 return NS_OK;
2462 NS_IMETHODIMP
2463 nsNavBookmarks::ChangeBookmarkURI(PRInt64 aBookmarkId, nsIURI* aNewURI)
2465 NS_ENSURE_ARG_MIN(aBookmarkId, 1);
2466 NS_ENSURE_ARG(aNewURI);
2468 mozStorageTransaction transaction(mDBConn, PR_FALSE);
2470 nsNavHistory* history = nsNavHistory::GetHistoryService();
2471 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2473 PRInt64 placeId;
2474 nsresult rv = history->GetUrlIdFor(aNewURI, &placeId, PR_TRUE);
2475 NS_ENSURE_SUCCESS(rv, rv);
2476 if (!placeId)
2477 return NS_ERROR_INVALID_ARG;
2479 // We need the bookmark's current corresponding places ID below, so get it now
2480 // before we change it. GetBookmarkURI will fail if aBookmarkId is bad.
2481 nsCOMPtr<nsIURI> oldURI;
2482 PRInt64 oldPlaceId;
2483 rv = GetBookmarkURI(aBookmarkId, getter_AddRefs(oldURI));
2484 NS_ENSURE_SUCCESS(rv, rv);
2485 rv = history->GetUrlIdFor(oldURI, &oldPlaceId, PR_FALSE);
2486 NS_ENSURE_SUCCESS(rv, rv);
2488 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(statement, mDBChangeBookmarkURI);
2489 rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), placeId);
2490 NS_ENSURE_SUCCESS(rv, rv);
2491 PRTime lastModified = PR_Now();
2492 rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"), lastModified);
2493 NS_ENSURE_SUCCESS(rv, rv);
2494 rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmarkId);
2495 NS_ENSURE_SUCCESS(rv, rv);
2496 rv = statement->Execute();
2497 NS_ENSURE_SUCCESS(rv, rv);
2499 rv = transaction.Commit();
2500 NS_ENSURE_SUCCESS(rv, rv);
2502 rv = history->UpdateFrecency(placeId);
2503 NS_ENSURE_SUCCESS(rv, rv);
2505 // Upon changing the URI for a bookmark, update the frecency for the old
2506 // place as well.
2507 rv = history->UpdateFrecency(oldPlaceId);
2508 NS_ENSURE_SUCCESS(rv, rv);
2510 nsCAutoString spec;
2511 rv = aNewURI->GetSpec(spec);
2512 NS_ENSURE_SUCCESS(rv, rv);
2514 // Pass the new URI to OnItemChanged.
2515 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2516 nsINavBookmarkObserver,
2517 OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("uri"),
2518 PR_FALSE, spec, lastModified, TYPE_BOOKMARK));
2520 return NS_OK;
2524 NS_IMETHODIMP
2525 nsNavBookmarks::GetFolderIdForItem(PRInt64 aItemId, PRInt64* aFolderId)
2527 NS_ENSURE_ARG_MIN(aItemId, 1);
2528 NS_ENSURE_ARG_POINTER(aFolderId);
2530 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2531 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2532 NS_ENSURE_SUCCESS(rv, rv);
2534 PRBool hasResult;
2535 rv = stmt->ExecuteStep(&hasResult);
2536 NS_ENSURE_SUCCESS(rv, rv);
2538 if (!hasResult)
2539 return NS_ERROR_INVALID_ARG; // invalid item id
2541 rv = stmt->GetInt64(kGetItemPropertiesIndex_Parent, aFolderId);
2542 NS_ENSURE_SUCCESS(rv, rv);
2544 // this should not happen, but see bug #400448 for details
2545 NS_ENSURE_TRUE(aItemId != *aFolderId, NS_ERROR_UNEXPECTED);
2546 return NS_OK;
2550 nsresult
2551 nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
2552 nsTArray<PRInt64>& aResult)
2554 NS_ENSURE_ARG(aURI);
2556 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBFindURIBookmarks);
2557 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2558 NS_ENSURE_SUCCESS(rv, rv);
2560 PRBool more;
2561 while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
2562 PRInt64 bookmarkId;
2563 rv = stmt->GetInt64(kFindBookmarksIndex_ID, &bookmarkId);
2564 NS_ENSURE_SUCCESS(rv, rv);
2565 NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
2567 NS_ENSURE_SUCCESS(rv, rv);
2569 return NS_OK;
2573 NS_IMETHODIMP
2574 nsNavBookmarks::GetBookmarkIdsForURI(nsIURI* aURI, PRUint32* aCount,
2575 PRInt64** aBookmarks)
2577 NS_ENSURE_ARG(aURI);
2578 NS_ENSURE_ARG_POINTER(aCount);
2579 NS_ENSURE_ARG_POINTER(aBookmarks);
2581 *aCount = 0;
2582 *aBookmarks = nsnull;
2583 nsTArray<PRInt64> bookmarks;
2585 // Get the information from the DB as a TArray
2586 nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
2587 NS_ENSURE_SUCCESS(rv, rv);
2589 // Copy the results into a new array for output
2590 if (bookmarks.Length()) {
2591 *aBookmarks =
2592 static_cast<PRInt64*>(nsMemory::Alloc(sizeof(PRInt64) * bookmarks.Length()));
2593 if (!*aBookmarks)
2594 return NS_ERROR_OUT_OF_MEMORY;
2595 for (PRUint32 i = 0; i < bookmarks.Length(); i ++)
2596 (*aBookmarks)[i] = bookmarks[i];
2598 *aCount = bookmarks.Length();
2600 return NS_OK;
2604 NS_IMETHODIMP
2605 nsNavBookmarks::GetItemIndex(PRInt64 aItemId, PRInt32* _index)
2607 NS_ENSURE_ARG_MIN(aItemId, 1);
2608 NS_ENSURE_ARG_POINTER(_index);
2610 *_index = -1;
2612 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
2613 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2614 NS_ENSURE_SUCCESS(rv, rv);
2615 PRBool hasResult;
2616 rv = stmt->ExecuteStep(&hasResult);
2617 NS_ENSURE_SUCCESS(rv, rv);
2618 if (!hasResult)
2619 return NS_OK;
2621 rv = stmt->GetInt32(kGetItemPropertiesIndex_Position, _index);
2622 NS_ENSURE_SUCCESS(rv, rv);
2624 return NS_OK;
2628 NS_IMETHODIMP
2629 nsNavBookmarks::SetItemIndex(PRInt64 aItemId, PRInt32 aNewIndex)
2631 NS_ENSURE_ARG_MIN(aItemId, 1);
2632 NS_ENSURE_ARG_MIN(aNewIndex, 0);
2634 nsresult rv;
2635 PRInt32 oldIndex = 0;
2636 PRInt64 parent = 0;
2637 PRInt32 itemType;
2640 mozIStorageStatement* getInfoStmt(mDBGetItemProperties);
2641 NS_ENSURE_STATE(getInfoStmt);
2642 mozStorageStatementScoper scoper(getInfoStmt);
2643 rv = getInfoStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2644 NS_ENSURE_SUCCESS(rv, rv);
2646 PRBool hasResult;
2647 rv = getInfoStmt->ExecuteStep(&hasResult);
2648 NS_ENSURE_SUCCESS(rv, rv);
2649 if (!hasResult)
2650 return NS_OK;
2652 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Position, &oldIndex);
2653 NS_ENSURE_SUCCESS(rv, rv);
2654 rv = getInfoStmt->GetInt32(kGetItemPropertiesIndex_Type, &itemType);
2655 NS_ENSURE_SUCCESS(rv, rv);
2656 rv = getInfoStmt->GetInt64(kGetItemPropertiesIndex_Parent, &parent);
2657 NS_ENSURE_SUCCESS(rv, rv);
2660 // Ensure we are not going out of range.
2661 PRInt32 folderCount;
2662 rv = FolderCount(parent, &folderCount);
2663 NS_ENSURE_SUCCESS(rv, rv);
2664 NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG);
2666 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBSetItemIndex);
2667 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
2668 NS_ENSURE_SUCCESS(rv, rv);
2669 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aNewIndex);
2670 NS_ENSURE_SUCCESS(rv, rv);
2672 rv = stmt->Execute();
2673 NS_ENSURE_SUCCESS(rv, rv);
2675 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2676 nsINavBookmarkObserver,
2677 OnItemMoved(aItemId, parent, oldIndex, parent, aNewIndex,
2678 itemType));
2680 return NS_OK;
2684 nsresult
2685 nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(PRInt64 aItemId)
2687 nsAutoString kw;
2688 if (NS_SUCCEEDED(GetKeywordForBookmark(aItemId, kw)) && !kw.IsEmpty()) {
2689 nsresult rv = EnsureKeywordsHash();
2690 NS_ENSURE_SUCCESS(rv, rv);
2691 mBookmarkToKeywordHash.Remove(aItemId);
2693 return NS_OK;
2697 NS_IMETHODIMP
2698 nsNavBookmarks::SetKeywordForBookmark(PRInt64 aBookmarkId,
2699 const nsAString& aUserCasedKeyword)
2701 NS_ENSURE_ARG_MIN(aBookmarkId, 1);
2703 nsresult rv = EnsureKeywordsHash();
2704 NS_ENSURE_SUCCESS(rv, rv);
2706 // Shortcuts are always lowercased internally.
2707 nsAutoString keyword(aUserCasedKeyword);
2708 ToLowerCase(keyword);
2710 // Check if bookmark was already associated to a keyword.
2711 nsAutoString oldKeyword;
2712 rv = GetKeywordForBookmark(aBookmarkId, oldKeyword);
2713 NS_ENSURE_SUCCESS(rv, rv);
2715 // Trying to set the same value or to remove a nonexistent keyword is a no-op.
2716 if (keyword.Equals(oldKeyword) || (keyword.IsEmpty() && oldKeyword.IsEmpty()))
2717 return NS_OK;
2719 mozStorageTransaction transaction(mDBConn, PR_FALSE);
2721 nsCOMPtr<mozIStorageStatement> updateBookmarkStmt;
2722 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
2723 "UPDATE moz_bookmarks "
2724 "SET keyword_id = (SELECT id FROM moz_keywords WHERE keyword = :keyword), "
2725 "lastModified = :date "
2726 "WHERE id = :item_id "
2727 ), getter_AddRefs(updateBookmarkStmt));
2728 NS_ENSURE_SUCCESS(rv, rv);
2730 if (keyword.IsEmpty()) {
2731 // Remove keyword association from the hash.
2732 mBookmarkToKeywordHash.Remove(aBookmarkId);
2733 rv = updateBookmarkStmt->BindNullByName(NS_LITERAL_CSTRING("keyword"));
2735 else {
2736 // We are associating bookmark to a new keyword. Create a new keyword
2737 // record if needed.
2738 nsCOMPtr<mozIStorageStatement> newKeywordStmt;
2739 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
2740 "INSERT OR IGNORE INTO moz_keywords (keyword) VALUES (:keyword)"
2741 ), getter_AddRefs(newKeywordStmt));
2742 NS_ENSURE_SUCCESS(rv, rv);
2743 rv = newKeywordStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"),
2744 keyword);
2745 NS_ENSURE_SUCCESS(rv, rv);
2746 rv = newKeywordStmt->Execute();
2747 NS_ENSURE_SUCCESS(rv, rv);
2749 // Add new keyword association to the hash, removing the old one if needed.
2750 if (!oldKeyword.IsEmpty())
2751 mBookmarkToKeywordHash.Remove(aBookmarkId);
2752 mBookmarkToKeywordHash.Put(aBookmarkId, keyword);
2753 rv = updateBookmarkStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
2755 NS_ENSURE_SUCCESS(rv, rv);
2756 PRTime lastModified = PR_Now();
2757 rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"),
2758 lastModified);
2759 NS_ENSURE_SUCCESS(rv, rv);
2760 rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
2761 aBookmarkId);
2762 NS_ENSURE_SUCCESS(rv, rv);
2763 rv = updateBookmarkStmt->Execute();
2764 NS_ENSURE_SUCCESS(rv, rv);
2766 rv = transaction.Commit();
2767 NS_ENSURE_SUCCESS(rv, rv);
2769 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2770 nsINavBookmarkObserver,
2771 OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("keyword"),
2772 PR_FALSE, NS_ConvertUTF16toUTF8(keyword),
2773 lastModified, TYPE_BOOKMARK));
2775 return NS_OK;
2779 NS_IMETHODIMP
2780 nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword)
2782 NS_ENSURE_ARG(aURI);
2783 aKeyword.Truncate(0);
2785 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetKeywordForURI);
2786 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
2787 NS_ENSURE_SUCCESS(rv, rv);
2789 PRBool hasMore = PR_FALSE;
2790 rv = stmt->ExecuteStep(&hasMore);
2791 if (NS_FAILED(rv) || !hasMore) {
2792 aKeyword.SetIsVoid(PR_TRUE);
2793 return NS_OK; // not found: return void keyword string
2796 // found, get the keyword
2797 rv = stmt->GetString(0, aKeyword);
2798 NS_ENSURE_SUCCESS(rv, rv);
2799 return NS_OK;
2803 NS_IMETHODIMP
2804 nsNavBookmarks::GetKeywordForBookmark(PRInt64 aBookmarkId, nsAString& aKeyword)
2806 NS_ENSURE_ARG_MIN(aBookmarkId, 1);
2807 aKeyword.Truncate(0);
2809 nsresult rv = EnsureKeywordsHash();
2810 NS_ENSURE_SUCCESS(rv, rv);
2812 nsAutoString keyword;
2813 if (!mBookmarkToKeywordHash.Get(aBookmarkId, &keyword)) {
2814 aKeyword.SetIsVoid(PR_TRUE);
2816 else {
2817 aKeyword.Assign(keyword);
2820 return NS_OK;
2824 NS_IMETHODIMP
2825 nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
2826 nsIURI** aURI)
2828 NS_ENSURE_ARG_POINTER(aURI);
2829 NS_ENSURE_TRUE(!aUserCasedKeyword.IsEmpty(), NS_ERROR_INVALID_ARG);
2830 *aURI = nsnull;
2832 // Shortcuts are always lowercased internally.
2833 nsAutoString keyword(aUserCasedKeyword);
2834 ToLowerCase(keyword);
2836 nsresult rv = EnsureKeywordsHash();
2837 NS_ENSURE_SUCCESS(rv, rv);
2839 keywordSearchData searchData;
2840 searchData.keyword.Assign(keyword);
2841 searchData.itemId = -1;
2842 mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
2844 if (searchData.itemId == -1) {
2845 // Not found.
2846 return NS_OK;
2849 rv = GetBookmarkURI(searchData.itemId, aURI);
2850 NS_ENSURE_SUCCESS(rv, rv);
2852 return NS_OK;
2856 nsresult
2857 nsNavBookmarks::EnsureKeywordsHash() {
2858 if (mBookmarkToKeywordHash.IsInitialized())
2859 return NS_OK;
2861 mBookmarkToKeywordHash.Init(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_SIZE);
2863 nsCOMPtr<mozIStorageStatement> stmt;
2864 nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
2865 "SELECT b.id, k.keyword "
2866 "FROM moz_bookmarks b "
2867 "JOIN moz_keywords k ON k.id = b.keyword_id "
2868 ), getter_AddRefs(stmt));
2869 NS_ENSURE_SUCCESS(rv, rv);
2871 PRBool hasMore;
2872 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2873 PRInt64 itemId;
2874 rv = stmt->GetInt64(0, &itemId);
2875 NS_ENSURE_SUCCESS(rv, rv);
2876 nsAutoString keyword;
2877 rv = stmt->GetString(1, keyword);
2878 NS_ENSURE_SUCCESS(rv, rv);
2880 rv = mBookmarkToKeywordHash.Put(itemId, keyword);
2881 NS_ENSURE_SUCCESS(rv, rv);
2884 return NS_OK;
2888 NS_IMETHODIMP
2889 nsNavBookmarks::RunInBatchMode(nsINavHistoryBatchCallback* aCallback,
2890 nsISupports* aUserData) {
2891 NS_ENSURE_ARG(aCallback);
2893 mBatching = true;
2895 // Just forward the request to history. History service must exist for
2896 // bookmarks to work and we are observing it, thus batch notifications will be
2897 // forwarded to bookmarks observers.
2898 nsNavHistory* history = nsNavHistory::GetHistoryService();
2899 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2900 nsresult rv = history->RunInBatchMode(aCallback, aUserData);
2901 NS_ENSURE_SUCCESS(rv, rv);
2903 return NS_OK;
2907 NS_IMETHODIMP
2908 nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
2909 PRBool aOwnsWeak)
2911 NS_ENSURE_ARG(aObserver);
2912 return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
2916 NS_IMETHODIMP
2917 nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
2919 return mObservers.RemoveWeakElement(aObserver);
2922 void
2923 nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
2925 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver,
2926 OnItemVisited(aData.itemId, aData.visitId, aData.time));
2929 void
2930 nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
2932 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver,
2933 OnItemChanged(aData.itemId, aData.property,
2934 aData.isAnnotation, aData.newValue,
2935 aData.lastModified, aData.itemType));
2938 ////////////////////////////////////////////////////////////////////////////////
2939 //// nsNavBookmarks::nsINavHistoryObserver
2941 NS_IMETHODIMP
2942 nsNavBookmarks::OnBeginUpdateBatch()
2944 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2945 nsINavBookmarkObserver, OnBeginUpdateBatch());
2946 return NS_OK;
2950 NS_IMETHODIMP
2951 nsNavBookmarks::OnEndUpdateBatch()
2953 if (mBatching) {
2954 mBatching = false;
2955 ForceWALCheckpoint(mDBConn);
2958 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2959 nsINavBookmarkObserver, OnEndUpdateBatch());
2960 return NS_OK;
2964 NS_IMETHODIMP
2965 nsNavBookmarks::OnVisit(nsIURI* aURI, PRInt64 aVisitId, PRTime aTime,
2966 PRInt64 aSessionID, PRInt64 aReferringID,
2967 PRUint32 aTransitionType, PRUint32* aAdded)
2969 // If the page is bookmarked, notify observers for each associated bookmark.
2970 ItemVisitData visitData;
2971 visitData.uri = aURI;
2972 visitData.visitId = aVisitId;
2973 visitData.time = aTime;
2975 nsRefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
2976 new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
2977 notifier->Init();
2978 return NS_OK;
2982 NS_IMETHODIMP
2983 nsNavBookmarks::OnBeforeDeleteURI(nsIURI* aURI)
2985 return NS_OK;
2989 NS_IMETHODIMP
2990 nsNavBookmarks::OnDeleteURI(nsIURI* aURI)
2992 // If the page is bookmarked, notify observers for each associated bookmark.
2993 ItemChangeData changeData;
2994 changeData.uri = aURI;
2995 changeData.property = NS_LITERAL_CSTRING("cleartime");
2996 changeData.isAnnotation = PR_FALSE;
2997 changeData.lastModified = 0;
2998 changeData.itemType = TYPE_BOOKMARK;
3000 nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
3001 new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
3002 notifier->Init();
3003 return NS_OK;
3007 NS_IMETHODIMP
3008 nsNavBookmarks::OnClearHistory()
3010 // TODO(bryner): we should notify on visited-time change for all URIs
3011 return NS_OK;
3015 NS_IMETHODIMP
3016 nsNavBookmarks::OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle)
3018 // NOOP. We don't consume page titles from moz_places anymore.
3019 // Title-change notifications are sent from SetItemTitle.
3020 return NS_OK;
3024 NS_IMETHODIMP
3025 nsNavBookmarks::OnPageChanged(nsIURI* aURI, PRUint32 aWhat,
3026 const nsAString& aValue)
3028 nsresult rv;
3029 if (aWhat == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
3030 ItemChangeData changeData;
3031 changeData.uri = aURI;
3032 changeData.property = NS_LITERAL_CSTRING("favicon");
3033 changeData.isAnnotation = PR_FALSE;
3034 changeData.newValue = NS_ConvertUTF16toUTF8(aValue);
3035 changeData.lastModified = 0;
3036 changeData.itemType = TYPE_BOOKMARK;
3038 // Favicons may be set to either pure URIs or to folder URIs
3039 PRBool isPlaceURI;
3040 rv = aURI->SchemeIs("place", &isPlaceURI);
3041 NS_ENSURE_SUCCESS(rv, rv);
3042 if (isPlaceURI) {
3043 nsCAutoString spec;
3044 rv = aURI->GetSpec(spec);
3045 NS_ENSURE_SUCCESS(rv, rv);
3047 nsNavHistory* history = nsNavHistory::GetHistoryService();
3048 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
3050 nsCOMArray<nsNavHistoryQuery> queries;
3051 nsCOMPtr<nsNavHistoryQueryOptions> options;
3052 rv = history->QueryStringToQueryArray(spec, &queries, getter_AddRefs(options));
3053 NS_ENSURE_SUCCESS(rv, rv);
3055 if (queries.Count() == 1 && queries[0]->Folders().Length() == 1) {
3056 changeData.itemId = queries[0]->Folders()[0];
3057 NotifyItemChanged(changeData);
3060 else {
3061 nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
3062 new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
3063 notifier->Init();
3066 return NS_OK;
3070 NS_IMETHODIMP
3071 nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime)
3073 // pages that are bookmarks shouldn't expire, so we don't need to handle it
3074 return NS_OK;
3078 // nsIAnnotationObserver
3080 NS_IMETHODIMP
3081 nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
3083 return NS_OK;
3087 NS_IMETHODIMP
3088 nsNavBookmarks::OnItemAnnotationSet(PRInt64 aItemId, const nsACString& aName)
3090 // GetItemType also ensures that aItemId points to a valid item.
3091 PRUint16 itemType;
3092 nsresult rv = GetItemType(aItemId, &itemType);
3093 NS_ENSURE_SUCCESS(rv, rv);
3095 PRTime lastModified = PR_Now();
3096 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
3097 aItemId, lastModified);
3098 NS_ENSURE_SUCCESS(rv, rv);
3100 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3101 nsINavBookmarkObserver,
3102 OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString(),
3103 lastModified, itemType));
3105 return NS_OK;
3109 NS_IMETHODIMP
3110 nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
3112 return NS_OK;
3116 NS_IMETHODIMP
3117 nsNavBookmarks::OnItemAnnotationRemoved(PRInt64 aItemId, const nsACString& aName)
3119 // GetItemType also ensures that aItemId points to a valid item.
3120 PRUint16 itemType;
3121 nsresult rv = GetItemType(aItemId, &itemType);
3122 NS_ENSURE_SUCCESS(rv, rv);
3124 PRTime lastModified = PR_Now();
3125 rv = SetItemDateInternal(GetStatement(mDBSetItemLastModified),
3126 aItemId, lastModified);
3127 NS_ENSURE_SUCCESS(rv, rv);
3129 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
3130 nsINavBookmarkObserver,
3131 OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString(),
3132 lastModified, itemType));
3134 return NS_OK;
3138 PRBool
3139 nsNavBookmarks::ItemExists(PRInt64 aItemId) {
3140 DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetItemProperties);
3141 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
3142 NS_ENSURE_SUCCESS(rv, PR_FALSE);
3144 PRBool hasResult;
3145 rv = stmt->ExecuteStep(&hasResult);
3146 NS_ENSURE_SUCCESS(rv, PR_FALSE);
3148 return hasResult;