/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Places. * * The Initial Developer of the Original Code is * Google Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brian Ryner (original author) * Dietrich Ayala * Drew Willcoxon * Marco Bonardo * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAppDirectoryServiceDefs.h" #include "nsNavBookmarks.h" #include "nsNavHistory.h" #include "mozStorageHelper.h" #include "nsIServiceManager.h" #include "nsNetUtil.h" #include "nsIDynamicContainer.h" #include "nsUnicharUtils.h" #include "nsFaviconService.h" #include "nsAnnotationService.h" #include "nsPrintfCString.h" #include "nsIUUIDGenerator.h" #include "prprf.h" #include "nsILivemarkService.h" #include "nsPlacesTriggers.h" #include "nsPlacesTables.h" #include "nsPlacesIndexes.h" #include "nsPlacesMacros.h" const PRInt32 nsNavBookmarks::kFindBookmarksIndex_ID = 0; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Type = 1; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_PlaceID = 2; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Parent = 3; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Position = 4; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Title = 5; // These columns sit to the right of the kGetInfoIndex_* columns. const PRInt32 nsNavBookmarks::kGetChildrenIndex_Position = 13; const PRInt32 nsNavBookmarks::kGetChildrenIndex_Type = 14; const PRInt32 nsNavBookmarks::kGetChildrenIndex_PlaceID = 15; const PRInt32 nsNavBookmarks::kGetChildrenIndex_ServiceContractId = 16; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_ID = 0; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_URI = 1; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Title = 2; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Position = 3; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_PlaceID = 4; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Parent = 5; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Type = 6; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_ServiceContractId = 7; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_DateAdded = 8; const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_LastModified = 9; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_Id = 0; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_PlaceId = 1; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_Type = 2; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_Parent = 3; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_Position = 4; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_Title = 5; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_ServiceContractId = 6; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_DateAdded = 7; const PRInt32 nsNavBookmarks::kInsertBookmarkIndex_LastModified = 8; PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService) #define BOOKMARKS_ANNO_PREFIX "bookmarks/" #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder") #define GUID_ANNO NS_LITERAL_CSTRING("placesInternal/GUID") #define READ_ONLY_ANNO NS_LITERAL_CSTRING("placesInternal/READ_ONLY") nsNavBookmarks::nsNavBookmarks() : mItemCount(0) , mRoot(0) , mBookmarksRoot(0) , mTagRoot(0) , mToolbarFolder(0) , mBatchLevel(0) , mBatchHasTransaction(PR_FALSE) , mCanNotify(false) , mCacheObservers("bookmark-observers") { NS_ASSERTION(!gBookmarksService, "Attempting to create two instances of the service!"); gBookmarksService = this; } nsNavBookmarks::~nsNavBookmarks() { NS_ASSERTION(gBookmarksService == this, "Deleting a non-singleton instance of the service"); if (gBookmarksService == this) gBookmarksService = nsnull; } NS_IMPL_ISUPPORTS3(nsNavBookmarks, nsINavBookmarksService, nsINavHistoryObserver, nsIAnnotationObserver) nsresult nsNavBookmarks::Init() { nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); mDBConn = history->GetStorageConnection(); mozStorageTransaction transaction(mDBConn, PR_FALSE); nsresult rv = InitStatements(); NS_ENSURE_SUCCESS(rv, rv); rv = InitRoots(); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); mCanNotify = true; // Add observers nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); annosvc->AddObserver(this); // allows us to notify on title changes. MUST BE LAST so it is impossible // to fail after this call, or the history service will have a reference to // us and we won't go away. history->AddObserver(this, PR_FALSE); // DO NOT PUT STUFF HERE that can fail. See observer comment above. return NS_OK; } // nsNavBookmarks::InitTables // // All commands that initialize the schema of the DB go in here. This is // called from history init before the dummy DB connection is started that // will prevent us from modifying the schema. nsresult // static nsNavBookmarks::InitTables(mozIStorageConnection* aDBConn) { PRBool exists; nsresult rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks"), &exists); NS_ENSURE_SUCCESS(rv, rv); if (! exists) { rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS); NS_ENSURE_SUCCESS(rv, rv); // This index will make it faster to determine if a given item is // bookmarked (used by history queries and vacuuming, for example). // Making it compound with "type" speeds up type-differentiation // queries, such as expiration and search. rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE); NS_ENSURE_SUCCESS(rv, rv); // The most common operation is to find the children given a parent and position. rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION); NS_ENSURE_SUCCESS(rv, rv); // fast access to lastModified is useful during sync and to get // last modified bookmark title for tags container's children. rv = aDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED); NS_ENSURE_SUCCESS(rv, rv); } // moz_bookmarks_roots rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_roots"), &exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_ROOTS); NS_ENSURE_SUCCESS(rv, rv); } // moz_keywords rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_keywords"), &exists); NS_ENSURE_SUCCESS(rv, rv); if (! exists) { rv = aDBConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS); NS_ENSURE_SUCCESS(rv, rv); // Create trigger to update as well rv = aDBConn->ExecuteSimpleSQL(CREATE_KEYWORD_VALIDITY_TRIGGER); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsNavBookmarks::InitStatements // // Create common statements to query the database nsresult nsNavBookmarks::InitStatements() { // mDBFindURIBookmarks // Double ordering covers possible lastModified ties, that could happen when // importing, syncing or due to extensions. nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT b.id " "FROM moz_bookmarks b " "WHERE b.type = ?2 AND b.fk = ( " "SELECT id FROM moz_places_temp " "WHERE url = ?1 " "UNION " "SELECT id FROM moz_places " "WHERE url = ?1 " "LIMIT 1 " ") " "ORDER BY b.lastModified DESC, b.id DESC "), getter_AddRefs(mDBFindURIBookmarks)); NS_ENSURE_SUCCESS(rv, rv); // Construct a result where the first columns exactly match those returned by // mDBGetURLPageInfo, and additionally contains columns for position, // item_child, and folder_child from moz_bookmarks. // Results are kGetInfoIndex_* // mDBGetChildren: select all children of a given folder, sorted by position // This is a LEFT OUTER JOIN with moz_places since folders does not have // a reference into that table. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT h.id, h.url, COALESCE(b.title, h.title), " "h.rev_host, h.visit_count, h.last_visit_date, f.url, null, b.id, " "b.dateAdded, b.lastModified, b.parent, null, b.position, b.type, " "b.fk, b.folder_type " "FROM moz_bookmarks b " "JOIN moz_places_temp h ON b.fk = h.id " "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " "WHERE b.parent = ?1 " "UNION ALL " "SELECT h.id, h.url, COALESCE(b.title, h.title), " "h.rev_host, h.visit_count, h.last_visit_date, f.url, null, b.id, " "b.dateAdded, b.lastModified, b.parent, null, b.position, b.type, " "b.fk, b.folder_type " "FROM moz_bookmarks b " "LEFT JOIN moz_places h ON b.fk = h.id " "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " "WHERE b.parent = ?1 " "AND (b.fk ISNULL OR b.fk NOT IN (select id FROM moz_places_temp)) " "ORDER BY position ASC"), getter_AddRefs(mDBGetChildren)); NS_ENSURE_SUCCESS(rv, rv); // mDBFolderCount: count all of the children of a given folder and checks // that it exists. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT COUNT(*), " "(SELECT id FROM moz_bookmarks WHERE id = ?1) " "FROM moz_bookmarks WHERE parent = ?1"), getter_AddRefs(mDBFolderCount)); NS_ENSURE_SUCCESS(rv, rv); rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT position FROM moz_bookmarks WHERE id = ?1"), getter_AddRefs(mDBGetItemIndex)); NS_ENSURE_SUCCESS(rv, rv); rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id, fk, type FROM moz_bookmarks WHERE parent = ?1 AND position = ?2"), getter_AddRefs(mDBGetChildAt)); NS_ENSURE_SUCCESS(rv, rv); // get bookmark/folder/separator properties rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT b.id, " "IFNULL((SELECT url FROM moz_places_temp WHERE id = b.fk), " "(SELECT url FROM moz_places WHERE id = b.fk)), " "b.title, b.position, b.fk, b.parent, b.type, b.folder_type, " "b.dateAdded, b.lastModified " "FROM moz_bookmarks b " "WHERE b.id = ?1"), getter_AddRefs(mDBGetItemProperties)); NS_ENSURE_SUCCESS(rv, rv); rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT item_id FROM moz_items_annos " "WHERE content = ?1 " "LIMIT 1"), getter_AddRefs(mDBGetItemIdForGUID)); NS_ENSURE_SUCCESS(rv, rv); // mDBGetRedirectDestinations // input = page ID, time threshold; output = unique ID input has redirected to rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT DISTINCT dest_v.place_id " "FROM moz_historyvisits_temp source_v " "JOIN moz_historyvisits_temp dest_v ON dest_v.from_visit = source_v.id " "WHERE source_v.place_id = ?1 " "AND source_v.visit_date >= ?2 " "AND dest_v.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") " "UNION " "SELECT DISTINCT dest_v.place_id " "FROM moz_historyvisits_temp source_v " "JOIN moz_historyvisits dest_v ON dest_v.from_visit = source_v.id " "WHERE source_v.place_id = ?1 " "AND source_v.visit_date >= ?2 " "AND dest_v.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") " "UNION " "SELECT DISTINCT dest_v.place_id " "FROM moz_historyvisits source_v " "JOIN moz_historyvisits_temp dest_v ON dest_v.from_visit = source_v.id " "WHERE source_v.place_id = ?1 " "AND source_v.visit_date >= ?2 " "AND dest_v.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") " "UNION " "SELECT DISTINCT dest_v.place_id " "FROM moz_historyvisits source_v " "JOIN moz_historyvisits dest_v ON dest_v.from_visit = source_v.id " "WHERE source_v.place_id = ?1 " "AND source_v.visit_date >= ?2 " "AND dest_v.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") "), getter_AddRefs(mDBGetRedirectDestinations)); NS_ENSURE_SUCCESS(rv, rv); // mDBInsertBookmark rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "INSERT INTO moz_bookmarks " "(id, fk, type, parent, position, title, folder_type, dateAdded, lastModified) " "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mDBInsertBookmark)); NS_ENSURE_SUCCESS(rv, rv); // mDBIsBookmarkedInDatabase // Just select position since it's just an int32 and may be faster. // We don't actually care about the data, just whether there is any. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT position FROM moz_bookmarks WHERE fk = ?1 AND type = ?2"), getter_AddRefs(mDBIsBookmarkedInDatabase)); NS_ENSURE_SUCCESS(rv, rv); // mDBIsRealBookmark // Checks to make sure a place_id is a bookmark, and isn't a livemark. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id " "FROM moz_bookmarks " "WHERE fk = ?1 " "AND type = ?2 " "AND parent NOT IN (" "SELECT a.item_id " "FROM moz_items_annos a " "JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id " "WHERE n.name = ?3" ") " "LIMIT 1"), getter_AddRefs(mDBIsRealBookmark)); NS_ENSURE_SUCCESS(rv, rv); // mDBGetLastBookmarkID rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id " "FROM moz_bookmarks " "ORDER BY ROWID DESC " "LIMIT 1"), getter_AddRefs(mDBGetLastBookmarkID)); NS_ENSURE_SUCCESS(rv, rv); // mDBSetItemDateAdded // lastModified is set to the same value as dateAdded. We do this for // performance reasons, since it will allow us to use an index to sort items // by date. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET dateAdded = ?1, lastModified = ?1 " "WHERE id = ?2"), getter_AddRefs(mDBSetItemDateAdded)); NS_ENSURE_SUCCESS(rv, rv); // mDBSetItemLastModified rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET lastModified = ?1 WHERE id = ?2"), getter_AddRefs(mDBSetItemLastModified)); NS_ENSURE_SUCCESS(rv, rv); // mDBSetItemIndex rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET position = ?2 WHERE id = ?1"), getter_AddRefs(mDBSetItemIndex)); NS_ENSURE_SUCCESS(rv, rv); // get keyword text for bookmark id rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT k.keyword FROM moz_bookmarks b " "JOIN moz_keywords k ON k.id = b.keyword_id " "WHERE b.id = ?1"), getter_AddRefs(mDBGetKeywordForBookmark)); NS_ENSURE_SUCCESS(rv, rv); // get keyword text for URI (must be a bookmarked URI) rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT k.keyword " "FROM ( " "SELECT id FROM moz_places_temp " "WHERE url = ?1 " "UNION ALL " "SELECT id FROM moz_places " "WHERE url = ?1 " "LIMIT 1 " ") AS h " "JOIN moz_bookmarks b ON b.fk = h.id " "JOIN moz_keywords k ON k.id = b.keyword_id"), getter_AddRefs(mDBGetKeywordForURI)); NS_ENSURE_SUCCESS(rv, rv); // get URI for keyword rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT url FROM moz_keywords k " "JOIN moz_bookmarks b ON b.keyword_id = k.id " "JOIN moz_places_temp h ON b.fk = h.id " "WHERE k.keyword = ?1 " "UNION ALL " "SELECT url FROM moz_keywords k " "JOIN moz_bookmarks b ON b.keyword_id = k.id " "JOIN moz_places h ON b.fk = h.id " "WHERE k.keyword = ?1 " "LIMIT 1"), getter_AddRefs(mDBGetURIForKeyword)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult nsNavBookmarks::FinalizeStatements() { mozIStorageStatement* stmts[] = { mDBGetChildren, mDBFindURIBookmarks, mDBFolderCount, mDBGetItemIndex, mDBGetChildAt, mDBGetItemProperties, mDBGetItemIdForGUID, mDBGetRedirectDestinations, mDBInsertBookmark, mDBIsBookmarkedInDatabase, mDBIsRealBookmark, mDBGetLastBookmarkID, mDBSetItemDateAdded, mDBSetItemLastModified, mDBSetItemIndex, mDBGetKeywordForURI, mDBGetKeywordForBookmark, mDBGetURIForKeyword }; for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(stmts); i++) { nsresult rv = nsNavHistory::FinalizeStatement(stmts[i]); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsNavBookmarks::InitRoots // // This locates and creates if necessary the root items in the bookmarks // folder hierarchy. These items are stored in a special roots table that // maps short predefined names to folder IDs. // // Normally, these folders will exist already and we will save their IDs // which are exposed through the bookmark service interface. // // If the root does not exist, a folder is created for it and the ID is // saved in the root table. No user-visible name is given to these folders // and they have no parent or other attributes. // // These attributes are set when the default_places.html file is imported. // It defines the hierarchy, and has special attributes that tell us when // a folder is one of our well-known roots. We then insert the root in the // defined point in the hierarchy and set its attributes from this. // // This should be called as the last part of the init process so that // all of the statements are set up and the service is ready to use. nsresult nsNavBookmarks::InitRoots() { nsCOMPtr getRootStatement; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = ?1"), getter_AddRefs(getRootStatement)); NS_ENSURE_SUCCESS(rv, rv); PRBool createdPlacesRoot = PR_FALSE; rv = CreateRoot(getRootStatement, NS_LITERAL_CSTRING("places"), &mRoot, 0, &createdPlacesRoot); NS_ENSURE_SUCCESS(rv, rv); getRootStatement->Reset(); rv = CreateRoot(getRootStatement, NS_LITERAL_CSTRING("menu"), &mBookmarksRoot, mRoot, nsnull); NS_ENSURE_SUCCESS(rv, rv); PRBool createdToolbarFolder; getRootStatement->Reset(); rv = CreateRoot(getRootStatement, NS_LITERAL_CSTRING("toolbar"), &mToolbarFolder, mRoot, &createdToolbarFolder); NS_ENSURE_SUCCESS(rv, rv); // Once toolbar was not a root, we may need to move over the items and // delete the custom folder if (!createdPlacesRoot && createdToolbarFolder) { nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); nsTArray folders; annosvc->GetItemsWithAnnotationTArray(BOOKMARKS_TOOLBAR_FOLDER_ANNO, &folders); if (folders.Length() > 0) { nsCOMPtr moveItems; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET parent = ?1 WHERE parent=?2"), getter_AddRefs(moveItems)); rv = moveItems->BindInt64Parameter(0, mToolbarFolder); NS_ENSURE_SUCCESS(rv, rv); rv = moveItems->BindInt64Parameter(1, folders[0]); NS_ENSURE_SUCCESS(rv, rv); rv = moveItems->Execute(); NS_ENSURE_SUCCESS(rv, rv); rv = RemoveFolder(folders[0]); NS_ENSURE_SUCCESS(rv, rv); } } getRootStatement->Reset(); rv = CreateRoot(getRootStatement, NS_LITERAL_CSTRING("tags"), &mTagRoot, mRoot, nsnull); NS_ENSURE_SUCCESS(rv, rv); getRootStatement->Reset(); rv = CreateRoot(getRootStatement, NS_LITERAL_CSTRING("unfiled"), &mUnfiledRoot, mRoot, nsnull); NS_ENSURE_SUCCESS(rv, rv); // Set titles for special folders // We cannot rely on createdPlacesRoot due to Fx3beta->final migration path PRUint16 databaseStatus = nsINavHistoryService::DATABASE_STATUS_OK; nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); rv = history->GetDatabaseStatus(&databaseStatus); if (NS_FAILED(rv) || databaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) { rv = InitDefaults(); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsNavBookmarks::InitDefaults // // Initializes default bookmarks and containers. // Pulls from places.propertes for l10n. // Replaces the old default_places.html file. nsresult nsNavBookmarks::InitDefaults() { nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); nsIStringBundle *bundle = history->GetBundle(); NS_ENSURE_TRUE(bundle, NS_ERROR_OUT_OF_MEMORY); // Bookmarks Menu nsXPIDLString bookmarksTitle; nsresult rv = bundle->GetStringFromName(NS_LITERAL_STRING("BookmarksMenuFolderTitle").get(), getter_Copies(bookmarksTitle)); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemTitle(mBookmarksRoot, NS_ConvertUTF16toUTF8(bookmarksTitle)); NS_ENSURE_SUCCESS(rv, rv); // Bookmarks Toolbar nsXPIDLString toolbarTitle; rv = bundle->GetStringFromName(NS_LITERAL_STRING("BookmarksToolbarFolderTitle").get(), getter_Copies(toolbarTitle)); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemTitle(mToolbarFolder, NS_ConvertUTF16toUTF8(toolbarTitle)); NS_ENSURE_SUCCESS(rv, rv); // Unsorted Bookmarks nsXPIDLString unfiledTitle; rv = bundle->GetStringFromName(NS_LITERAL_STRING("UnsortedBookmarksFolderTitle").get(), getter_Copies(unfiledTitle)); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemTitle(mUnfiledRoot, NS_ConvertUTF16toUTF8(unfiledTitle)); NS_ENSURE_SUCCESS(rv, rv); // Tags nsXPIDLString tagsTitle; rv = bundle->GetStringFromName(NS_LITERAL_STRING("TagsFolderTitle").get(), getter_Copies(tagsTitle)); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemTitle(mTagRoot, NS_ConvertUTF16toUTF8(tagsTitle)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // nsNavBookmarks::CreateRoot // // This gets or creates a root folder of the given type. aWasCreated // (optional) is true if the folder had to be created, false if we just used // an old one. The statement that gets a folder ID from a root name is // passed in so the DB only needs to parse the statement once, and we don't // have to have a global for this. Creation is less optimized because it // happens rarely. nsresult nsNavBookmarks::CreateRoot(mozIStorageStatement* aGetRootStatement, const nsCString& name, PRInt64* aID, PRInt64 aParentID, PRBool* aWasCreated) { PRBool hasResult = PR_FALSE; nsresult rv = aGetRootStatement->BindUTF8StringParameter(0, name); NS_ENSURE_SUCCESS(rv, rv); rv = aGetRootStatement->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, rv); if (hasResult) { if (aWasCreated) *aWasCreated = PR_FALSE; rv = aGetRootStatement->GetInt64(0, aID); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(*aID != 0, "Root is 0 for some reason, folders can't have 0 ID"); return NS_OK; } if (aWasCreated) *aWasCreated = PR_TRUE; // create folder with no name or attributes nsCOMPtr insertStatement; rv = CreateFolder(aParentID, EmptyCString(), nsINavBookmarksService::DEFAULT_INDEX, aID); NS_ENSURE_SUCCESS(rv, rv); // save root ID rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "INSERT INTO moz_bookmarks_roots (root_name, folder_id) VALUES (?1, ?2)"), getter_AddRefs(insertStatement)); NS_ENSURE_SUCCESS(rv, rv); rv = insertStatement->BindUTF8StringParameter(0, name); NS_ENSURE_SUCCESS(rv, rv); rv = insertStatement->BindInt64Parameter(1, *aID); NS_ENSURE_SUCCESS(rv, rv); rv = insertStatement->Execute(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // nsNavBookmarks::GetBookmarksHash // // Getter and lazy initializer of the bookmarks redirect hash. // See FillBookmarksHash for more information. nsDataHashtable* nsNavBookmarks::GetBookmarksHash() { if (!mBookmarksHash.IsInitialized()) { nsresult rv = FillBookmarksHash(); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "FillBookmarksHash() failed!"); } return &mBookmarksHash; } // nsNavBookmarks::FillBookmarksHash // // This initializes the bookmarks hashtable that tells us which bookmark // a given URI redirects to. This hashtable includes all URIs that // redirect to bookmarks. nsresult nsNavBookmarks::FillBookmarksHash() { PRBool hasMore; // first init the hashtable NS_ENSURE_TRUE(mBookmarksHash.Init(1024), NS_ERROR_OUT_OF_MEMORY); // first populate the hashtable with all bookmarks nsCOMPtr statement; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT b.fk " "FROM moz_bookmarks b " "WHERE b.type = ?1 " "AND b.fk NOTNULL"), getter_AddRefs(statement)); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt32Parameter(0, TYPE_BOOKMARK); NS_ENSURE_SUCCESS(rv, rv); while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { PRInt64 pageID; rv = statement->GetInt64(0, &pageID); NS_ENSURE_TRUE(mBookmarksHash.Put(pageID, pageID), NS_ERROR_OUT_OF_MEMORY); } // Find all pages h2 that have been redirected to from a bookmarked URI: // bookmarked -> url (h1) url (h2) // | ^ // . | // visit (v1) -> destination visit (v2) // This should catch most redirects, which are only one level. More levels of // redirection will be handled separately. rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT v1.place_id, v2.place_id " "FROM moz_bookmarks b " "LEFT JOIN moz_historyvisits_temp v1 on b.fk = v1.place_id " "LEFT JOIN moz_historyvisits v2 on v2.from_visit = v1.id " "WHERE b.fk IS NOT NULL AND b.type = ?1 " "AND v2.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") GROUP BY v2.place_id " "UNION " "SELECT v1.place_id, v2.place_id " "FROM moz_bookmarks b " "LEFT JOIN moz_historyvisits v1 on b.fk = v1.place_id " "LEFT JOIN moz_historyvisits_temp v2 on v2.from_visit = v1.id " "WHERE b.fk IS NOT NULL AND b.type = ?1 " "AND v2.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") GROUP BY v2.place_id " "UNION " "SELECT v1.place_id, v2.place_id " "FROM moz_bookmarks b " "LEFT JOIN moz_historyvisits v1 on b.fk = v1.place_id " "LEFT JOIN moz_historyvisits v2 on v2.from_visit = v1.id " "WHERE b.fk IS NOT NULL AND b.type = ?1 " "AND v2.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") GROUP BY v2.place_id " "UNION " "SELECT v1.place_id, v2.place_id " "FROM moz_bookmarks b " "LEFT JOIN moz_historyvisits_temp v1 on b.fk = v1.place_id " "LEFT JOIN moz_historyvisits_temp v2 on v2.from_visit = v1.id " "WHERE b.fk IS NOT NULL AND b.type = ?1 " "AND v2.visit_type IN (") + nsPrintfCString("%d,%d", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + NS_LITERAL_CSTRING(") GROUP BY v2.place_id "), getter_AddRefs(statement)); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(0, TYPE_BOOKMARK); NS_ENSURE_SUCCESS(rv, rv); while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { PRInt64 fromId, toId; statement->GetInt64(0, &fromId); statement->GetInt64(1, &toId); NS_ENSURE_TRUE(mBookmarksHash.Put(toId, fromId), NS_ERROR_OUT_OF_MEMORY); // handle redirects deeper than one level rv = RecursiveAddBookmarkHash(fromId, toId, 0); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsNavBookmarks::AddBookmarkToHash // // Given a bookmark that was potentially added, this goes through all // redirects that this page may have resulted in and adds them to our hash. // Note that this takes the ID of the URL in the history system, which we // generally have when calling this function and which makes it faster. // // For better performance, this call should be in a DB transaction. // // @see RecursiveAddBookmarkHash nsresult nsNavBookmarks::AddBookmarkToHash(PRInt64 aPlaceId, PRTime aMinTime) { if (!GetBookmarksHash()->Put(aPlaceId, aPlaceId)) return NS_ERROR_OUT_OF_MEMORY; return RecursiveAddBookmarkHash(aPlaceId, aPlaceId, aMinTime); } // nsNavBookmkars::RecursiveAddBookmarkHash // // Used to add a new level of redirect information to the bookmark hash. // Given a source bookmark 'aBookmark' and 'aCurrentSource' that has already // been added to the hashtable, this will add all redirect destinations of // 'aCurrentSource'. Will call itself recursively to walk down the chain. // // 'aMinTime' is the minimum time to consider visits from. Visits previous // to this will not be considered. This allows the search to be much more // efficient if you know something happened recently. Use 0 for the min time // to search all history for redirects. nsresult nsNavBookmarks::RecursiveAddBookmarkHash(PRInt64 aPlaceID, PRInt64 aCurrentSource, PRTime aMinTime) { nsresult rv; nsTArray found; // scope for the DB statement. The statement must be reset by the time we // recursively call ourselves again, because our recursive call will use the // same statement. { mozStorageStatementScoper scoper(mDBGetRedirectDestinations); rv = mDBGetRedirectDestinations->BindInt64Parameter(0, aCurrentSource); NS_ENSURE_SUCCESS(rv, rv); rv = mDBGetRedirectDestinations->BindInt64Parameter(1, aMinTime); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore; while (NS_SUCCEEDED(mDBGetRedirectDestinations->ExecuteStep(&hasMore)) && hasMore) { // add this newly found redirect destination to the hashtable PRInt64 curID; rv = mDBGetRedirectDestinations->GetInt64(0, &curID); NS_ENSURE_SUCCESS(rv, rv); // It is very important we ignore anything already in our hashtable. It // is actually pretty common to get loops of redirects. For example, // a restricted page will redirect you to a login page, which will // redirect you to the restricted page again with the proper cookie. PRInt64 alreadyExistingOne; if (GetBookmarksHash()->Get(curID, &alreadyExistingOne)) continue; if (!GetBookmarksHash()->Put(curID, aPlaceID)) return NS_ERROR_OUT_OF_MEMORY; // save for recursion later found.AppendElement(curID); } } // recurse on each found item now that we're done with the statement for (PRUint32 i = 0; i < found.Length(); i ++) { rv = RecursiveAddBookmarkHash(aPlaceID, found[i], aMinTime); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsNavBookmarks::UpdateBookmarkHashOnRemove // // Call this when a bookmark is removed. It will see if the bookmark still // exists anywhere in the system, and, if not, remove all references to it // in the bookmark hashtable. // // The callback takes a pointer to what bookmark is being removed (as // an Int64 history page ID) as the userArg and removes all redirect // destinations that reference it. static PLDHashOperator RemoveBookmarkHashCallback(nsTrimInt64HashKey::KeyType aKey, PRInt64& aPlaceId, void* aUserArg) { const PRInt64* removeThisOne = reinterpret_cast(aUserArg); if (aPlaceId == *removeThisOne) return PL_DHASH_REMOVE; return PL_DHASH_NEXT; } nsresult nsNavBookmarks::UpdateBookmarkHashOnRemove(PRInt64 aPlaceId) { // note we have to use the DB version here since the hashtable may be // out-of-date PRBool inDB; nsresult rv = IsBookmarkedInDatabase(aPlaceId, &inDB); NS_ENSURE_SUCCESS(rv, rv); if (inDB) return NS_OK; // bookmark still exists, don't need to update hashtable // remove it GetBookmarksHash()->Enumerate(RemoveBookmarkHashCallback, reinterpret_cast(&aPlaceId)); return NS_OK; } PRBool nsNavBookmarks::IsRealBookmark(PRInt64 aPlaceId) { // Fast path is to check the hash table first. If it is in the hash table, // then verify that it is a real bookmark. PRInt64 bookmarkId; PRBool isBookmark = GetBookmarksHash()->Get(aPlaceId, &bookmarkId); if (!isBookmark) return PR_FALSE; { mozStorageStatementScoper scope(mDBIsRealBookmark); (void)mDBIsRealBookmark->BindInt64Parameter(0, aPlaceId); (void)mDBIsRealBookmark->BindInt32Parameter(1, TYPE_BOOKMARK); (void)mDBIsRealBookmark->BindUTF8StringParameter( 2, NS_LITERAL_CSTRING(LMANNO_FEEDURI) ); // If we get any rows, then there exists at least one bookmark corresponding // to aPlaceId that is not a livemark item. if (NS_SUCCEEDED(mDBIsRealBookmark->ExecuteStep(&isBookmark))) return isBookmark; } return PR_FALSE; } // nsNavBookmarks::IsBookmarkedInDatabase // // This checks to see if the specified place_id is actually bookmarked. // This does not check for redirects in the hashtable. nsresult nsNavBookmarks::IsBookmarkedInDatabase(PRInt64 aPlaceId, PRBool *aIsBookmarked) { mozStorageStatementScoper scope(mDBIsBookmarkedInDatabase); nsresult rv = mDBIsBookmarkedInDatabase->BindInt64Parameter(0, aPlaceId); NS_ENSURE_SUCCESS(rv, rv); rv = mDBIsBookmarkedInDatabase->BindInt32Parameter(1, TYPE_BOOKMARK); NS_ENSURE_SUCCESS(rv, rv); return mDBIsBookmarkedInDatabase->ExecuteStep(aIsBookmarked); } nsresult nsNavBookmarks::AdjustIndices(PRInt64 aFolder, PRInt32 aStartIndex, PRInt32 aEndIndex, PRInt32 aDelta) { NS_ASSERTION(aStartIndex <= aEndIndex, "start index must be <= end index"); nsCAutoString buffer; buffer.AssignLiteral("UPDATE moz_bookmarks SET position = position + "); buffer.AppendInt(aDelta); buffer.AppendLiteral(" WHERE parent = "); buffer.AppendInt(aFolder); if (aStartIndex != 0) { buffer.AppendLiteral(" AND position >= "); buffer.AppendInt(aStartIndex); } if (aEndIndex != PR_INT32_MAX) { buffer.AppendLiteral(" AND position <= "); buffer.AppendInt(aEndIndex); } nsresult rv = mDBConn->ExecuteSimpleSQL(buffer); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetPlacesRoot(PRInt64 *aRoot) { *aRoot = mRoot; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetBookmarksMenuFolder(PRInt64 *aRoot) { *aRoot = mBookmarksRoot; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetToolbarFolder(PRInt64 *aFolderId) { *aFolderId = mToolbarFolder; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetTagsFolder(PRInt64 *aRoot) { *aRoot = mTagRoot; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetUnfiledBookmarksFolder(PRInt64 *aRoot) { *aRoot = mUnfiledRoot; return NS_OK; } nsresult nsNavBookmarks::InsertBookmarkInDB(PRInt64 aItemId, PRInt64 aPlaceId, enum ItemType aItemType, PRInt64 aParentId, PRInt32 aIndex, const nsACString &aTitle, PRTime aDateAdded, PRTime aLastModified, const nsAString &aServiceContractId, PRInt64 *_newItemId) { NS_ASSERTION(_newItemId, "Null pointer passed to InsertBookmarkInDB!"); mozStorageStatementScoper scope(mDBInsertBookmark); nsresult rv; if (aItemId && aItemId != -1) rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_Id, aItemId); else rv = mDBInsertBookmark->BindNullParameter(kInsertBookmarkIndex_Id); NS_ENSURE_SUCCESS(rv, rv); if (aPlaceId && aPlaceId != -1) rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_PlaceId, aPlaceId); else rv = mDBInsertBookmark->BindNullParameter(kInsertBookmarkIndex_PlaceId); NS_ENSURE_SUCCESS(rv, rv); rv = mDBInsertBookmark->BindInt32Parameter(kInsertBookmarkIndex_Type, aItemType); NS_ENSURE_SUCCESS(rv, rv); rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_Parent, aParentId); NS_ENSURE_SUCCESS(rv, rv); rv = mDBInsertBookmark->BindInt32Parameter(kInsertBookmarkIndex_Position, aIndex); NS_ENSURE_SUCCESS(rv, rv); // Support NULL titles. if (aTitle.IsVoid()) rv = mDBInsertBookmark->BindNullParameter(kInsertBookmarkIndex_Title); else rv = mDBInsertBookmark->BindUTF8StringParameter(kInsertBookmarkIndex_Title, aTitle); NS_ENSURE_SUCCESS(rv, rv); if (aServiceContractId.IsEmpty()) rv = mDBInsertBookmark->BindNullParameter(kInsertBookmarkIndex_ServiceContractId); else rv = mDBInsertBookmark->BindStringParameter(kInsertBookmarkIndex_ServiceContractId, aServiceContractId); NS_ENSURE_SUCCESS(rv, rv); rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_DateAdded, aDateAdded); NS_ENSURE_SUCCESS(rv, rv); if (aLastModified) rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_LastModified, aLastModified); else rv = mDBInsertBookmark->BindInt64Parameter(kInsertBookmarkIndex_LastModified, aDateAdded); NS_ENSURE_SUCCESS(rv, rv); rv = mDBInsertBookmark->Execute(); NS_ENSURE_SUCCESS(rv, rv); if (!aItemId || aItemId == -1) { // Get the new inserted item id. mozStorageStatementScoper scoper(mDBGetLastBookmarkID); PRBool hasResult; rv = mDBGetLastBookmarkID->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED); *_newItemId = mDBGetLastBookmarkID->AsInt64(0); } else *_newItemId = aItemId; // Update last modified date of the parent folder. // XXX TODO: This should be done recursively for all ancestors, that would // be slow without a nested tree though. See bug 408991. rv = SetItemDateInternal(mDBSetItemLastModified, aParentId, aDateAdded); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::InsertBookmark(PRInt64 aFolder, nsIURI *aURI, PRInt32 aIndex, const nsACString& aTitle, PRInt64 *aNewBookmarkId) { NS_ENSURE_ARG(aURI); NS_ENSURE_ARG_POINTER(aNewBookmarkId); // You can pass -1 to indicate append, but no other negative number is allowed if (aIndex < nsINavBookmarksService::DEFAULT_INDEX) return NS_ERROR_INVALID_ARG; mozStorageTransaction transaction(mDBConn, PR_FALSE); nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); // This is really a place ID PRInt64 childID; nsresult rv = history->GetUrlIdFor(aURI, &childID, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); PRInt32 index; PRInt32 folderCount; rv = FolderCount(aFolder, &folderCount); NS_ENSURE_SUCCESS(rv, rv); if (aIndex == nsINavBookmarksService::DEFAULT_INDEX || aIndex >= folderCount) { index = folderCount; } else { index = aIndex; rv = AdjustIndices(aFolder, index, PR_INT32_MAX, 1); NS_ENSURE_SUCCESS(rv, rv); } nsString voidString; voidString.SetIsVoid(PR_TRUE); rv = InsertBookmarkInDB(-1, childID, BOOKMARK, aFolder, index, aTitle, PR_Now(), nsnull, EmptyString(), aNewBookmarkId); NS_ENSURE_SUCCESS(rv, rv); // XXX // 0n import / fx 2 migration, is the frecency work going to slow us down? // We might want to skip this stuff, as well as the frecency work // caused by GetUrlIdFor() which calls InternalAddNewPage(). // If we do skip this, after import, we will // need to call FixInvalidFrecenciesForExcludedPlaces(). // We might need to call it anyways, if items aren't properly annotated // as livemarks feeds yet. nsCAutoString url; rv = aURI->GetSpec(url); NS_ENSURE_SUCCESS(rv, rv); // prevent place: queries from showing up in the URL bar autocomplete results PRBool isBookmark = !IsQueryURI(url); if (isBookmark) { // if it is a livemark item (the parent is a livemark), // we pass in false for isBookmark. otherwise, unvisited livemark // items will appear in URL autocomplete before we visit them. PRBool parentIsLivemark; nsCOMPtr lms = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = lms->IsLivemark(aFolder, &parentIsLivemark); NS_ENSURE_SUCCESS(rv, rv); isBookmark = !parentIsLivemark; } // when we created the moz_place entry for the new bookmark // (a side effect of calling GetUrlIdFor()) frecency -1; // now we re-calculate the frecency for this moz_place entry. rv = history->UpdateFrecency(childID, isBookmark); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); AddBookmarkToHash(childID, 0); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(*aNewBookmarkId, aFolder, index, TYPE_BOOKMARK)); // If the bookmark has been added to a tag container, notify all // bookmark-folder result nodes which contain a bookmark for the new // bookmark's url PRInt64 grandParentId; rv = GetFolderIdForItem(aFolder, &grandParentId); NS_ENSURE_SUCCESS(rv, rv); if (grandParentId == mTagRoot) { // query for all bookmarks for that URI, notify for each nsTArray bookmarks; rv = GetBookmarkIdsForURITArray(aURI, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString(), 0, TYPE_BOOKMARK)); } } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::RemoveItem(PRInt64 aItemId) { NS_ENSURE_TRUE(aItemId != mRoot, NS_ERROR_INVALID_ARG); nsresult rv; PRInt32 childIndex; PRInt64 placeId, folderId; PRUint16 itemType; nsCAutoString buffer; nsCAutoString spec; { // scoping to ensure the statement gets reset mozStorageStatementScoper scope(mDBGetItemProperties); mDBGetItemProperties->BindInt64Parameter(0, aItemId); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid bookmark id childIndex = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Position); placeId = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_PlaceID); folderId = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent); itemType = (PRUint16) mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); if (itemType == TYPE_BOOKMARK) { rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_URI, spec); NS_ENSURE_SUCCESS(rv, rv); } } if (itemType == TYPE_FOLDER) { rv = RemoveFolder(aItemId); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(aItemId, itemType)); mozStorageTransaction transaction(mDBConn, PR_FALSE); // First, remove item annotations nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); rv = annosvc->RemoveItemAnnotations(aItemId); NS_ENSURE_SUCCESS(rv, rv); buffer.AssignLiteral("DELETE FROM moz_bookmarks WHERE id = "); buffer.AppendInt(aItemId); rv = mDBConn->ExecuteSimpleSQL(buffer); NS_ENSURE_SUCCESS(rv, rv); if (childIndex != -1) { rv = AdjustIndices(folderId, childIndex + 1, PR_INT32_MAX, -1); NS_ENSURE_SUCCESS(rv, rv); } rv = SetItemDateInternal(mDBSetItemLastModified, folderId, PR_Now()); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); rv = UpdateBookmarkHashOnRemove(placeId); NS_ENSURE_SUCCESS(rv, rv); if (itemType == TYPE_BOOKMARK) { // UpdateFrecency needs to know whether placeId is still bookmarked. // Although we removed aItemId, placeId may still be bookmarked elsewhere; // IsRealBookmark will know. nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); rv = history->UpdateFrecency(placeId, IsRealBookmark(placeId)); NS_ENSURE_SUCCESS(rv, rv); } ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(aItemId, folderId, childIndex, itemType)); if (itemType == TYPE_BOOKMARK) { // If the removed bookmark was a child of a tag container, notify all // bookmark-folder result nodes which contain a bookmark for the removed // bookmark's url. PRInt64 grandParentId; rv = GetFolderIdForItem(folderId, &grandParentId); NS_ENSURE_SUCCESS(rv, rv); if (grandParentId == mTagRoot) { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), spec); NS_ENSURE_SUCCESS(rv, rv); nsTArray bookmarks; rv = GetBookmarkIdsForURITArray(uri, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString(), 0, TYPE_BOOKMARK)); } } } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::CreateFolder(PRInt64 aParent, const nsACString &aName, PRInt32 aIndex, PRInt64 *aNewFolder) { // NOTE: aParent can be null for root creation, so not checked NS_ENSURE_ARG_POINTER(aNewFolder); // CreateContainerWithID returns the index of the new folder, but that's not // used here. To avoid any risk of corrupting data should this function // be changed, we'll use a local variable to hold it. The PR_TRUE argument // will cause notifications to be sent to bookmark observers. PRInt32 localIndex = aIndex; nsString voidString; voidString.SetIsVoid(PR_TRUE); return CreateContainerWithID(-1, aParent, aName, voidString, PR_TRUE, &localIndex, aNewFolder); } NS_IMETHODIMP nsNavBookmarks::CreateDynamicContainer(PRInt64 aParent, const nsACString &aName, const nsAString &aContractId, PRInt32 aIndex, PRInt64 *aNewFolder) { if (aContractId.IsEmpty()) return NS_ERROR_INVALID_ARG; return CreateContainerWithID(-1, aParent, aName, aContractId, PR_FALSE, &aIndex, aNewFolder); } NS_IMETHODIMP nsNavBookmarks::GetFolderReadonly(PRInt64 aFolder, PRBool *aResult) { NS_ENSURE_ARG_MIN(aFolder, 1); NS_ENSURE_ARG_POINTER(aResult); nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); return annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, aResult); } NS_IMETHODIMP nsNavBookmarks::SetFolderReadonly(PRInt64 aFolder, PRBool aReadOnly) { NS_ENSURE_ARG_MIN(aFolder, 1); nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); if (aReadOnly) { return annosvc->SetItemAnnotationInt32(aFolder, READ_ONLY_ANNO, 1, 0, nsAnnotationService::EXPIRE_NEVER); } else { PRBool hasAnno; nsresult rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, &hasAnno); NS_ENSURE_SUCCESS(rv, rv); if (hasAnno) return annosvc->RemoveItemAnnotation(aFolder, READ_ONLY_ANNO); } return NS_OK; } nsresult nsNavBookmarks::CreateContainerWithID(PRInt64 aItemId, PRInt64 aParent, const nsACString& aName, const nsAString& aContractId, PRBool aIsBookmarkFolder, PRInt32* aIndex, PRInt64* aNewFolder) { // You can pass -1 to indicate append, but no other negative number is allowed if (*aIndex < -1) return NS_ERROR_INVALID_ARG; mozStorageTransaction transaction(mDBConn, PR_FALSE); PRInt32 index; PRInt32 folderCount; nsresult rv = FolderCount(aParent, &folderCount); NS_ENSURE_SUCCESS(rv, rv); if (*aIndex == nsINavBookmarksService::DEFAULT_INDEX || *aIndex >= folderCount) { index = folderCount; } else { index = *aIndex; rv = AdjustIndices(aParent, index, PR_INT32_MAX, 1); NS_ENSURE_SUCCESS(rv, rv); } ItemType containerType = aIsBookmarkFolder ? FOLDER : DYNAMIC_CONTAINER; rv = InsertBookmarkInDB(aItemId, nsnull, containerType, aParent, index, aName, PR_Now(), nsnull, aContractId, aNewFolder); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(*aNewFolder, aParent, index, containerType)); *aIndex = index; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::InsertSeparator(PRInt64 aParent, PRInt32 aIndex, PRInt64* aNewItemId) { NS_ENSURE_ARG_MIN(aParent, 1); // -1 means "append", but no other negative value is allowed. NS_ENSURE_ARG_MIN(aIndex, -1); NS_ENSURE_ARG_POINTER(aNewItemId); mozStorageTransaction transaction(mDBConn, PR_FALSE); PRInt32 index; PRInt32 folderCount; nsresult rv = FolderCount(aParent, &folderCount); NS_ENSURE_SUCCESS(rv, rv); if (aIndex == nsINavBookmarksService::DEFAULT_INDEX || aIndex >= folderCount) { index = folderCount; } else { index = aIndex; rv = AdjustIndices(aParent, index, PR_INT32_MAX, 1); NS_ENSURE_SUCCESS(rv, rv); } nsCString voidString; voidString.SetIsVoid(PR_TRUE); rv = InsertBookmarkInDB(-1, nsnull, SEPARATOR, aParent, index, voidString, PR_Now(), nsnull, EmptyString(), aNewItemId); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR)); return NS_OK; } nsresult nsNavBookmarks::GetLastChildId(PRInt64 aFolder, PRInt64* aItemId) { *aItemId = -1; nsCOMPtr statement; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id FROM moz_bookmarks WHERE parent = ?1 " "ORDER BY position DESC LIMIT 1"), getter_AddRefs(statement)); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(0, aFolder); NS_ENSURE_SUCCESS(rv, rv); PRBool found; rv = statement->ExecuteStep(&found); NS_ENSURE_SUCCESS(rv, rv); if (found) *aItemId = statement->AsInt64(0); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetIdForItemAt(PRInt64 aFolder, PRInt32 aIndex, PRInt64* aItemId) { NS_ENSURE_ARG_MIN(aFolder, 1); NS_ENSURE_ARG_POINTER(aItemId); *aItemId = -1; nsresult rv; if (aIndex == nsINavBookmarksService::DEFAULT_INDEX) { // Get last item within aFolder. rv = GetLastChildId(aFolder, aItemId); NS_ENSURE_SUCCESS(rv, rv); } else { // Get the item in aFolder with position aIndex. mozStorageStatementScoper scope(mDBGetChildAt); rv = mDBGetChildAt->BindInt64Parameter(0, aFolder); NS_ENSURE_SUCCESS(rv, rv); rv = mDBGetChildAt->BindInt32Parameter(1, aIndex); NS_ENSURE_SUCCESS(rv, rv); PRBool found; rv = mDBGetChildAt->ExecuteStep(&found); NS_ENSURE_SUCCESS(rv, rv); if (found) *aItemId = mDBGetChildAt->AsInt64(0); } return NS_OK; } nsresult nsNavBookmarks::GetParentAndIndexOfFolder(PRInt64 aFolder, PRInt64* aParent, PRInt32* aIndex) { nsCOMPtr statement; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT parent, position FROM moz_bookmarks WHERE id = ?1"), getter_AddRefs(statement)); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(0, aFolder); NS_ENSURE_SUCCESS(rv, rv); PRBool found; rv = statement->ExecuteStep(&found); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(found, NS_ERROR_INVALID_ARG); // Folder does not exist. *aParent = statement->AsInt64(0); *aIndex = statement->AsInt32(1); return NS_OK; } NS_HIDDEN_(nsresult) nsNavBookmarks::RemoveFolder(PRInt64 aFolderId) { NS_ENSURE_TRUE(aFolderId != mRoot, NS_ERROR_INVALID_ARG); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(aFolderId, TYPE_FOLDER)); mozStorageTransaction transaction(mDBConn, PR_FALSE); nsresult rv; PRInt64 parent; PRInt32 index, type; nsCAutoString folderType; { mozStorageStatementScoper scope(mDBGetItemProperties); rv = mDBGetItemProperties->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) { return NS_ERROR_INVALID_ARG; // folder is not in the hierarchy } type = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); parent = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent); index = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Position); rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId, folderType); NS_ENSURE_SUCCESS(rv, rv); } if (type != TYPE_FOLDER) { NS_WARNING("RemoveFolder(): aFolderId is not a folder!"); return NS_ERROR_INVALID_ARG; // aFolderId is not a folder! } // First, remove item annotations nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); rv = annosvc->RemoveItemAnnotations(aFolderId); NS_ENSURE_SUCCESS(rv, rv); // If this is a container bookmark, try to notify its service. if (folderType.Length() > 0) { // There is a type associated with this folder. nsCOMPtr bmcServ = do_GetService(folderType.get()); if (bmcServ) { rv = bmcServ->OnContainerRemoving(aFolderId); if (NS_FAILED(rv)) NS_WARNING("Remove folder container notification failed."); } } // Remove all of the folder's children rv = RemoveFolderChildren(aFolderId); NS_ENSURE_SUCCESS(rv, rv); // Remove the folder from its parent nsCAutoString buffer; buffer.AssignLiteral("DELETE FROM moz_bookmarks WHERE id = "); buffer.AppendInt(aFolderId); rv = mDBConn->ExecuteSimpleSQL(buffer); NS_ENSURE_SUCCESS(rv, rv); rv = AdjustIndices(parent, index + 1, PR_INT32_MAX, -1); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemDateInternal(mDBSetItemLastModified, parent, PR_Now()); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); if (aFolderId == mToolbarFolder) { mToolbarFolder = 0; } ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(aFolderId, parent, index, TYPE_FOLDER)); return NS_OK; } NS_IMPL_ISUPPORTS1(nsNavBookmarks::RemoveFolderTransaction, nsITransaction) NS_IMETHODIMP nsNavBookmarks::GetRemoveFolderTransaction(PRInt64 aFolderId, nsITransaction** aResult) { NS_ENSURE_ARG_MIN(aFolderId, 1); NS_ENSURE_ARG_POINTER(aResult); // Create and initialize a RemoveFolderTransaction object that can be used to // recreate the folder safely later. RemoveFolderTransaction* rft = new RemoveFolderTransaction(aFolderId); if (!rft) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult = rft); return NS_OK; } nsresult nsNavBookmarks::GetDescendantChildren(PRInt64 aFolderId, PRInt64 aGrandParentId, nsTArray& aFolderChildrenArray) { // New children will be added from this index on. PRUint32 startIndex = aFolderChildrenArray.Length(); nsresult rv; { // Collect children informations. mozStorageStatementScoper scope(mDBGetChildren); rv = mDBGetChildren->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore; while (NS_SUCCEEDED(mDBGetChildren->ExecuteStep(&hasMore)) && hasMore) { folderChildrenInfo child; child.itemId = mDBGetChildren->AsInt64(nsNavHistory::kGetInfoIndex_ItemId); child.parentId = aFolderId; child.grandParentId = aGrandParentId; child.itemType = (PRUint16) mDBGetChildren->AsInt32(kGetChildrenIndex_Type); child.placeId = mDBGetChildren->AsInt64(kGetChildrenIndex_PlaceID); child.index = mDBGetChildren->AsInt32(kGetChildrenIndex_Position); if (child.itemType == TYPE_BOOKMARK) { nsCAutoString URIString; rv = mDBGetChildren->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, URIString); NS_ENSURE_SUCCESS(rv, rv); child.url = URIString; } else if (child.itemType == TYPE_FOLDER) { nsCAutoString folderType; rv = mDBGetChildren->GetUTF8String(kGetChildrenIndex_ServiceContractId, folderType); NS_ENSURE_SUCCESS(rv, rv); child.folderType = folderType; } // Append item to children's array. aFolderChildrenArray.AppendElement(child); } } // Recursively call GetDescendantChildren for added folders. // We start at startIndex since previous folders are checked // by previous calls to this method. PRUint32 childCount = aFolderChildrenArray.Length(); for (PRUint32 i = startIndex; i < childCount; i++) { if (aFolderChildrenArray[i].itemType == TYPE_FOLDER) { GetDescendantChildren(aFolderChildrenArray[i].itemId, aFolderId, aFolderChildrenArray); } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId) { nsresult rv; PRUint16 itemType; PRInt64 grandParentId; { mozStorageStatementScoper scope(mDBGetItemProperties); rv = mDBGetItemProperties->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); // Sanity check: ensure that item exists. PRBool folderExists; if (NS_FAILED(mDBGetItemProperties->ExecuteStep(&folderExists)) || !folderExists) return NS_ERROR_INVALID_ARG; // Sanity check: ensure that this is a folder. itemType = (PRUint16) mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); if (itemType != TYPE_FOLDER) return NS_ERROR_INVALID_ARG; // Get the grandParent. // We have to do this only once since recursion will give us other // grandParents without the need of additional queries. grandParentId = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent); } // Fill folder children array recursively. nsTArray folderChildrenArray; rv = GetDescendantChildren(aFolderId, grandParentId, folderChildrenArray); NS_ENSURE_SUCCESS(rv, rv); // Build a string of folders whose children will be removed. nsCString foldersToRemove; for (PRUint32 i = 0; i < folderChildrenArray.Length(); i++) { folderChildrenInfo child = folderChildrenArray[i]; // Notify observers that we are about to remove this child. ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(child.itemId, child.itemType)); if (child.itemType == TYPE_FOLDER) { foldersToRemove.AppendLiteral(","); foldersToRemove.AppendInt(child.itemId); // If this is a dynamic container, try to notify its service that we // are going to remove it. // XXX (bug 484094) this should use a bookmark observer! if (child.folderType.Length() > 0) { nsCOMPtr bmcServ = do_GetService(child.folderType.get()); if (bmcServ) { rv = bmcServ->OnContainerRemoving(child.itemId); if (NS_FAILED(rv)) NS_WARNING("Remove folder container notification failed."); } } } } // Delete items from the database now. mozStorageTransaction transaction(mDBConn, PR_FALSE); nsCOMPtr deleteStatement; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "DELETE FROM moz_bookmarks " "WHERE parent IN (?1") + foldersToRemove + NS_LITERAL_CSTRING(")"), getter_AddRefs(deleteStatement)); NS_ENSURE_SUCCESS(rv, rv); rv = deleteStatement->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); rv = deleteStatement->Execute(); NS_ENSURE_SUCCESS(rv, rv); // Clean up orphan items annotations. rv = mDBConn->ExecuteSimpleSQL( NS_LITERAL_CSTRING( "DELETE FROM moz_items_annos " "WHERE id IN (" "SELECT a.id from moz_items_annos a " "LEFT JOIN moz_bookmarks b ON a.item_id = b.id " "WHERE b.id ISNULL)")); NS_ENSURE_SUCCESS(rv, rv); // Set the lastModified date. rv = SetItemDateInternal(mDBSetItemLastModified, aFolderId, PR_Now()); NS_ENSURE_SUCCESS(rv, rv); for (PRUint32 i = 0; i < folderChildrenArray.Length(); i++) { folderChildrenInfo child = folderChildrenArray[i]; if (child.itemType == TYPE_BOOKMARK) { PRInt64 placeId = child.placeId; UpdateBookmarkHashOnRemove(placeId); // UpdateFrecency needs to know whether placeId is still bookmarked. // Although we removed a child of aFolderId that bookmarked it, it may // still be bookmarked elsewhere; IsRealBookmark will know. nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); rv = history->UpdateFrecency(placeId, IsRealBookmark(placeId)); NS_ENSURE_SUCCESS(rv, rv); } } rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); // Call observers in reverse order to serve children before their parent. for (PRInt32 i = folderChildrenArray.Length() - 1; i >= 0 ; i--) { folderChildrenInfo child = folderChildrenArray[i]; ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(child.itemId, child.parentId, child.index, child.itemType)); if (child.itemType == TYPE_BOOKMARK) { // If the removed bookmark was a child of a tag container, notify all // bookmark-folder result nodes which contain a bookmark for the removed // bookmark's url. if (child.grandParentId == mTagRoot) { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), child.url); NS_ENSURE_SUCCESS(rv, rv); nsTArray bookmarks; rv = GetBookmarkIdsForURITArray(uri, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString(), 0, TYPE_BOOKMARK)); } } } } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::MoveItem(PRInt64 aItemId, PRInt64 aNewParent, PRInt32 aIndex) { NS_ENSURE_TRUE(aItemId != mRoot, NS_ERROR_INVALID_ARG); // You can pass -1 to indicate append, but no other negative number is allowed if (aIndex < -1) return NS_ERROR_INVALID_ARG; // Disallow making an item its own parent. if (aItemId == aNewParent) return NS_ERROR_INVALID_ARG; mozStorageTransaction transaction(mDBConn, PR_FALSE); // get item properties nsresult rv; PRInt64 oldParent; PRInt32 oldIndex; PRUint16 itemType; nsCAutoString folderType; { mozStorageStatementScoper scope(mDBGetItemProperties); rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) { return NS_ERROR_INVALID_ARG; // folder is not in the hierarchy } oldParent = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent); oldIndex = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Position); itemType = (PRUint16) mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); if (itemType == TYPE_FOLDER) { rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId, folderType); NS_ENSURE_SUCCESS(rv, rv); } } // if parent and index are the same, nothing to do if (oldParent == aNewParent && oldIndex == aIndex) return NS_OK; // Make sure aNewParent is not aFolder or a subfolder of aFolder if (itemType == TYPE_FOLDER) { PRInt64 p = aNewParent; while (p) { mozStorageStatementScoper scope(mDBGetItemProperties); if (p == aItemId) { return NS_ERROR_INVALID_ARG; } rv = mDBGetItemProperties->BindInt64Parameter(0, p); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); p = results ? mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent) : 0; } } // calculate new index PRInt32 newIndex; PRInt32 folderCount; rv = FolderCount(aNewParent, &folderCount); NS_ENSURE_SUCCESS(rv, rv); if (aIndex == nsINavBookmarksService::DEFAULT_INDEX || aIndex >= folderCount) { newIndex = folderCount; // If the parent remains the same, then the folder is really being moved // to count - 1 (since it's being removed from the old position) if (oldParent == aNewParent) { --newIndex; } } else { newIndex = aIndex; if (oldParent == aNewParent && newIndex > oldIndex) { // when an item is being moved lower in the same folder, the new index // refers to the index before it was removed. Removal causes everything // to shift up. --newIndex; } } // this is like the previous check, except this covers if // the specified index was -1 (append), and the calculated // new index is the same as the existing index if (aNewParent == oldParent && newIndex == oldIndex) { // Nothing to do! return NS_OK; } // adjust indices to account for the move // do this before we update the parent/index fields // or we'll re-adjust the index for the item we are moving if (oldParent == aNewParent) { // We can optimize the updates if moving within the same container. // We only shift the items between the old and new positions, since the // insertion will offset the deletion. if (oldIndex > newIndex) { rv = AdjustIndices(oldParent, newIndex, oldIndex - 1, 1); } else { rv = AdjustIndices(oldParent, oldIndex + 1, newIndex, -1); } } else { // We're moving between containers, so this happens in two steps. // First, fill the hole from the removal from the old parent. rv = AdjustIndices(oldParent, oldIndex + 1, PR_INT32_MAX, -1); NS_ENSURE_SUCCESS(rv, rv); // Now, make room in the new parent for the insertion. rv = AdjustIndices(aNewParent, newIndex, PR_INT32_MAX, 1); } NS_ENSURE_SUCCESS(rv, rv); // update parent/index fields nsCAutoString buffer; buffer.AssignLiteral("UPDATE moz_bookmarks SET "); if (aNewParent != oldParent) { buffer.AppendLiteral(" parent = "); buffer.AppendInt(aNewParent); } if (newIndex != oldIndex) { if (aNewParent != oldParent) buffer.AppendLiteral(", "); buffer.AppendLiteral(" position = "); buffer.AppendInt(newIndex); } buffer.AppendLiteral(" WHERE id = "); buffer.AppendInt(aItemId); rv = mDBConn->ExecuteSimpleSQL(buffer); NS_ENSURE_SUCCESS(rv, rv); PRTime now = PR_Now(); rv = SetItemDateInternal(mDBSetItemLastModified, oldParent, now); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemDateInternal(mDBSetItemLastModified, aNewParent, now); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); // notify bookmark observers ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemMoved(aItemId, oldParent, oldIndex, aNewParent, newIndex, itemType)); // notify dynamic container provider if there is one if (!folderType.IsEmpty()) { nsCOMPtr container = do_GetService(folderType.get(), &rv); if (NS_SUCCEEDED(rv)) { rv = container->OnContainerMoved(aItemId, aNewParent, newIndex); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } nsresult nsNavBookmarks::SetItemDateInternal(mozIStorageStatement* aStatement, PRInt64 aItemId, PRTime aValue) { mozStorageStatementScoper scope(aStatement); nsresult rv = aStatement->BindInt64Parameter(0, aValue); NS_ENSURE_SUCCESS(rv, rv); rv = aStatement->BindInt64Parameter(1, aItemId); NS_ENSURE_SUCCESS(rv, rv); rv = aStatement->Execute(); NS_ENSURE_SUCCESS(rv, rv); // note, we are not notifying the observers // that the item has changed. return NS_OK; } NS_IMETHODIMP nsNavBookmarks::SetItemDateAdded(PRInt64 aItemId, PRTime aDateAdded) { // GetItemType also ensures that aItemId points to a valid item. PRUint16 itemType; nsresult rv = GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemDateInternal(mDBSetItemDateAdded, aItemId, aDateAdded); NS_ENSURE_SUCCESS(rv, rv); // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded. ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("dateAdded"), PR_FALSE, nsPrintfCString(16, "%lld", aDateAdded), aDateAdded, itemType)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemDateAdded(PRInt64 aItemId, PRTime *aDateAdded) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aDateAdded); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid item id return mDBGetItemProperties->GetInt64(kGetItemPropertiesIndex_DateAdded, aDateAdded); } NS_IMETHODIMP nsNavBookmarks::SetItemLastModified(PRInt64 aItemId, PRTime aLastModified) { // GetItemType also ensures that aItemId points to a valid item. PRUint16 itemType; nsresult rv = GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, aLastModified); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("lastModified"), PR_FALSE, nsPrintfCString(16, "%lld", aLastModified), aLastModified, itemType)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemLastModified(PRInt64 aItemId, PRTime *aLastModified) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aLastModified); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid item id return mDBGetItemProperties->GetInt64(kGetItemPropertiesIndex_LastModified, aLastModified); } nsresult nsNavBookmarks::GetGUIDBase(nsAString &aGUIDBase) { if (!mGUIDBase.IsEmpty()) { aGUIDBase = mGUIDBase; return NS_OK; } // generate a new GUID base for this session nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1"); NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY); nsID GUID; nsresult rv = uuidgen->GenerateUUIDInPlace(&GUID); NS_ENSURE_SUCCESS(rv, rv); char GUIDChars[NSID_LENGTH]; GUID.ToProvidedString(GUIDChars); CopyASCIItoUTF16(GUIDChars, mGUIDBase); aGUIDBase = mGUIDBase; return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemGUID(PRInt64 aItemId, nsAString &aGUID) { NS_ENSURE_ARG_MIN(aItemId, 1); nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); nsresult rv = annosvc->GetItemAnnotationString(aItemId, GUID_ANNO, aGUID); if (NS_SUCCEEDED(rv) || rv != NS_ERROR_NOT_AVAILABLE) return rv; nsAutoString tmp; tmp.AppendInt(mItemCount++); aGUID.SetCapacity(NSID_LENGTH - 1 + tmp.Length()); nsString GUIDBase; rv = GetGUIDBase(GUIDBase); NS_ENSURE_SUCCESS(rv, rv); aGUID.Assign(GUIDBase); aGUID.Append(tmp); return SetItemGUID(aItemId, aGUID); } NS_IMETHODIMP nsNavBookmarks::SetItemGUID(PRInt64 aItemId, const nsAString &aGUID) { NS_ENSURE_ARG_MIN(aItemId, 1); PRInt64 checkId; GetItemIdForGUID(aGUID, &checkId); if (checkId != -1) return NS_ERROR_INVALID_ARG; // invalid GUID, already exists nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); return annosvc->SetItemAnnotationString(aItemId, GUID_ANNO, aGUID, 0, nsIAnnotationService::EXPIRE_NEVER); } NS_IMETHODIMP nsNavBookmarks::GetItemIdForGUID(const nsAString &aGUID, PRInt64 *aItemId) { NS_ENSURE_ARG_POINTER(aItemId); mozStorageStatementScoper scoper(mDBGetItemIdForGUID); nsresult rv = mDBGetItemIdForGUID->BindStringParameter(0, aGUID); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore = PR_FALSE; rv = mDBGetItemIdForGUID->ExecuteStep(&hasMore); if (NS_FAILED(rv) || ! hasMore) { *aItemId = -1; return NS_OK; // not found: return -1 } // found, get the itemId return mDBGetItemIdForGUID->GetInt64(0, aItemId); } NS_IMETHODIMP nsNavBookmarks::SetItemTitle(PRInt64 aItemId, const nsACString &aTitle) { // GetItemType also ensures that aItemId points to a valid item. PRUint16 itemType; nsresult rv = GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr statement; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET title = ?1, lastModified = ?2 WHERE id = ?3"), getter_AddRefs(statement)); NS_ENSURE_SUCCESS(rv, rv); // Support setting a null title, we support this in insertBookmark. if (aTitle.IsVoid()) rv = statement->BindNullParameter(0); else rv = statement->BindUTF8StringParameter(0, aTitle); NS_ENSURE_SUCCESS(rv, rv); PRTime lastModified = PR_Now(); rv = statement->BindInt64Parameter(1, lastModified); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(2, aItemId); NS_ENSURE_SUCCESS(rv, rv); rv = statement->Execute(); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("title"), PR_FALSE, aTitle, lastModified, itemType)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemTitle(PRInt64 aItemId, nsACString &aTitle) { NS_ENSURE_ARG_MIN(aItemId, 1); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid bookmark id return mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_Title, aTitle); } NS_IMETHODIMP nsNavBookmarks::GetBookmarkURI(PRInt64 aItemId, nsIURI **aURI) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aURI); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid bookmark id PRInt32 type = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); if (type != TYPE_BOOKMARK) return NS_ERROR_INVALID_ARG; // invalid type (only for bookmarks) nsCAutoString spec; rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_URI, spec); NS_ENSURE_SUCCESS(rv, rv); rv = NS_NewURI(aURI, spec); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemType(PRInt64 aItemId, PRUint16 *aType) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aType); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) { return NS_ERROR_INVALID_ARG; // invalid bookmark id } *aType = (PRUint16)mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); return NS_OK; } nsresult nsNavBookmarks::GetFolderType(PRInt64 aFolder, nsACString &aType) { mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aFolder); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) { return NS_ERROR_INVALID_ARG; } return mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId, aType); } nsresult nsNavBookmarks::ResultNodeForContainer(PRInt64 aID, nsNavHistoryQueryOptions *aOptions, nsNavHistoryResultNode **aNode) { mozStorageStatementScoper scope(mDBGetItemProperties); mDBGetItemProperties->BindInt64Parameter(0, aID); PRBool results; nsresult rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(results, "ResultNodeForContainer expects a valid item id"); // title nsCAutoString title; rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_Title, title); PRUint16 itemType = (PRUint16) mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); if (itemType == TYPE_DYNAMIC_CONTAINER) { // contract id nsCAutoString contractId; rv = mDBGetItemProperties->GetUTF8String(kGetItemPropertiesIndex_ServiceContractId, contractId); NS_ENSURE_SUCCESS(rv, rv); *aNode = new nsNavHistoryContainerResultNode(EmptyCString(), title, EmptyCString(), nsINavHistoryResultNode::RESULT_TYPE_DYNAMIC_CONTAINER, PR_TRUE, contractId, aOptions); (*aNode)->mItemId = aID; } else { // TYPE_FOLDER *aNode = new nsNavHistoryFolderResultNode(title, aOptions, aID, EmptyCString()); } if (!*aNode) return NS_ERROR_OUT_OF_MEMORY; (*aNode)->mDateAdded = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_DateAdded); (*aNode)->mLastModified = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_LastModified); NS_ADDREF(*aNode); return NS_OK; } nsresult nsNavBookmarks::QueryFolderChildren(PRInt64 aFolderId, nsNavHistoryQueryOptions *aOptions, nsCOMArray *aChildren) { mozStorageStatementScoper scope(mDBGetChildren); nsresult rv = mDBGetChildren->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; nsCOMPtr options = do_QueryInterface(aOptions, &rv); NS_ENSURE_SUCCESS(rv, rv); PRInt32 index = -1; while (NS_SUCCEEDED(mDBGetChildren->ExecuteStep(&results)) && results) { // The results will be in order of index. Even if we don't add a node // because it was excluded, we need to count its index, so do that // before doing anything else. Index was initialized to -1 above, so // it will start counting at 0 the first time through the loop. index ++; PRUint16 itemType = (PRUint16) mDBGetChildren->AsInt32(kGetChildrenIndex_Type); PRInt64 id = mDBGetChildren->AsInt64(nsNavHistory::kGetInfoIndex_ItemId); nsRefPtr node; if (itemType == TYPE_BOOKMARK) { nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); rv = history->RowToResult(mDBGetChildren, options, getter_AddRefs(node)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 nodeType; node->GetType(&nodeType); if ((nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY && aOptions->ExcludeQueries()) || (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY && nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT && aOptions->ExcludeItems())) { continue; } } else if (itemType == TYPE_FOLDER || itemType == TYPE_DYNAMIC_CONTAINER) { if (options->ExcludeReadOnlyFolders()) { // see if it's read only and skip it PRBool readOnly = PR_FALSE; GetFolderReadonly(id, &readOnly); if (readOnly) continue; // skip } rv = ResultNodeForContainer(id, aOptions, getter_AddRefs(node)); if (NS_FAILED(rv)) continue; } else { // separator if (aOptions->ExcludeItems()) { continue; } node = new nsNavHistorySeparatorResultNode(); NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY); // add the item identifier (RowToResult does so for bookmark items in // the next else block); node->mItemId = mDBGetChildren->AsInt64(nsNavHistory::kGetInfoIndex_ItemId); // date-added and last-modified node->mDateAdded = mDBGetChildren->AsInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded); node->mLastModified = mDBGetChildren->AsInt64(nsNavHistory::kGetInfoIndex_ItemLastModified); } // this method fills all bookmark queries, so we store the index of the // item in its parent node->mBookmarkIndex = index; NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY); } return NS_OK; } nsresult nsNavBookmarks::FolderCount(PRInt64 aFolderId, PRInt32 *aFolderCount) { *aFolderCount = 0; mozStorageStatementScoper scope(mDBFolderCount); nsresult rv = mDBFolderCount->BindInt64Parameter(0, aFolderId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBFolderCount->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); // Ensure that the folder we are looking for exists. NS_ENSURE_TRUE(mDBFolderCount->AsInt64(1) == aFolderId, NS_ERROR_INVALID_ARG); *aFolderCount = mDBFolderCount->AsInt32(0); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::IsBookmarked(nsIURI *aURI, PRBool *aBookmarked) { NS_ENSURE_ARG(aURI); NS_ENSURE_ARG_POINTER(aBookmarked); nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); // convert the URL to an ID PRInt64 urlID; nsresult rv = history->GetUrlIdFor(aURI, &urlID, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); if (! urlID) { // never seen this before, not even in history *aBookmarked = PR_FALSE; return NS_OK; } PRInt64 bookmarkedID; PRBool foundOne = GetBookmarksHash()->Get(urlID, &bookmarkedID); // IsBookmarked only tests if this exact URI is bookmarked, so we need to // check that the destination matches if (foundOne) *aBookmarked = (urlID == bookmarkedID); else *aBookmarked = PR_FALSE; #ifdef DEBUG // sanity check for the bookmark hashtable PRBool realBookmarked; rv = IsBookmarkedInDatabase(urlID, &realBookmarked); NS_ASSERTION(realBookmarked == *aBookmarked, "Bookmark hash table out-of-sync with the database"); #endif return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval) { NS_ENSURE_ARG(aURI); NS_ENSURE_ARG_POINTER(_retval); *_retval = nsnull; nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); // convert the URL to an ID PRInt64 urlID; nsresult rv = history->GetUrlIdFor(aURI, &urlID, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); if (! urlID) { // never seen this before, not even in history, leave result NULL return NS_OK; } PRInt64 bookmarkID; if (GetBookmarksHash()->Get(urlID, &bookmarkID)) { // found one, convert ID back to URL. This statement is NOT refcounted mozIStorageStatement* statement = history->DBGetIdPageInfo(); NS_ENSURE_TRUE(statement, NS_ERROR_UNEXPECTED); mozStorageStatementScoper scoper(statement); rv = statement->BindInt64Parameter(0, bookmarkID); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore; if (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { nsCAutoString spec; statement->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, spec); return NS_NewURI(_retval, spec); } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::ChangeBookmarkURI(PRInt64 aBookmarkId, nsIURI *aNewURI) { NS_ENSURE_ARG_MIN(aBookmarkId, 1); NS_ENSURE_ARG(aNewURI); mozStorageTransaction transaction(mDBConn, PR_FALSE); nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); PRInt64 placeId; nsresult rv = history->GetUrlIdFor(aNewURI, &placeId, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); if (!placeId) return NS_ERROR_INVALID_ARG; // We need the bookmark's current corresponding places ID below, so get it now // before we change it. GetBookmarkURI will fail if aBookmarkId is bad. nsCOMPtr oldURI; PRInt64 oldPlaceId; rv = GetBookmarkURI(aBookmarkId, getter_AddRefs(oldURI)); NS_ENSURE_SUCCESS(rv, rv); rv = history->GetUrlIdFor(oldURI, &oldPlaceId, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr statement; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET fk = ?1, lastModified = ?2 WHERE id = ?3"), getter_AddRefs(statement)); rv = statement->BindInt64Parameter(0, placeId); NS_ENSURE_SUCCESS(rv, rv); PRTime lastModified = PR_Now(); rv = statement->BindInt64Parameter(1, lastModified); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(2, aBookmarkId); NS_ENSURE_SUCCESS(rv, rv); rv = statement->Execute(); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); // Add new URI to bookmark hash. rv = AddBookmarkToHash(placeId, 0); NS_ENSURE_SUCCESS(rv, rv); // Remove old URI from bookmark hash. rv = UpdateBookmarkHashOnRemove(oldPlaceId); NS_ENSURE_SUCCESS(rv, rv); // Upon changing the URI for a bookmark, update the frecency for the new place. // UpdateFrecency needs to know whether placeId is bookmarked (as opposed // to a livemark item). Bookmarking it is exactly what we did above. rv = history->UpdateFrecency(placeId, PR_TRUE /* isBookmarked */); NS_ENSURE_SUCCESS(rv, rv); // Upon changing the URI for a bookmark, update the frecency for the old place. // UpdateFrecency again needs to know whether oldPlaceId is bookmarked. It may // no longer be, so we need to figure out whether it still is. Our strategy // is: find all bookmarks corresponding to oldPlaceId that are not livemark // items, i.e., whose parents are not livemarks. If any such bookmarks exist, // oldPlaceId is still bookmarked. rv = history->UpdateFrecency(oldPlaceId, IsRealBookmark(oldPlaceId)); NS_ENSURE_SUCCESS(rv, rv); nsCAutoString spec; rv = aNewURI->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); // Pass the new URI to OnItemChanged. ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("uri"), PR_FALSE, spec, lastModified, TYPE_BOOKMARK)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetFolderIdForItem(PRInt64 aItemId, PRInt64 *aFolderId) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aFolderId); mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_ERROR_INVALID_ARG; // invalid item id rv = mDBGetItemProperties->GetInt64(kGetItemPropertiesIndex_Parent, aFolderId); NS_ENSURE_SUCCESS(rv, rv); // this should not happen, but see bug #400448 for details NS_ENSURE_TRUE(aItemId != *aFolderId, NS_ERROR_UNEXPECTED); return NS_OK; } nsresult nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI *aURI, nsTArray &aResult) { NS_PRECONDITION(aURI, "Should not be null"); NS_ENSURE_ARG(aURI); mozStorageStatementScoper scope(mDBFindURIBookmarks); nsresult rv = BindStatementURI(mDBFindURIBookmarks, 0, aURI); NS_ENSURE_SUCCESS(rv, rv); rv = mDBFindURIBookmarks->BindInt32Parameter(1, TYPE_BOOKMARK); NS_ENSURE_SUCCESS(rv, rv); PRBool more; while (NS_SUCCEEDED((rv = mDBFindURIBookmarks->ExecuteStep(&more))) && more) { if (!aResult.AppendElement( mDBFindURIBookmarks->AsInt64(kFindBookmarksIndex_ID))) return NS_ERROR_OUT_OF_MEMORY; } NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetBookmarkIdsForURI(nsIURI *aURI, PRUint32 *aCount, PRInt64 **aBookmarks) { NS_ENSURE_ARG(aURI); NS_ENSURE_ARG_POINTER(aCount); NS_ENSURE_ARG_POINTER(aBookmarks); *aCount = 0; *aBookmarks = nsnull; nsTArray bookmarks; // Get the information from the DB as a TArray nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks); NS_ENSURE_SUCCESS(rv, rv); // Copy the results into a new array for output if (bookmarks.Length()) { *aBookmarks = static_cast (nsMemory::Alloc(sizeof(PRInt64) * bookmarks.Length())); if (! *aBookmarks) return NS_ERROR_OUT_OF_MEMORY; for (PRUint32 i = 0; i < bookmarks.Length(); i ++) (*aBookmarks)[i] = bookmarks[i]; } *aCount = bookmarks.Length(); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetItemIndex(PRInt64 aItemId, PRInt32 *aIndex) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_POINTER(aIndex); mozStorageStatementScoper scope(mDBGetItemIndex); mDBGetItemIndex->BindInt64Parameter(0, aItemId); PRBool results; nsresult rv = mDBGetItemIndex->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) { *aIndex = -1; return NS_OK; } *aIndex = mDBGetItemIndex->AsInt32(0); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::SetItemIndex(PRInt64 aItemId, PRInt32 aNewIndex) { NS_ENSURE_ARG_MIN(aItemId, 1); NS_ENSURE_ARG_MIN(aNewIndex, 0); nsresult rv; PRInt32 oldIndex = 0; PRInt64 parent = 0; PRUint16 itemType; { mozStorageStatementScoper scopeGet(mDBGetItemProperties); rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (!results) return NS_OK; oldIndex = mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Position); itemType = (PRUint16)mDBGetItemProperties->AsInt32(kGetItemPropertiesIndex_Type); parent = mDBGetItemProperties->AsInt64(kGetItemPropertiesIndex_Parent); } // Ensure we are not going out of range. PRInt32 folderCount; rv = FolderCount(parent, &folderCount); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG); mozStorageStatementScoper scoper(mDBSetItemIndex); rv = mDBSetItemIndex->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, rv); rv = mDBSetItemIndex->BindInt32Parameter(1, aNewIndex); NS_ENSURE_SUCCESS(rv, rv); rv = mDBSetItemIndex->Execute(); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemMoved(aItemId, parent, oldIndex, parent, aNewIndex, itemType)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::SetKeywordForBookmark(PRInt64 aBookmarkId, const nsAString& aKeyword) { NS_ENSURE_ARG_MIN(aBookmarkId, 1); // Shortcuts are always lowercased internally. nsAutoString kwd(aKeyword); ToLowerCase(kwd); mozStorageTransaction transaction(mDBConn, PR_FALSE); nsresult rv; PRBool results; PRInt64 keywordId = 0; if (!kwd.IsEmpty()) { // Attempt to find pre-existing keyword record nsCOMPtr getKeywordStmnt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id from moz_keywords WHERE keyword = ?1"), getter_AddRefs(getKeywordStmnt)); NS_ENSURE_SUCCESS(rv, rv); rv = getKeywordStmnt->BindStringParameter(0, kwd); NS_ENSURE_SUCCESS(rv, rv); rv = getKeywordStmnt->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, rv); if (results) { rv = getKeywordStmnt->GetInt64(0, &keywordId); NS_ENSURE_SUCCESS(rv, rv); } else { // If not already in the db, create new keyword record nsCOMPtr addKeywordStmnt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "INSERT INTO moz_keywords (keyword) VALUES (?1)"), getter_AddRefs(addKeywordStmnt)); NS_ENSURE_SUCCESS(rv, rv); rv = addKeywordStmnt->BindStringParameter(0, kwd); NS_ENSURE_SUCCESS(rv, rv); rv = addKeywordStmnt->Execute(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr idStmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id " "FROM moz_keywords " "ORDER BY ROWID DESC " "LIMIT 1"), getter_AddRefs(idStmt)); NS_ENSURE_SUCCESS(rv, rv); PRBool hasResult; rv = idStmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?"); keywordId = idStmt->AsInt64(0); } } // Update bookmark record w/ the keyword's id, or null nsCOMPtr updateKeywordStmnt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_bookmarks SET keyword_id = ?1, lastModified = ?2 " "WHERE id = ?3"), getter_AddRefs(updateKeywordStmnt)); NS_ENSURE_SUCCESS(rv, rv); rv = updateKeywordStmnt->BindInt64Parameter(0, keywordId); NS_ENSURE_SUCCESS(rv, rv); PRTime lastModified = PR_Now(); rv = updateKeywordStmnt->BindInt64Parameter(1, lastModified); NS_ENSURE_SUCCESS(rv, rv); rv = updateKeywordStmnt->BindInt64Parameter(2, aBookmarkId); NS_ENSURE_SUCCESS(rv, rv); rv = updateKeywordStmnt->Execute(); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); // Pass the new keyword to OnItemChanged. ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("keyword"), PR_FALSE, NS_ConvertUTF16toUTF8(aKeyword), lastModified, TYPE_BOOKMARK)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword) { NS_ENSURE_ARG(aURI); aKeyword.Truncate(0); mozStorageStatementScoper scoper(mDBGetKeywordForURI); nsresult rv = BindStatementURI(mDBGetKeywordForURI, 0, aURI); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore = PR_FALSE; rv = mDBGetKeywordForURI->ExecuteStep(&hasMore); if (NS_FAILED(rv) || ! hasMore) { aKeyword.SetIsVoid(PR_TRUE); return NS_OK; // not found: return void keyword string } // found, get the keyword return mDBGetKeywordForURI->GetString(0, aKeyword); } NS_IMETHODIMP nsNavBookmarks::GetKeywordForBookmark(PRInt64 aBookmarkId, nsAString& aKeyword) { NS_ENSURE_ARG_MIN(aBookmarkId, 1); aKeyword.Truncate(0); mozStorageStatementScoper scoper(mDBGetKeywordForBookmark); nsresult rv = mDBGetKeywordForBookmark->BindInt64Parameter(0, aBookmarkId); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore = PR_FALSE; rv = mDBGetKeywordForBookmark->ExecuteStep(&hasMore); if (NS_FAILED(rv) || ! hasMore) { aKeyword.SetIsVoid(PR_TRUE); return NS_OK; // not found: return void keyword string } // found, get the keyword return mDBGetKeywordForBookmark->GetString(0, aKeyword); } NS_IMETHODIMP nsNavBookmarks::GetURIForKeyword(const nsAString& aKeyword, nsIURI** aURI) { NS_ENSURE_ARG_POINTER(aURI); *aURI = nsnull; if (aKeyword.IsEmpty()) return NS_ERROR_INVALID_ARG; // Shortcuts are always lowercased internally. nsAutoString kwd(aKeyword); ToLowerCase(kwd); mozStorageStatementScoper scoper(mDBGetURIForKeyword); nsresult rv = mDBGetURIForKeyword->BindStringParameter(0, kwd); NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore = PR_FALSE; rv = mDBGetURIForKeyword->ExecuteStep(&hasMore); if (NS_FAILED(rv) || ! hasMore) return NS_OK; // not found: leave URI null // found, get the URI nsCAutoString spec; rv = mDBGetURIForKeyword->GetUTF8String(0, spec); NS_ENSURE_SUCCESS(rv, rv); return NS_NewURI(aURI, spec); } // See RunInBatchMode nsresult nsNavBookmarks::BeginUpdateBatch() { if (mBatchLevel++ == 0) { mozIStorageConnection* conn = mDBConn; PRBool transactionInProgress = PR_TRUE; // default to no transaction on err conn->GetTransactionInProgress(&transactionInProgress); mBatchHasTransaction = ! transactionInProgress; if (mBatchHasTransaction) conn->BeginTransaction(); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeginUpdateBatch()); } return NS_OK; } nsresult nsNavBookmarks::EndUpdateBatch() { if (--mBatchLevel == 0) { if (mBatchHasTransaction) mDBConn->CommitTransaction(); mBatchHasTransaction = PR_FALSE; ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnEndUpdateBatch()); } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::RunInBatchMode(nsINavHistoryBatchCallback* aCallback, nsISupports* aUserData) { NS_ENSURE_ARG(aCallback); BeginUpdateBatch(); nsresult rv = aCallback->RunBatched(aUserData); EndUpdateBatch(); return rv; } NS_IMETHODIMP nsNavBookmarks::AddObserver(nsINavBookmarkObserver *aObserver, PRBool aOwnsWeak) { NS_ENSURE_ARG(aObserver); return mObservers.AppendWeakElement(aObserver, aOwnsWeak); } NS_IMETHODIMP nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver *aObserver) { return mObservers.RemoveWeakElement(aObserver); } // nsNavBookmarks::nsINavHistoryObserver NS_IMETHODIMP nsNavBookmarks::OnBeginUpdateBatch() { // These aren't passed through to bookmark observers currently. return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnEndUpdateBatch() { // These aren't passed through to bookmark observers currently. return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnVisit(nsIURI *aURI, PRInt64 aVisitID, PRTime aTime, PRInt64 aSessionID, PRInt64 aReferringID, PRUint32 aTransitionType, PRUint32* aAdded) { // If the page is bookmarked, we need to notify observers PRBool bookmarked = PR_FALSE; IsBookmarked(aURI, &bookmarked); if (bookmarked) { // query for all bookmarks for that URI, notify for each nsTArray bookmarks; nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemVisited(bookmarks[i], aVisitID, aTime)); } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnBeforeDeleteURI(nsIURI *aURI) { return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnDeleteURI(nsIURI *aURI) { // If the page is bookmarked, we need to notify observers PRBool bookmarked = PR_FALSE; IsBookmarked(aURI, &bookmarked); if (bookmarked) { // query for all bookmarks for that URI, notify for each nsTArray bookmarks; nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i ++) ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("cleartime"), PR_FALSE, EmptyCString(), 0, TYPE_BOOKMARK)); } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnClearHistory() { // TODO(bryner): we should notify on visited-time change for all URIs return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle) { // NOOP. We don't consume page titles from moz_places anymore. // Title-change notifications are sent from SetItemTitle. return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnPageChanged(nsIURI *aURI, PRUint32 aWhat, const nsAString &aValue) { nsresult rv; if (aWhat == nsINavHistoryObserver::ATTRIBUTE_FAVICON) { // Favicons may be set to either pure URIs or to folder URIs PRBool isPlaceURI; rv = aURI->SchemeIs("place", &isPlaceURI); NS_ENSURE_SUCCESS(rv, rv); if (isPlaceURI) { nsCAutoString spec; rv = aURI->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); nsNavHistory *history = nsNavHistory::GetHistoryService(); NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); nsCOMArray queries; nsCOMPtr options; rv = history->QueryStringToQueryArray(spec, &queries, getter_AddRefs(options)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(queries.Count() == 1); NS_ENSURE_STATE(queries[0]->Folders().Length() == 1); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(queries[0]->Folders()[0], NS_LITERAL_CSTRING("favicon"), PR_FALSE, NS_ConvertUTF16toUTF8(aValue), 0, TYPE_BOOKMARK)); } else { // query for all bookmarks for that URI, notify for each nsTArray bookmarks; rv = GetBookmarkIdsForURITArray(aURI, bookmarks); NS_ENSURE_SUCCESS(rv, rv); if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i ++) ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("favicon"), PR_FALSE, NS_ConvertUTF16toUTF8(aValue), 0, TYPE_BOOKMARK)); } } } return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnPageExpired(nsIURI* aURI, PRTime aVisitTime, PRBool aWholeEntry) { // pages that are bookmarks shouldn't expire, so we don't need to handle it return NS_OK; } // nsIAnnotationObserver NS_IMETHODIMP nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName) { return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnItemAnnotationSet(PRInt64 aItemId, const nsACString& aName) { // GetItemType also ensures that aItemId points to a valid item. PRUint16 itemType; nsresult rv = GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); PRTime lastModified = PR_Now(); rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, lastModified); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString(), lastModified, itemType)); return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName) { return NS_OK; } NS_IMETHODIMP nsNavBookmarks::OnItemAnnotationRemoved(PRInt64 aItemId, const nsACString& aName) { // GetItemType also ensures that aItemId points to a valid item. PRUint16 itemType; nsresult rv = GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); PRTime lastModified = PR_Now(); rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, lastModified); NS_ENSURE_SUCCESS(rv, rv); ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString(), lastModified, itemType)); return NS_OK; } PRBool nsNavBookmarks::ItemExists(PRInt64 aItemId) { mozStorageStatementScoper scope(mDBGetItemProperties); nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId); NS_ENSURE_SUCCESS(rv, PR_FALSE); PRBool results; rv = mDBGetItemProperties->ExecuteStep(&results); NS_ENSURE_SUCCESS(rv, PR_FALSE); if (!results) return PR_FALSE; return PR_TRUE; }