Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | //* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include <stdio.h> |
michael@0 | 7 | |
michael@0 | 8 | #include "mozilla/DebugOnly.h" |
michael@0 | 9 | |
michael@0 | 10 | #include "nsNavHistory.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "mozIPlacesAutoComplete.h" |
michael@0 | 13 | #include "nsNavBookmarks.h" |
michael@0 | 14 | #include "nsAnnotationService.h" |
michael@0 | 15 | #include "nsFaviconService.h" |
michael@0 | 16 | #include "nsPlacesMacros.h" |
michael@0 | 17 | #include "History.h" |
michael@0 | 18 | #include "Helpers.h" |
michael@0 | 19 | |
michael@0 | 20 | #include "nsTArray.h" |
michael@0 | 21 | #include "nsCollationCID.h" |
michael@0 | 22 | #include "nsILocaleService.h" |
michael@0 | 23 | #include "nsNetUtil.h" |
michael@0 | 24 | #include "nsPrintfCString.h" |
michael@0 | 25 | #include "nsPromiseFlatString.h" |
michael@0 | 26 | #include "nsString.h" |
michael@0 | 27 | #include "nsUnicharUtils.h" |
michael@0 | 28 | #include "prsystem.h" |
michael@0 | 29 | #include "prtime.h" |
michael@0 | 30 | #include "nsEscape.h" |
michael@0 | 31 | #include "nsIEffectiveTLDService.h" |
michael@0 | 32 | #include "nsIClassInfoImpl.h" |
michael@0 | 33 | #include "nsThreadUtils.h" |
michael@0 | 34 | #include "nsAppDirectoryServiceDefs.h" |
michael@0 | 35 | #include "nsMathUtils.h" |
michael@0 | 36 | #include "mozilla/storage.h" |
michael@0 | 37 | #include "mozilla/Preferences.h" |
michael@0 | 38 | #include <algorithm> |
michael@0 | 39 | |
michael@0 | 40 | #ifdef MOZ_XUL |
michael@0 | 41 | #include "nsIAutoCompleteInput.h" |
michael@0 | 42 | #include "nsIAutoCompletePopup.h" |
michael@0 | 43 | #endif |
michael@0 | 44 | |
michael@0 | 45 | using namespace mozilla; |
michael@0 | 46 | using namespace mozilla::places; |
michael@0 | 47 | |
michael@0 | 48 | // The maximum number of things that we will store in the recent events list |
michael@0 | 49 | // before calling ExpireNonrecentEvents. This number should be big enough so it |
michael@0 | 50 | // is very difficult to get that many unconsumed events (for example, typed but |
michael@0 | 51 | // never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start |
michael@0 | 52 | // checking each one for every page visit, which will be somewhat slower. |
michael@0 | 53 | #define RECENT_EVENT_QUEUE_MAX_LENGTH 128 |
michael@0 | 54 | |
michael@0 | 55 | // preference ID strings |
michael@0 | 56 | #define PREF_HISTORY_ENABLED "places.history.enabled" |
michael@0 | 57 | |
michael@0 | 58 | #define PREF_FREC_NUM_VISITS "places.frecency.numVisits" |
michael@0 | 59 | #define PREF_FREC_NUM_VISITS_DEF 10 |
michael@0 | 60 | #define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff" |
michael@0 | 61 | #define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4 |
michael@0 | 62 | #define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff" |
michael@0 | 63 | #define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14 |
michael@0 | 64 | #define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff" |
michael@0 | 65 | #define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31 |
michael@0 | 66 | #define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff" |
michael@0 | 67 | #define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90 |
michael@0 | 68 | #define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight" |
michael@0 | 69 | #define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100 |
michael@0 | 70 | #define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight" |
michael@0 | 71 | #define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70 |
michael@0 | 72 | #define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight" |
michael@0 | 73 | #define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50 |
michael@0 | 74 | #define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight" |
michael@0 | 75 | #define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30 |
michael@0 | 76 | #define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight" |
michael@0 | 77 | #define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10 |
michael@0 | 78 | #define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus" |
michael@0 | 79 | #define PREF_FREC_EMBED_VISIT_BONUS_DEF 0 |
michael@0 | 80 | #define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus" |
michael@0 | 81 | #define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0 |
michael@0 | 82 | #define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus" |
michael@0 | 83 | #define PREF_FREC_LINK_VISIT_BONUS_DEF 100 |
michael@0 | 84 | #define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus" |
michael@0 | 85 | #define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000 |
michael@0 | 86 | #define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus" |
michael@0 | 87 | #define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75 |
michael@0 | 88 | #define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus" |
michael@0 | 89 | #define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0 |
michael@0 | 90 | #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS "places.frecency.permRedirectVisitBonus" |
michael@0 | 91 | #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0 |
michael@0 | 92 | #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS "places.frecency.tempRedirectVisitBonus" |
michael@0 | 93 | #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0 |
michael@0 | 94 | #define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus" |
michael@0 | 95 | #define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0 |
michael@0 | 96 | #define PREF_FREC_UNVISITED_BOOKMARK_BONUS "places.frecency.unvisitedBookmarkBonus" |
michael@0 | 97 | #define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140 |
michael@0 | 98 | #define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus" |
michael@0 | 99 | #define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200 |
michael@0 | 100 | |
michael@0 | 101 | // In order to avoid calling PR_now() too often we use a cached "now" value |
michael@0 | 102 | // for repeating stuff. These are milliseconds between "now" cache refreshes. |
michael@0 | 103 | #define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC) |
michael@0 | 104 | |
michael@0 | 105 | static const int64_t USECS_PER_DAY = (int64_t)PR_USEC_PER_SEC * 60 * 60 * 24; |
michael@0 | 106 | |
michael@0 | 107 | // character-set annotation |
michael@0 | 108 | #define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet") |
michael@0 | 109 | |
michael@0 | 110 | // These macros are used when splitting history by date. |
michael@0 | 111 | // These are the day containers and catch-all final container. |
michael@0 | 112 | #define HISTORY_ADDITIONAL_DATE_CONT_NUM 3 |
michael@0 | 113 | // We use a guess of the number of months considering all of them 30 days |
michael@0 | 114 | // long, but we split only the last 6 months. |
michael@0 | 115 | #define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \ |
michael@0 | 116 | (HISTORY_ADDITIONAL_DATE_CONT_NUM + \ |
michael@0 | 117 | std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit/30))) |
michael@0 | 118 | // Max number of containers, used to initialize the params hash. |
michael@0 | 119 | #define HISTORY_DATE_CONT_MAX 10 |
michael@0 | 120 | |
michael@0 | 121 | // Initial size of the embed visits cache. |
michael@0 | 122 | #define EMBED_VISITS_INITIAL_CACHE_SIZE 128 |
michael@0 | 123 | |
michael@0 | 124 | // Initial size of the recent events caches. |
michael@0 | 125 | #define RECENT_EVENTS_INITIAL_CACHE_SIZE 128 |
michael@0 | 126 | |
michael@0 | 127 | // Observed topics. |
michael@0 | 128 | #ifdef MOZ_XUL |
michael@0 | 129 | #define TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING "autocomplete-will-enter-text" |
michael@0 | 130 | #endif |
michael@0 | 131 | #define TOPIC_IDLE_DAILY "idle-daily" |
michael@0 | 132 | #define TOPIC_PREF_CHANGED "nsPref:changed" |
michael@0 | 133 | #define TOPIC_PROFILE_TEARDOWN "profile-change-teardown" |
michael@0 | 134 | #define TOPIC_PROFILE_CHANGE "profile-before-change" |
michael@0 | 135 | |
michael@0 | 136 | static const char* kObservedPrefs[] = { |
michael@0 | 137 | PREF_HISTORY_ENABLED |
michael@0 | 138 | , PREF_FREC_NUM_VISITS |
michael@0 | 139 | , PREF_FREC_FIRST_BUCKET_CUTOFF |
michael@0 | 140 | , PREF_FREC_SECOND_BUCKET_CUTOFF |
michael@0 | 141 | , PREF_FREC_THIRD_BUCKET_CUTOFF |
michael@0 | 142 | , PREF_FREC_FOURTH_BUCKET_CUTOFF |
michael@0 | 143 | , PREF_FREC_FIRST_BUCKET_WEIGHT |
michael@0 | 144 | , PREF_FREC_SECOND_BUCKET_WEIGHT |
michael@0 | 145 | , PREF_FREC_THIRD_BUCKET_WEIGHT |
michael@0 | 146 | , PREF_FREC_FOURTH_BUCKET_WEIGHT |
michael@0 | 147 | , PREF_FREC_DEFAULT_BUCKET_WEIGHT |
michael@0 | 148 | , PREF_FREC_EMBED_VISIT_BONUS |
michael@0 | 149 | , PREF_FREC_FRAMED_LINK_VISIT_BONUS |
michael@0 | 150 | , PREF_FREC_LINK_VISIT_BONUS |
michael@0 | 151 | , PREF_FREC_TYPED_VISIT_BONUS |
michael@0 | 152 | , PREF_FREC_BOOKMARK_VISIT_BONUS |
michael@0 | 153 | , PREF_FREC_DOWNLOAD_VISIT_BONUS |
michael@0 | 154 | , PREF_FREC_PERM_REDIRECT_VISIT_BONUS |
michael@0 | 155 | , PREF_FREC_TEMP_REDIRECT_VISIT_BONUS |
michael@0 | 156 | , PREF_FREC_DEFAULT_VISIT_BONUS |
michael@0 | 157 | , PREF_FREC_UNVISITED_BOOKMARK_BONUS |
michael@0 | 158 | , PREF_FREC_UNVISITED_TYPED_BONUS |
michael@0 | 159 | , nullptr |
michael@0 | 160 | }; |
michael@0 | 161 | |
michael@0 | 162 | NS_IMPL_ADDREF(nsNavHistory) |
michael@0 | 163 | NS_IMPL_RELEASE(nsNavHistory) |
michael@0 | 164 | |
michael@0 | 165 | NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON, |
michael@0 | 166 | NS_NAVHISTORYSERVICE_CID) |
michael@0 | 167 | NS_INTERFACE_MAP_BEGIN(nsNavHistory) |
michael@0 | 168 | NS_INTERFACE_MAP_ENTRY(nsINavHistoryService) |
michael@0 | 169 | NS_INTERFACE_MAP_ENTRY(nsIBrowserHistory) |
michael@0 | 170 | NS_INTERFACE_MAP_ENTRY(nsIObserver) |
michael@0 | 171 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
michael@0 | 172 | NS_INTERFACE_MAP_ENTRY(nsPIPlacesDatabase) |
michael@0 | 173 | NS_INTERFACE_MAP_ENTRY(nsPIPlacesHistoryListenersNotifier) |
michael@0 | 174 | NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant) |
michael@0 | 175 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService) |
michael@0 | 176 | NS_IMPL_QUERY_CLASSINFO(nsNavHistory) |
michael@0 | 177 | NS_INTERFACE_MAP_END |
michael@0 | 178 | |
michael@0 | 179 | // We don't care about flattening everything |
michael@0 | 180 | NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, |
michael@0 | 181 | nsINavHistoryService, |
michael@0 | 182 | nsIBrowserHistory) |
michael@0 | 183 | |
michael@0 | 184 | namespace { |
michael@0 | 185 | |
michael@0 | 186 | static int64_t GetSimpleBookmarksQueryFolder( |
michael@0 | 187 | const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 188 | nsNavHistoryQueryOptions* aOptions); |
michael@0 | 189 | static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 190 | nsTArray<nsTArray<nsString>*>* aTerms); |
michael@0 | 191 | |
michael@0 | 192 | void GetTagsSqlFragment(int64_t aTagsFolder, |
michael@0 | 193 | const nsACString& aRelation, |
michael@0 | 194 | bool aHasSearchTerms, |
michael@0 | 195 | nsACString& _sqlFragment) { |
michael@0 | 196 | if (!aHasSearchTerms) |
michael@0 | 197 | _sqlFragment.AssignLiteral("null"); |
michael@0 | 198 | else { |
michael@0 | 199 | // This subquery DOES NOT order tags for performance reasons. |
michael@0 | 200 | _sqlFragment.Assign(NS_LITERAL_CSTRING( |
michael@0 | 201 | "(SELECT GROUP_CONCAT(t_t.title, ',') " |
michael@0 | 202 | "FROM moz_bookmarks b_t " |
michael@0 | 203 | "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent " |
michael@0 | 204 | "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" " |
michael@0 | 205 | "AND t_t.parent = ") + |
michael@0 | 206 | nsPrintfCString("%lld", aTagsFolder) + NS_LITERAL_CSTRING(" " |
michael@0 | 207 | ")")); |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | _sqlFragment.AppendLiteral(" AS tags "); |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | /** |
michael@0 | 214 | * This class sets begin/end of batch updates to correspond to C++ scopes so |
michael@0 | 215 | * we can be sure end always gets called. |
michael@0 | 216 | */ |
michael@0 | 217 | class UpdateBatchScoper |
michael@0 | 218 | { |
michael@0 | 219 | public: |
michael@0 | 220 | UpdateBatchScoper(nsNavHistory& aNavHistory) : mNavHistory(aNavHistory) |
michael@0 | 221 | { |
michael@0 | 222 | mNavHistory.BeginUpdateBatch(); |
michael@0 | 223 | } |
michael@0 | 224 | ~UpdateBatchScoper() |
michael@0 | 225 | { |
michael@0 | 226 | mNavHistory.EndUpdateBatch(); |
michael@0 | 227 | } |
michael@0 | 228 | protected: |
michael@0 | 229 | nsNavHistory& mNavHistory; |
michael@0 | 230 | }; |
michael@0 | 231 | |
michael@0 | 232 | } // anonymouse namespace |
michael@0 | 233 | |
michael@0 | 234 | |
michael@0 | 235 | // Queries rows indexes to bind or get values, if adding a new one, be sure to |
michael@0 | 236 | // update nsNavBookmarks statements and its kGetChildrenIndex_* constants |
michael@0 | 237 | const int32_t nsNavHistory::kGetInfoIndex_PageID = 0; |
michael@0 | 238 | const int32_t nsNavHistory::kGetInfoIndex_URL = 1; |
michael@0 | 239 | const int32_t nsNavHistory::kGetInfoIndex_Title = 2; |
michael@0 | 240 | const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3; |
michael@0 | 241 | const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4; |
michael@0 | 242 | const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5; |
michael@0 | 243 | const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6; |
michael@0 | 244 | const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7; |
michael@0 | 245 | const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8; |
michael@0 | 246 | const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9; |
michael@0 | 247 | const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10; |
michael@0 | 248 | const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11; |
michael@0 | 249 | const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12; |
michael@0 | 250 | const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13; |
michael@0 | 251 | const int32_t nsNavHistory::kGetInfoIndex_Guid = 14; |
michael@0 | 252 | |
michael@0 | 253 | PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService) |
michael@0 | 254 | |
michael@0 | 255 | |
michael@0 | 256 | nsNavHistory::nsNavHistory() |
michael@0 | 257 | : mBatchLevel(0) |
michael@0 | 258 | , mBatchDBTransaction(nullptr) |
michael@0 | 259 | , mCachedNow(0) |
michael@0 | 260 | , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
michael@0 | 261 | , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
michael@0 | 262 | , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
michael@0 | 263 | , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_SIZE) |
michael@0 | 264 | , mHistoryEnabled(true) |
michael@0 | 265 | , mNumVisitsForFrecency(10) |
michael@0 | 266 | , mTagsFolder(-1) |
michael@0 | 267 | , mDaysOfHistory(-1) |
michael@0 | 268 | , mLastCachedStartOfDay(INT64_MAX) |
michael@0 | 269 | , mLastCachedEndOfDay(0) |
michael@0 | 270 | , mCanNotify(true) |
michael@0 | 271 | , mCacheObservers("history-observers") |
michael@0 | 272 | { |
michael@0 | 273 | NS_ASSERTION(!gHistoryService, |
michael@0 | 274 | "Attempting to create two instances of the service!"); |
michael@0 | 275 | gHistoryService = this; |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | |
michael@0 | 279 | nsNavHistory::~nsNavHistory() |
michael@0 | 280 | { |
michael@0 | 281 | // remove the static reference to the service. Check to make sure its us |
michael@0 | 282 | // in case somebody creates an extra instance of the service. |
michael@0 | 283 | NS_ASSERTION(gHistoryService == this, |
michael@0 | 284 | "Deleting a non-singleton instance of the service"); |
michael@0 | 285 | if (gHistoryService == this) |
michael@0 | 286 | gHistoryService = nullptr; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | |
michael@0 | 290 | nsresult |
michael@0 | 291 | nsNavHistory::Init() |
michael@0 | 292 | { |
michael@0 | 293 | LoadPrefs(); |
michael@0 | 294 | |
michael@0 | 295 | mDB = Database::GetDatabase(); |
michael@0 | 296 | NS_ENSURE_STATE(mDB); |
michael@0 | 297 | |
michael@0 | 298 | /***************************************************************************** |
michael@0 | 299 | *** IMPORTANT NOTICE! |
michael@0 | 300 | *** |
michael@0 | 301 | *** Nothing after these add observer calls should return anything but NS_OK. |
michael@0 | 302 | *** If a failure code is returned, this nsNavHistory object will be held onto |
michael@0 | 303 | *** by the observer service and the preference service. |
michael@0 | 304 | ****************************************************************************/ |
michael@0 | 305 | |
michael@0 | 306 | // Observe preferences changes. |
michael@0 | 307 | Preferences::AddWeakObservers(this, kObservedPrefs); |
michael@0 | 308 | |
michael@0 | 309 | nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); |
michael@0 | 310 | if (obsSvc) { |
michael@0 | 311 | (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true); |
michael@0 | 312 | (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true); |
michael@0 | 313 | #ifdef MOZ_XUL |
michael@0 | 314 | (void)obsSvc->AddObserver(this, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING, true); |
michael@0 | 315 | #endif |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | // Don't add code that can fail here! Do it up above, before we add our |
michael@0 | 319 | // observers. |
michael@0 | 320 | |
michael@0 | 321 | return NS_OK; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | NS_IMETHODIMP |
michael@0 | 325 | nsNavHistory::GetDatabaseStatus(uint16_t *aDatabaseStatus) |
michael@0 | 326 | { |
michael@0 | 327 | NS_ENSURE_ARG_POINTER(aDatabaseStatus); |
michael@0 | 328 | *aDatabaseStatus = mDB->GetDatabaseStatus(); |
michael@0 | 329 | return NS_OK; |
michael@0 | 330 | } |
michael@0 | 331 | |
michael@0 | 332 | uint32_t |
michael@0 | 333 | nsNavHistory::GetRecentFlags(nsIURI *aURI) |
michael@0 | 334 | { |
michael@0 | 335 | uint32_t result = 0; |
michael@0 | 336 | nsAutoCString spec; |
michael@0 | 337 | nsresult rv = aURI->GetSpec(spec); |
michael@0 | 338 | NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to get aURI's spec"); |
michael@0 | 339 | |
michael@0 | 340 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 341 | if (CheckIsRecentEvent(&mRecentTyped, spec)) |
michael@0 | 342 | result |= RECENT_TYPED; |
michael@0 | 343 | if (CheckIsRecentEvent(&mRecentLink, spec)) |
michael@0 | 344 | result |= RECENT_ACTIVATED; |
michael@0 | 345 | if (CheckIsRecentEvent(&mRecentBookmark, spec)) |
michael@0 | 346 | result |= RECENT_BOOKMARKED; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | return result; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | nsresult |
michael@0 | 353 | nsNavHistory::GetIdForPage(nsIURI* aURI, |
michael@0 | 354 | int64_t* _pageId, |
michael@0 | 355 | nsCString& _GUID) |
michael@0 | 356 | { |
michael@0 | 357 | *_pageId = 0; |
michael@0 | 358 | |
michael@0 | 359 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
michael@0 | 360 | "SELECT id, url, title, rev_host, visit_count, guid " |
michael@0 | 361 | "FROM moz_places " |
michael@0 | 362 | "WHERE url = :page_url " |
michael@0 | 363 | ); |
michael@0 | 364 | NS_ENSURE_STATE(stmt); |
michael@0 | 365 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 366 | |
michael@0 | 367 | nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
michael@0 | 368 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 369 | |
michael@0 | 370 | bool hasEntry = false; |
michael@0 | 371 | rv = stmt->ExecuteStep(&hasEntry); |
michael@0 | 372 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 373 | |
michael@0 | 374 | if (hasEntry) { |
michael@0 | 375 | rv = stmt->GetInt64(0, _pageId); |
michael@0 | 376 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 377 | rv = stmt->GetUTF8String(5, _GUID); |
michael@0 | 378 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | return NS_OK; |
michael@0 | 382 | } |
michael@0 | 383 | |
michael@0 | 384 | nsresult |
michael@0 | 385 | nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, |
michael@0 | 386 | int64_t* _pageId, |
michael@0 | 387 | nsCString& _GUID) |
michael@0 | 388 | { |
michael@0 | 389 | nsresult rv = GetIdForPage(aURI, _pageId, _GUID); |
michael@0 | 390 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 391 | |
michael@0 | 392 | if (*_pageId != 0) { |
michael@0 | 393 | return NS_OK; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | // Create a new hidden, untyped and unvisited entry. |
michael@0 | 397 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
michael@0 | 398 | "INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) " |
michael@0 | 399 | "VALUES (:page_url, :rev_host, :hidden, :frecency, GENERATE_GUID()) " |
michael@0 | 400 | ); |
michael@0 | 401 | NS_ENSURE_STATE(stmt); |
michael@0 | 402 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 403 | |
michael@0 | 404 | rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
michael@0 | 405 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 406 | // host (reversed with trailing period) |
michael@0 | 407 | nsAutoString revHost; |
michael@0 | 408 | rv = GetReversedHostname(aURI, revHost); |
michael@0 | 409 | // Not all URI types have hostnames, so this is optional. |
michael@0 | 410 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 411 | rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost); |
michael@0 | 412 | } else { |
michael@0 | 413 | rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host")); |
michael@0 | 414 | } |
michael@0 | 415 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 416 | rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1); |
michael@0 | 417 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 418 | nsAutoCString spec; |
michael@0 | 419 | rv = aURI->GetSpec(spec); |
michael@0 | 420 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 421 | rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), |
michael@0 | 422 | IsQueryURI(spec) ? 0 : -1); |
michael@0 | 423 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 424 | |
michael@0 | 425 | rv = stmt->Execute(); |
michael@0 | 426 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 427 | |
michael@0 | 428 | { |
michael@0 | 429 | nsCOMPtr<mozIStorageStatement> getIdStmt = mDB->GetStatement( |
michael@0 | 430 | "SELECT id, guid FROM moz_places WHERE url = :page_url " |
michael@0 | 431 | ); |
michael@0 | 432 | NS_ENSURE_STATE(getIdStmt); |
michael@0 | 433 | mozStorageStatementScoper getIdScoper(getIdStmt); |
michael@0 | 434 | |
michael@0 | 435 | rv = URIBinder::Bind(getIdStmt, NS_LITERAL_CSTRING("page_url"), aURI); |
michael@0 | 436 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 437 | |
michael@0 | 438 | bool hasResult = false; |
michael@0 | 439 | rv = getIdStmt->ExecuteStep(&hasResult); |
michael@0 | 440 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 441 | NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?"); |
michael@0 | 442 | *_pageId = getIdStmt->AsInt64(0); |
michael@0 | 443 | rv = getIdStmt->GetUTF8String(1, _GUID); |
michael@0 | 444 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | return NS_OK; |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | |
michael@0 | 451 | void |
michael@0 | 452 | nsNavHistory::LoadPrefs() |
michael@0 | 453 | { |
michael@0 | 454 | // History preferences. |
michael@0 | 455 | mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true); |
michael@0 | 456 | |
michael@0 | 457 | // Frecency preferences. |
michael@0 | 458 | #define FRECENCY_PREF(_prop, _pref) \ |
michael@0 | 459 | _prop = Preferences::GetInt(_pref, _pref##_DEF) |
michael@0 | 460 | |
michael@0 | 461 | FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS); |
michael@0 | 462 | FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF); |
michael@0 | 463 | FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF); |
michael@0 | 464 | FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF); |
michael@0 | 465 | FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF); |
michael@0 | 466 | FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS); |
michael@0 | 467 | FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS); |
michael@0 | 468 | FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS); |
michael@0 | 469 | FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS); |
michael@0 | 470 | FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS); |
michael@0 | 471 | FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS); |
michael@0 | 472 | FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS); |
michael@0 | 473 | FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS); |
michael@0 | 474 | FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS); |
michael@0 | 475 | FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS); |
michael@0 | 476 | FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS); |
michael@0 | 477 | FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT); |
michael@0 | 478 | FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT); |
michael@0 | 479 | FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT); |
michael@0 | 480 | FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT); |
michael@0 | 481 | FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT); |
michael@0 | 482 | |
michael@0 | 483 | #undef FRECENCY_PREF |
michael@0 | 484 | } |
michael@0 | 485 | |
michael@0 | 486 | |
michael@0 | 487 | void |
michael@0 | 488 | nsNavHistory::NotifyOnVisit(nsIURI* aURI, |
michael@0 | 489 | int64_t aVisitID, |
michael@0 | 490 | PRTime aTime, |
michael@0 | 491 | int64_t referringVisitID, |
michael@0 | 492 | int32_t aTransitionType, |
michael@0 | 493 | const nsACString& aGUID, |
michael@0 | 494 | bool aHidden) |
michael@0 | 495 | { |
michael@0 | 496 | MOZ_ASSERT(!aGUID.IsEmpty()); |
michael@0 | 497 | // If there's no history, this visit will surely add a day. If the visit is |
michael@0 | 498 | // added before or after the last cached day, the day count may have changed. |
michael@0 | 499 | // Otherwise adding multiple visits in the same day should not invalidate |
michael@0 | 500 | // the cache. |
michael@0 | 501 | if (mDaysOfHistory == 0) { |
michael@0 | 502 | mDaysOfHistory = 1; |
michael@0 | 503 | } else if (aTime > mLastCachedEndOfDay || aTime < mLastCachedStartOfDay) { |
michael@0 | 504 | mDaysOfHistory = -1; |
michael@0 | 505 | } |
michael@0 | 506 | |
michael@0 | 507 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 508 | nsINavHistoryObserver, |
michael@0 | 509 | OnVisit(aURI, aVisitID, aTime, 0, |
michael@0 | 510 | referringVisitID, aTransitionType, aGUID, aHidden)); |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | void |
michael@0 | 514 | nsNavHistory::NotifyTitleChange(nsIURI* aURI, |
michael@0 | 515 | const nsString& aTitle, |
michael@0 | 516 | const nsACString& aGUID) |
michael@0 | 517 | { |
michael@0 | 518 | MOZ_ASSERT(!aGUID.IsEmpty()); |
michael@0 | 519 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 520 | nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID)); |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | void |
michael@0 | 524 | nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI, |
michael@0 | 525 | int32_t aNewFrecency, |
michael@0 | 526 | const nsACString& aGUID, |
michael@0 | 527 | bool aHidden, |
michael@0 | 528 | PRTime aLastVisitDate) |
michael@0 | 529 | { |
michael@0 | 530 | MOZ_ASSERT(!aGUID.IsEmpty()); |
michael@0 | 531 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 532 | nsINavHistoryObserver, |
michael@0 | 533 | OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, |
michael@0 | 534 | aLastVisitDate)); |
michael@0 | 535 | } |
michael@0 | 536 | |
michael@0 | 537 | void |
michael@0 | 538 | nsNavHistory::NotifyManyFrecenciesChanged() |
michael@0 | 539 | { |
michael@0 | 540 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 541 | nsINavHistoryObserver, |
michael@0 | 542 | OnManyFrecenciesChanged()); |
michael@0 | 543 | } |
michael@0 | 544 | |
michael@0 | 545 | namespace { |
michael@0 | 546 | |
michael@0 | 547 | class FrecencyNotification : public nsRunnable |
michael@0 | 548 | { |
michael@0 | 549 | public: |
michael@0 | 550 | FrecencyNotification(const nsACString& aSpec, |
michael@0 | 551 | int32_t aNewFrecency, |
michael@0 | 552 | const nsACString& aGUID, |
michael@0 | 553 | bool aHidden, |
michael@0 | 554 | PRTime aLastVisitDate) |
michael@0 | 555 | : mSpec(aSpec) |
michael@0 | 556 | , mNewFrecency(aNewFrecency) |
michael@0 | 557 | , mGUID(aGUID) |
michael@0 | 558 | , mHidden(aHidden) |
michael@0 | 559 | , mLastVisitDate(aLastVisitDate) |
michael@0 | 560 | { |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | NS_IMETHOD Run() |
michael@0 | 564 | { |
michael@0 | 565 | MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); |
michael@0 | 566 | nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); |
michael@0 | 567 | if (navHistory) { |
michael@0 | 568 | nsCOMPtr<nsIURI> uri; |
michael@0 | 569 | (void)NS_NewURI(getter_AddRefs(uri), mSpec); |
michael@0 | 570 | navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden, |
michael@0 | 571 | mLastVisitDate); |
michael@0 | 572 | } |
michael@0 | 573 | return NS_OK; |
michael@0 | 574 | } |
michael@0 | 575 | |
michael@0 | 576 | private: |
michael@0 | 577 | nsCString mSpec; |
michael@0 | 578 | int32_t mNewFrecency; |
michael@0 | 579 | nsCString mGUID; |
michael@0 | 580 | bool mHidden; |
michael@0 | 581 | PRTime mLastVisitDate; |
michael@0 | 582 | }; |
michael@0 | 583 | |
michael@0 | 584 | } // anonymous namespace |
michael@0 | 585 | |
michael@0 | 586 | void |
michael@0 | 587 | nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec, |
michael@0 | 588 | int32_t aNewFrecency, |
michael@0 | 589 | const nsACString& aGUID, |
michael@0 | 590 | bool aHidden, |
michael@0 | 591 | PRTime aLastVisitDate) const |
michael@0 | 592 | { |
michael@0 | 593 | nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency, |
michael@0 | 594 | aGUID, aHidden, |
michael@0 | 595 | aLastVisitDate); |
michael@0 | 596 | (void)NS_DispatchToMainThread(notif); |
michael@0 | 597 | } |
michael@0 | 598 | |
michael@0 | 599 | int32_t |
michael@0 | 600 | nsNavHistory::GetDaysOfHistory() { |
michael@0 | 601 | MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 602 | |
michael@0 | 603 | if (mDaysOfHistory != -1) |
michael@0 | 604 | return mDaysOfHistory; |
michael@0 | 605 | |
michael@0 | 606 | // SQLite doesn't have a CEIL() function, so we must do that later. |
michael@0 | 607 | // We should also take into account timers resolution, that may be as bad as |
michael@0 | 608 | // 16ms on Windows, so in some cases the difference may be 0, if the |
michael@0 | 609 | // check is done near the visit. Thus remember to check for NULL separately. |
michael@0 | 610 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
michael@0 | 611 | "SELECT CAST(( " |
michael@0 | 612 | "strftime('%s','now','localtime','utc') - " |
michael@0 | 613 | "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) " |
michael@0 | 614 | ") AS DOUBLE) " |
michael@0 | 615 | "/86400, " |
michael@0 | 616 | "strftime('%s','now','localtime','+1 day','start of day','utc') * 1000000" |
michael@0 | 617 | ); |
michael@0 | 618 | NS_ENSURE_TRUE(stmt, 0); |
michael@0 | 619 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 620 | |
michael@0 | 621 | bool hasResult; |
michael@0 | 622 | if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { |
michael@0 | 623 | // If we get NULL, then there are no visits, otherwise there must always be |
michael@0 | 624 | // at least 1 day of history. |
michael@0 | 625 | bool hasNoVisits; |
michael@0 | 626 | (void)stmt->GetIsNull(0, &hasNoVisits); |
michael@0 | 627 | mDaysOfHistory = hasNoVisits ? |
michael@0 | 628 | 0 : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0)))); |
michael@0 | 629 | mLastCachedStartOfDay = |
michael@0 | 630 | NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0); |
michael@0 | 631 | mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1. |
michael@0 | 632 | } |
michael@0 | 633 | |
michael@0 | 634 | return mDaysOfHistory; |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | PRTime |
michael@0 | 638 | nsNavHistory::GetNow() |
michael@0 | 639 | { |
michael@0 | 640 | if (!mCachedNow) { |
michael@0 | 641 | mCachedNow = PR_Now(); |
michael@0 | 642 | if (!mExpireNowTimer) |
michael@0 | 643 | mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1"); |
michael@0 | 644 | if (mExpireNowTimer) |
michael@0 | 645 | mExpireNowTimer->InitWithFuncCallback(expireNowTimerCallback, this, |
michael@0 | 646 | RENEW_CACHED_NOW_TIMEOUT, |
michael@0 | 647 | nsITimer::TYPE_ONE_SHOT); |
michael@0 | 648 | } |
michael@0 | 649 | return mCachedNow; |
michael@0 | 650 | } |
michael@0 | 651 | |
michael@0 | 652 | |
michael@0 | 653 | void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) |
michael@0 | 654 | { |
michael@0 | 655 | nsNavHistory *history = static_cast<nsNavHistory *>(aClosure); |
michael@0 | 656 | if (history) { |
michael@0 | 657 | history->mCachedNow = 0; |
michael@0 | 658 | history->mExpireNowTimer = 0; |
michael@0 | 659 | } |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | |
michael@0 | 663 | /** |
michael@0 | 664 | * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp |
michael@0 | 665 | * Pass in a pre-normalized now and a date, and we'll find the difference since |
michael@0 | 666 | * midnight on each of the days. |
michael@0 | 667 | */ |
michael@0 | 668 | static PRTime |
michael@0 | 669 | NormalizeTimeRelativeToday(PRTime aTime) |
michael@0 | 670 | { |
michael@0 | 671 | // round to midnight this morning |
michael@0 | 672 | PRExplodedTime explodedTime; |
michael@0 | 673 | PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); |
michael@0 | 674 | |
michael@0 | 675 | // set to midnight (0:00) |
michael@0 | 676 | explodedTime.tm_min = |
michael@0 | 677 | explodedTime.tm_hour = |
michael@0 | 678 | explodedTime.tm_sec = |
michael@0 | 679 | explodedTime.tm_usec = 0; |
michael@0 | 680 | |
michael@0 | 681 | return PR_ImplodeTime(&explodedTime); |
michael@0 | 682 | } |
michael@0 | 683 | |
michael@0 | 684 | // nsNavHistory::NormalizeTime |
michael@0 | 685 | // |
michael@0 | 686 | // Converts a nsINavHistoryQuery reference+offset time into a PRTime |
michael@0 | 687 | // relative to the epoch. |
michael@0 | 688 | // |
michael@0 | 689 | // It is important that this function NOT use the current time optimization. |
michael@0 | 690 | // It is called to update queries, and we really need to know what right |
michael@0 | 691 | // now is because those incoming values will also have current times that |
michael@0 | 692 | // we will have to compare against. |
michael@0 | 693 | |
michael@0 | 694 | PRTime // static |
michael@0 | 695 | nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) |
michael@0 | 696 | { |
michael@0 | 697 | PRTime ref; |
michael@0 | 698 | switch (aRelative) |
michael@0 | 699 | { |
michael@0 | 700 | case nsINavHistoryQuery::TIME_RELATIVE_EPOCH: |
michael@0 | 701 | return aOffset; |
michael@0 | 702 | case nsINavHistoryQuery::TIME_RELATIVE_TODAY: |
michael@0 | 703 | ref = NormalizeTimeRelativeToday(PR_Now()); |
michael@0 | 704 | break; |
michael@0 | 705 | case nsINavHistoryQuery::TIME_RELATIVE_NOW: |
michael@0 | 706 | ref = PR_Now(); |
michael@0 | 707 | break; |
michael@0 | 708 | default: |
michael@0 | 709 | NS_NOTREACHED("Invalid relative time"); |
michael@0 | 710 | return 0; |
michael@0 | 711 | } |
michael@0 | 712 | return ref + aOffset; |
michael@0 | 713 | } |
michael@0 | 714 | |
michael@0 | 715 | // nsNavHistory::GetUpdateRequirements |
michael@0 | 716 | // |
michael@0 | 717 | // Returns conditions for query update. |
michael@0 | 718 | // |
michael@0 | 719 | // QUERYUPDATE_TIME: |
michael@0 | 720 | // This query is only limited by an inclusive time range on the first |
michael@0 | 721 | // query object. The caller can quickly evaluate the time itself if it |
michael@0 | 722 | // chooses. This is even simpler than "simple" below. |
michael@0 | 723 | // QUERYUPDATE_SIMPLE: |
michael@0 | 724 | // This query is evaluatable using EvaluateQueryForNode to do live |
michael@0 | 725 | // updating. |
michael@0 | 726 | // QUERYUPDATE_COMPLEX: |
michael@0 | 727 | // This query is not evaluatable using EvaluateQueryForNode. When something |
michael@0 | 728 | // happens that this query updates, you will need to re-run the query. |
michael@0 | 729 | // QUERYUPDATE_COMPLEX_WITH_BOOKMARKS: |
michael@0 | 730 | // A complex query that additionally has dependence on bookmarks. All |
michael@0 | 731 | // bookmark-dependent queries fall under this category. |
michael@0 | 732 | // |
michael@0 | 733 | // aHasSearchTerms will be set to true if the query has any dependence on |
michael@0 | 734 | // keywords. When there is no dependence on keywords, we can handle title |
michael@0 | 735 | // change operations as simple instead of complex. |
michael@0 | 736 | |
michael@0 | 737 | uint32_t |
michael@0 | 738 | nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 739 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 740 | bool* aHasSearchTerms) |
michael@0 | 741 | { |
michael@0 | 742 | NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query"); |
michael@0 | 743 | |
michael@0 | 744 | // first check if there are search terms |
michael@0 | 745 | *aHasSearchTerms = false; |
michael@0 | 746 | int32_t i; |
michael@0 | 747 | for (i = 0; i < aQueries.Count(); i ++) { |
michael@0 | 748 | aQueries[i]->GetHasSearchTerms(aHasSearchTerms); |
michael@0 | 749 | if (*aHasSearchTerms) |
michael@0 | 750 | break; |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | bool nonTimeBasedItems = false; |
michael@0 | 754 | bool domainBasedItems = false; |
michael@0 | 755 | |
michael@0 | 756 | for (i = 0; i < aQueries.Count(); i ++) { |
michael@0 | 757 | nsNavHistoryQuery* query = aQueries[i]; |
michael@0 | 758 | |
michael@0 | 759 | if (query->Folders().Length() > 0 || |
michael@0 | 760 | query->OnlyBookmarked() || |
michael@0 | 761 | query->Tags().Length() > 0) { |
michael@0 | 762 | return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; |
michael@0 | 763 | } |
michael@0 | 764 | |
michael@0 | 765 | // Note: we don't currently have any complex non-bookmarked items, but these |
michael@0 | 766 | // are expected to be added. Put detection of these items here. |
michael@0 | 767 | if (!query->SearchTerms().IsEmpty() || |
michael@0 | 768 | !query->Domain().IsVoid() || |
michael@0 | 769 | query->Uri() != nullptr) |
michael@0 | 770 | nonTimeBasedItems = true; |
michael@0 | 771 | |
michael@0 | 772 | if (! query->Domain().IsVoid()) |
michael@0 | 773 | domainBasedItems = true; |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | if (aOptions->ResultType() == |
michael@0 | 777 | nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) |
michael@0 | 778 | return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; |
michael@0 | 779 | |
michael@0 | 780 | // Whenever there is a maximum number of results, |
michael@0 | 781 | // and we are not a bookmark query we must requery. This |
michael@0 | 782 | // is because we can't generally know if any given addition/change causes |
michael@0 | 783 | // the item to be in the top N items in the database. |
michael@0 | 784 | if (aOptions->MaxResults() > 0) |
michael@0 | 785 | return QUERYUPDATE_COMPLEX; |
michael@0 | 786 | |
michael@0 | 787 | if (aQueries.Count() == 1 && domainBasedItems) |
michael@0 | 788 | return QUERYUPDATE_HOST; |
michael@0 | 789 | if (aQueries.Count() == 1 && !nonTimeBasedItems) |
michael@0 | 790 | return QUERYUPDATE_TIME; |
michael@0 | 791 | |
michael@0 | 792 | return QUERYUPDATE_SIMPLE; |
michael@0 | 793 | } |
michael@0 | 794 | |
michael@0 | 795 | |
michael@0 | 796 | // nsNavHistory::EvaluateQueryForNode |
michael@0 | 797 | // |
michael@0 | 798 | // This runs the node through the given queries to see if satisfies the |
michael@0 | 799 | // query conditions. Not every query parameters are handled by this code, |
michael@0 | 800 | // but we handle the most common ones so that performance is better. |
michael@0 | 801 | // |
michael@0 | 802 | // We assume that the time on the node is the time that we want to compare. |
michael@0 | 803 | // This is not necessarily true because URL nodes have the last access time, |
michael@0 | 804 | // which is not necessarily the same. However, since this is being called |
michael@0 | 805 | // to update the list, we assume that the last access time is the current |
michael@0 | 806 | // access time that we are being asked to compare so it works out. |
michael@0 | 807 | // |
michael@0 | 808 | // Returns true if node matches the query, false if not. |
michael@0 | 809 | |
michael@0 | 810 | bool |
michael@0 | 811 | nsNavHistory::EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 812 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 813 | nsNavHistoryResultNode* aNode) |
michael@0 | 814 | { |
michael@0 | 815 | // lazily created from the node's string when we need to match URIs |
michael@0 | 816 | nsCOMPtr<nsIURI> nodeUri; |
michael@0 | 817 | |
michael@0 | 818 | // --- hidden --- |
michael@0 | 819 | if (aNode->mHidden && !aOptions->IncludeHidden()) |
michael@0 | 820 | return false; |
michael@0 | 821 | |
michael@0 | 822 | for (int32_t i = 0; i < aQueries.Count(); i ++) { |
michael@0 | 823 | bool hasIt; |
michael@0 | 824 | nsCOMPtr<nsNavHistoryQuery> query = aQueries[i]; |
michael@0 | 825 | |
michael@0 | 826 | // --- begin time --- |
michael@0 | 827 | query->GetHasBeginTime(&hasIt); |
michael@0 | 828 | if (hasIt) { |
michael@0 | 829 | PRTime beginTime = NormalizeTime(query->BeginTimeReference(), |
michael@0 | 830 | query->BeginTime()); |
michael@0 | 831 | if (aNode->mTime < beginTime) |
michael@0 | 832 | continue; // before our time range |
michael@0 | 833 | } |
michael@0 | 834 | |
michael@0 | 835 | // --- end time --- |
michael@0 | 836 | query->GetHasEndTime(&hasIt); |
michael@0 | 837 | if (hasIt) { |
michael@0 | 838 | PRTime endTime = NormalizeTime(query->EndTimeReference(), |
michael@0 | 839 | query->EndTime()); |
michael@0 | 840 | if (aNode->mTime > endTime) |
michael@0 | 841 | continue; // after our time range |
michael@0 | 842 | } |
michael@0 | 843 | |
michael@0 | 844 | // --- search terms --- |
michael@0 | 845 | if (! query->SearchTerms().IsEmpty()) { |
michael@0 | 846 | // we can use the existing filtering code, just give it our one object in |
michael@0 | 847 | // an array. |
michael@0 | 848 | nsCOMArray<nsNavHistoryResultNode> inputSet; |
michael@0 | 849 | inputSet.AppendObject(aNode); |
michael@0 | 850 | nsCOMArray<nsNavHistoryQuery> queries; |
michael@0 | 851 | queries.AppendObject(query); |
michael@0 | 852 | nsCOMArray<nsNavHistoryResultNode> filteredSet; |
michael@0 | 853 | nsresult rv = FilterResultSet(nullptr, inputSet, &filteredSet, queries, aOptions); |
michael@0 | 854 | if (NS_FAILED(rv)) |
michael@0 | 855 | continue; |
michael@0 | 856 | if (! filteredSet.Count()) |
michael@0 | 857 | continue; // did not make it through the filter, doesn't match |
michael@0 | 858 | } |
michael@0 | 859 | |
michael@0 | 860 | // --- domain/host matching --- |
michael@0 | 861 | query->GetHasDomain(&hasIt); |
michael@0 | 862 | if (hasIt) { |
michael@0 | 863 | if (! nodeUri) { |
michael@0 | 864 | // lazy creation of nodeUri, which might be checked for multiple queries |
michael@0 | 865 | if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) |
michael@0 | 866 | continue; |
michael@0 | 867 | } |
michael@0 | 868 | nsAutoCString asciiRequest; |
michael@0 | 869 | if (NS_FAILED(AsciiHostNameFromHostString(query->Domain(), asciiRequest))) |
michael@0 | 870 | continue; |
michael@0 | 871 | |
michael@0 | 872 | if (query->DomainIsHost()) { |
michael@0 | 873 | nsAutoCString host; |
michael@0 | 874 | if (NS_FAILED(nodeUri->GetAsciiHost(host))) |
michael@0 | 875 | continue; |
michael@0 | 876 | |
michael@0 | 877 | if (! asciiRequest.Equals(host)) |
michael@0 | 878 | continue; // host names don't match |
michael@0 | 879 | } |
michael@0 | 880 | // check domain names |
michael@0 | 881 | nsAutoCString domain; |
michael@0 | 882 | DomainNameFromURI(nodeUri, domain); |
michael@0 | 883 | if (! asciiRequest.Equals(domain)) |
michael@0 | 884 | continue; // domain names don't match |
michael@0 | 885 | } |
michael@0 | 886 | |
michael@0 | 887 | // --- URI matching --- |
michael@0 | 888 | if (query->Uri()) { |
michael@0 | 889 | if (! nodeUri) { // lazy creation of nodeUri |
michael@0 | 890 | if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) |
michael@0 | 891 | continue; |
michael@0 | 892 | } |
michael@0 | 893 | if (! query->UriIsPrefix()) { |
michael@0 | 894 | // easy case: the URI is an exact match |
michael@0 | 895 | bool equals; |
michael@0 | 896 | nsresult rv = query->Uri()->Equals(nodeUri, &equals); |
michael@0 | 897 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 898 | if (! equals) |
michael@0 | 899 | continue; |
michael@0 | 900 | } else { |
michael@0 | 901 | // harder case: match prefix, note that we need to get the ASCII string |
michael@0 | 902 | // from the node's parsed URI instead of using the node's mUrl string, |
michael@0 | 903 | // because that might not be normalized |
michael@0 | 904 | nsAutoCString nodeUriString; |
michael@0 | 905 | nodeUri->GetAsciiSpec(nodeUriString); |
michael@0 | 906 | nsAutoCString queryUriString; |
michael@0 | 907 | query->Uri()->GetAsciiSpec(queryUriString); |
michael@0 | 908 | if (queryUriString.Length() > nodeUriString.Length()) |
michael@0 | 909 | continue; // not long enough to match as prefix |
michael@0 | 910 | nodeUriString.SetLength(queryUriString.Length()); |
michael@0 | 911 | if (! nodeUriString.Equals(queryUriString)) |
michael@0 | 912 | continue; // prefixes don't match |
michael@0 | 913 | } |
michael@0 | 914 | } |
michael@0 | 915 | |
michael@0 | 916 | // Transitions matching. |
michael@0 | 917 | const nsTArray<uint32_t>& transitions = query->Transitions(); |
michael@0 | 918 | if (aNode->mTransitionType > 0 && |
michael@0 | 919 | transitions.Length() && |
michael@0 | 920 | !transitions.Contains(aNode->mTransitionType)) { |
michael@0 | 921 | continue; // transition doesn't match. |
michael@0 | 922 | } |
michael@0 | 923 | |
michael@0 | 924 | // If we ever make it to the bottom of this loop, that means it passed all |
michael@0 | 925 | // tests for the given query. Since queries are ORed together, that means |
michael@0 | 926 | // it passed everything and we are done. |
michael@0 | 927 | return true; |
michael@0 | 928 | } |
michael@0 | 929 | |
michael@0 | 930 | // didn't match any query |
michael@0 | 931 | return false; |
michael@0 | 932 | } |
michael@0 | 933 | |
michael@0 | 934 | |
michael@0 | 935 | // nsNavHistory::AsciiHostNameFromHostString |
michael@0 | 936 | // |
michael@0 | 937 | // We might have interesting encodings and different case in the host name. |
michael@0 | 938 | // This will convert that host name into an ASCII host name by sending it |
michael@0 | 939 | // through the URI canonicalization. The result can be used for comparison |
michael@0 | 940 | // with other ASCII host name strings. |
michael@0 | 941 | nsresult // static |
michael@0 | 942 | nsNavHistory::AsciiHostNameFromHostString(const nsACString& aHostName, |
michael@0 | 943 | nsACString& aAscii) |
michael@0 | 944 | { |
michael@0 | 945 | // To properly generate a uri we must provide a protocol. |
michael@0 | 946 | nsAutoCString fakeURL("http://"); |
michael@0 | 947 | fakeURL.Append(aHostName); |
michael@0 | 948 | nsCOMPtr<nsIURI> uri; |
michael@0 | 949 | nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL); |
michael@0 | 950 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 951 | rv = uri->GetAsciiHost(aAscii); |
michael@0 | 952 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 953 | return NS_OK; |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | |
michael@0 | 957 | // nsNavHistory::DomainNameFromURI |
michael@0 | 958 | // |
michael@0 | 959 | // This does the www.mozilla.org -> mozilla.org and |
michael@0 | 960 | // foo.theregister.co.uk -> theregister.co.uk conversion |
michael@0 | 961 | void |
michael@0 | 962 | nsNavHistory::DomainNameFromURI(nsIURI *aURI, |
michael@0 | 963 | nsACString& aDomainName) |
michael@0 | 964 | { |
michael@0 | 965 | // lazily get the effective tld service |
michael@0 | 966 | if (!mTLDService) |
michael@0 | 967 | mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); |
michael@0 | 968 | |
michael@0 | 969 | if (mTLDService) { |
michael@0 | 970 | // get the base domain for a given hostname. |
michael@0 | 971 | // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk". |
michael@0 | 972 | nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName); |
michael@0 | 973 | if (NS_SUCCEEDED(rv)) |
michael@0 | 974 | return; |
michael@0 | 975 | } |
michael@0 | 976 | |
michael@0 | 977 | // just return the original hostname |
michael@0 | 978 | // (it's also possible the host is an IP address) |
michael@0 | 979 | aURI->GetAsciiHost(aDomainName); |
michael@0 | 980 | } |
michael@0 | 981 | |
michael@0 | 982 | |
michael@0 | 983 | NS_IMETHODIMP |
michael@0 | 984 | nsNavHistory::GetHasHistoryEntries(bool* aHasEntries) |
michael@0 | 985 | { |
michael@0 | 986 | NS_ENSURE_ARG_POINTER(aHasEntries); |
michael@0 | 987 | *aHasEntries = GetDaysOfHistory() > 0; |
michael@0 | 988 | return NS_OK; |
michael@0 | 989 | } |
michael@0 | 990 | |
michael@0 | 991 | |
michael@0 | 992 | namespace { |
michael@0 | 993 | |
michael@0 | 994 | class InvalidateAllFrecenciesCallback : public AsyncStatementCallback |
michael@0 | 995 | { |
michael@0 | 996 | public: |
michael@0 | 997 | InvalidateAllFrecenciesCallback() |
michael@0 | 998 | { |
michael@0 | 999 | } |
michael@0 | 1000 | |
michael@0 | 1001 | NS_IMETHOD HandleCompletion(uint16_t aReason) |
michael@0 | 1002 | { |
michael@0 | 1003 | if (aReason == REASON_FINISHED) { |
michael@0 | 1004 | nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
michael@0 | 1005 | NS_ENSURE_STATE(navHistory); |
michael@0 | 1006 | navHistory->NotifyManyFrecenciesChanged(); |
michael@0 | 1007 | } |
michael@0 | 1008 | return NS_OK; |
michael@0 | 1009 | } |
michael@0 | 1010 | }; |
michael@0 | 1011 | |
michael@0 | 1012 | } // anonymous namespace |
michael@0 | 1013 | |
michael@0 | 1014 | nsresult |
michael@0 | 1015 | nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString) |
michael@0 | 1016 | { |
michael@0 | 1017 | // Exclude place: queries by setting their frecency to zero. |
michael@0 | 1018 | nsCString invalidFrecenciesSQLFragment( |
michael@0 | 1019 | "UPDATE moz_places SET frecency = " |
michael@0 | 1020 | ); |
michael@0 | 1021 | if (!aPlaceIdsQueryString.IsEmpty()) |
michael@0 | 1022 | invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY("); |
michael@0 | 1023 | invalidFrecenciesSQLFragment.AppendLiteral( |
michael@0 | 1024 | "(CASE " |
michael@0 | 1025 | "WHEN url BETWEEN 'place:' AND 'place;' " |
michael@0 | 1026 | "THEN 0 " |
michael@0 | 1027 | "ELSE -1 " |
michael@0 | 1028 | "END) " |
michael@0 | 1029 | ); |
michael@0 | 1030 | if (!aPlaceIdsQueryString.IsEmpty()) { |
michael@0 | 1031 | invalidFrecenciesSQLFragment.AppendLiteral( |
michael@0 | 1032 | ", url, guid, hidden, last_visit_date) " |
michael@0 | 1033 | ); |
michael@0 | 1034 | } |
michael@0 | 1035 | invalidFrecenciesSQLFragment.AppendLiteral( |
michael@0 | 1036 | "WHERE frecency > 0 " |
michael@0 | 1037 | ); |
michael@0 | 1038 | if (!aPlaceIdsQueryString.IsEmpty()) { |
michael@0 | 1039 | invalidFrecenciesSQLFragment.AppendLiteral("AND id IN("); |
michael@0 | 1040 | invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString); |
michael@0 | 1041 | invalidFrecenciesSQLFragment.AppendLiteral(")"); |
michael@0 | 1042 | } |
michael@0 | 1043 | nsRefPtr<InvalidateAllFrecenciesCallback> cb = |
michael@0 | 1044 | aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback() |
michael@0 | 1045 | : nullptr; |
michael@0 | 1046 | |
michael@0 | 1047 | nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
michael@0 | 1048 | invalidFrecenciesSQLFragment |
michael@0 | 1049 | ); |
michael@0 | 1050 | NS_ENSURE_STATE(stmt); |
michael@0 | 1051 | |
michael@0 | 1052 | nsCOMPtr<mozIStoragePendingStatement> ps; |
michael@0 | 1053 | nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps)); |
michael@0 | 1054 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1055 | |
michael@0 | 1056 | return NS_OK; |
michael@0 | 1057 | } |
michael@0 | 1058 | |
michael@0 | 1059 | |
michael@0 | 1060 | // Call this method before visiting a URL in order to help determine the |
michael@0 | 1061 | // transition type of the visit. |
michael@0 | 1062 | // |
michael@0 | 1063 | // @see MarkPageAsTyped |
michael@0 | 1064 | |
michael@0 | 1065 | NS_IMETHODIMP |
michael@0 | 1066 | nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) |
michael@0 | 1067 | { |
michael@0 | 1068 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1069 | NS_ENSURE_ARG(aURI); |
michael@0 | 1070 | |
michael@0 | 1071 | // don't add when history is disabled |
michael@0 | 1072 | if (IsHistoryDisabled()) |
michael@0 | 1073 | return NS_OK; |
michael@0 | 1074 | |
michael@0 | 1075 | nsAutoCString uriString; |
michael@0 | 1076 | nsresult rv = aURI->GetSpec(uriString); |
michael@0 | 1077 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1078 | |
michael@0 | 1079 | // if URL is already in the bookmark queue, then we need to remove the old one |
michael@0 | 1080 | int64_t unusedEventTime; |
michael@0 | 1081 | if (mRecentBookmark.Get(uriString, &unusedEventTime)) |
michael@0 | 1082 | mRecentBookmark.Remove(uriString); |
michael@0 | 1083 | |
michael@0 | 1084 | if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
michael@0 | 1085 | ExpireNonrecentEvents(&mRecentBookmark); |
michael@0 | 1086 | |
michael@0 | 1087 | mRecentBookmark.Put(uriString, GetNow()); |
michael@0 | 1088 | return NS_OK; |
michael@0 | 1089 | } |
michael@0 | 1090 | |
michael@0 | 1091 | |
michael@0 | 1092 | // nsNavHistory::CanAddURI |
michael@0 | 1093 | // |
michael@0 | 1094 | // Filter out unwanted URIs such as "chrome:", "mailbox:", etc. |
michael@0 | 1095 | // |
michael@0 | 1096 | // The model is if we don't know differently then add which basically means |
michael@0 | 1097 | // we are suppose to try all the things we know not to allow in and then if |
michael@0 | 1098 | // we don't bail go on and allow it in. |
michael@0 | 1099 | |
michael@0 | 1100 | NS_IMETHODIMP |
michael@0 | 1101 | nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) |
michael@0 | 1102 | { |
michael@0 | 1103 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1104 | NS_ENSURE_ARG(aURI); |
michael@0 | 1105 | NS_ENSURE_ARG_POINTER(canAdd); |
michael@0 | 1106 | |
michael@0 | 1107 | // If history is disabled, don't add any entry. |
michael@0 | 1108 | if (IsHistoryDisabled()) { |
michael@0 | 1109 | *canAdd = false; |
michael@0 | 1110 | return NS_OK; |
michael@0 | 1111 | } |
michael@0 | 1112 | |
michael@0 | 1113 | nsAutoCString scheme; |
michael@0 | 1114 | nsresult rv = aURI->GetScheme(scheme); |
michael@0 | 1115 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1116 | |
michael@0 | 1117 | // first check the most common cases (HTTP, HTTPS) to allow in to avoid most |
michael@0 | 1118 | // of the work |
michael@0 | 1119 | if (scheme.EqualsLiteral("http")) { |
michael@0 | 1120 | *canAdd = true; |
michael@0 | 1121 | return NS_OK; |
michael@0 | 1122 | } |
michael@0 | 1123 | if (scheme.EqualsLiteral("https")) { |
michael@0 | 1124 | *canAdd = true; |
michael@0 | 1125 | return NS_OK; |
michael@0 | 1126 | } |
michael@0 | 1127 | |
michael@0 | 1128 | // now check for all bad things |
michael@0 | 1129 | if (scheme.EqualsLiteral("about") || |
michael@0 | 1130 | scheme.EqualsLiteral("imap") || |
michael@0 | 1131 | scheme.EqualsLiteral("news") || |
michael@0 | 1132 | scheme.EqualsLiteral("mailbox") || |
michael@0 | 1133 | scheme.EqualsLiteral("moz-anno") || |
michael@0 | 1134 | scheme.EqualsLiteral("view-source") || |
michael@0 | 1135 | scheme.EqualsLiteral("chrome") || |
michael@0 | 1136 | scheme.EqualsLiteral("resource") || |
michael@0 | 1137 | scheme.EqualsLiteral("data") || |
michael@0 | 1138 | scheme.EqualsLiteral("wyciwyg") || |
michael@0 | 1139 | scheme.EqualsLiteral("javascript") || |
michael@0 | 1140 | scheme.EqualsLiteral("blob")) { |
michael@0 | 1141 | *canAdd = false; |
michael@0 | 1142 | return NS_OK; |
michael@0 | 1143 | } |
michael@0 | 1144 | *canAdd = true; |
michael@0 | 1145 | return NS_OK; |
michael@0 | 1146 | } |
michael@0 | 1147 | |
michael@0 | 1148 | // nsNavHistory::GetNewQuery |
michael@0 | 1149 | |
michael@0 | 1150 | NS_IMETHODIMP |
michael@0 | 1151 | nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval) |
michael@0 | 1152 | { |
michael@0 | 1153 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1154 | NS_ENSURE_ARG_POINTER(_retval); |
michael@0 | 1155 | |
michael@0 | 1156 | nsRefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery(); |
michael@0 | 1157 | query.forget(_retval); |
michael@0 | 1158 | return NS_OK; |
michael@0 | 1159 | } |
michael@0 | 1160 | |
michael@0 | 1161 | // nsNavHistory::GetNewQueryOptions |
michael@0 | 1162 | |
michael@0 | 1163 | NS_IMETHODIMP |
michael@0 | 1164 | nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions **_retval) |
michael@0 | 1165 | { |
michael@0 | 1166 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1167 | NS_ENSURE_ARG_POINTER(_retval); |
michael@0 | 1168 | |
michael@0 | 1169 | nsRefPtr<nsNavHistoryQueryOptions> queryOptions = new nsNavHistoryQueryOptions(); |
michael@0 | 1170 | queryOptions.forget(_retval); |
michael@0 | 1171 | return NS_OK; |
michael@0 | 1172 | } |
michael@0 | 1173 | |
michael@0 | 1174 | // nsNavHistory::ExecuteQuery |
michael@0 | 1175 | // |
michael@0 | 1176 | |
michael@0 | 1177 | NS_IMETHODIMP |
michael@0 | 1178 | nsNavHistory::ExecuteQuery(nsINavHistoryQuery *aQuery, nsINavHistoryQueryOptions *aOptions, |
michael@0 | 1179 | nsINavHistoryResult** _retval) |
michael@0 | 1180 | { |
michael@0 | 1181 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1182 | NS_ENSURE_ARG(aQuery); |
michael@0 | 1183 | NS_ENSURE_ARG(aOptions); |
michael@0 | 1184 | NS_ENSURE_ARG_POINTER(_retval); |
michael@0 | 1185 | |
michael@0 | 1186 | return ExecuteQueries(&aQuery, 1, aOptions, _retval); |
michael@0 | 1187 | } |
michael@0 | 1188 | |
michael@0 | 1189 | |
michael@0 | 1190 | // nsNavHistory::ExecuteQueries |
michael@0 | 1191 | // |
michael@0 | 1192 | // This function is actually very simple, we just create the proper root node (either |
michael@0 | 1193 | // a bookmark folder or a complex query node) and assign it to the result. The node |
michael@0 | 1194 | // will then populate itself accordingly. |
michael@0 | 1195 | // |
michael@0 | 1196 | // Quick overview of query operation: When you call this function, we will construct |
michael@0 | 1197 | // the correct container node and set the options you give it. This node will then |
michael@0 | 1198 | // fill itself. Folder nodes will call nsNavBookmarks::QueryFolderChildren, and |
michael@0 | 1199 | // all other queries will call GetQueryResults. If these results contain other |
michael@0 | 1200 | // queries, those will be populated when the container is opened. |
michael@0 | 1201 | |
michael@0 | 1202 | NS_IMETHODIMP |
michael@0 | 1203 | nsNavHistory::ExecuteQueries(nsINavHistoryQuery** aQueries, uint32_t aQueryCount, |
michael@0 | 1204 | nsINavHistoryQueryOptions *aOptions, |
michael@0 | 1205 | nsINavHistoryResult** _retval) |
michael@0 | 1206 | { |
michael@0 | 1207 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 1208 | NS_ENSURE_ARG(aQueries); |
michael@0 | 1209 | NS_ENSURE_ARG(aOptions); |
michael@0 | 1210 | NS_ENSURE_ARG(aQueryCount); |
michael@0 | 1211 | NS_ENSURE_ARG_POINTER(_retval); |
michael@0 | 1212 | |
michael@0 | 1213 | nsresult rv; |
michael@0 | 1214 | // concrete options |
michael@0 | 1215 | nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); |
michael@0 | 1216 | NS_ENSURE_TRUE(options, NS_ERROR_INVALID_ARG); |
michael@0 | 1217 | |
michael@0 | 1218 | // concrete queries array |
michael@0 | 1219 | nsCOMArray<nsNavHistoryQuery> queries; |
michael@0 | 1220 | for (uint32_t i = 0; i < aQueryCount; i ++) { |
michael@0 | 1221 | nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i], &rv); |
michael@0 | 1222 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1223 | queries.AppendObject(query); |
michael@0 | 1224 | } |
michael@0 | 1225 | |
michael@0 | 1226 | // Create the root node. |
michael@0 | 1227 | nsRefPtr<nsNavHistoryContainerResultNode> rootNode; |
michael@0 | 1228 | int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); |
michael@0 | 1229 | if (folderId) { |
michael@0 | 1230 | // In the simple case where we're just querying children of a single |
michael@0 | 1231 | // bookmark folder, we can more efficiently generate results. |
michael@0 | 1232 | nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 1233 | NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 1234 | nsRefPtr<nsNavHistoryResultNode> tempRootNode; |
michael@0 | 1235 | rv = bookmarks->ResultNodeForContainer(folderId, options, |
michael@0 | 1236 | getter_AddRefs(tempRootNode)); |
michael@0 | 1237 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 1238 | rootNode = tempRootNode->GetAsContainer(); |
michael@0 | 1239 | } |
michael@0 | 1240 | else { |
michael@0 | 1241 | NS_WARNING("Generating a generic empty node for a broken query!"); |
michael@0 | 1242 | // This is a perf hack to generate an empty query that skips filtering. |
michael@0 | 1243 | options->SetExcludeItems(true); |
michael@0 | 1244 | } |
michael@0 | 1245 | } |
michael@0 | 1246 | |
michael@0 | 1247 | if (!rootNode) { |
michael@0 | 1248 | // Either this is not a folder shortcut, or is a broken one. In both cases |
michael@0 | 1249 | // just generate a query node. |
michael@0 | 1250 | rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), EmptyCString(), |
michael@0 | 1251 | queries, options); |
michael@0 | 1252 | } |
michael@0 | 1253 | |
michael@0 | 1254 | // Create the result that will hold nodes. Inject batching status into it. |
michael@0 | 1255 | nsRefPtr<nsNavHistoryResult> result; |
michael@0 | 1256 | rv = nsNavHistoryResult::NewHistoryResult(aQueries, aQueryCount, options, |
michael@0 | 1257 | rootNode, isBatching(), |
michael@0 | 1258 | getter_AddRefs(result)); |
michael@0 | 1259 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1260 | |
michael@0 | 1261 | result.forget(_retval); |
michael@0 | 1262 | return NS_OK; |
michael@0 | 1263 | } |
michael@0 | 1264 | |
michael@0 | 1265 | // determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions |
michael@0 | 1266 | // if this is the place query from the history menu. |
michael@0 | 1267 | // from browser-menubar.inc, our history menu query is: |
michael@0 | 1268 | // place:sort=4&maxResults=10 |
michael@0 | 1269 | // note, any maxResult > 0 will still be considered a history menu query |
michael@0 | 1270 | // or if this is the place query from the "Most Visited" item in the |
michael@0 | 1271 | // "Smart Bookmarks" folder: place:sort=8&maxResults=10 |
michael@0 | 1272 | // note, any maxResult > 0 will still be considered a Most Visited menu query |
michael@0 | 1273 | static |
michael@0 | 1274 | bool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 1275 | nsNavHistoryQueryOptions *aOptions, |
michael@0 | 1276 | uint16_t aSortMode) |
michael@0 | 1277 | { |
michael@0 | 1278 | if (aQueries.Count() != 1) |
michael@0 | 1279 | return false; |
michael@0 | 1280 | |
michael@0 | 1281 | nsNavHistoryQuery *aQuery = aQueries[0]; |
michael@0 | 1282 | |
michael@0 | 1283 | if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) |
michael@0 | 1284 | return false; |
michael@0 | 1285 | |
michael@0 | 1286 | if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI) |
michael@0 | 1287 | return false; |
michael@0 | 1288 | |
michael@0 | 1289 | if (aOptions->SortingMode() != aSortMode) |
michael@0 | 1290 | return false; |
michael@0 | 1291 | |
michael@0 | 1292 | if (aOptions->MaxResults() <= 0) |
michael@0 | 1293 | return false; |
michael@0 | 1294 | |
michael@0 | 1295 | if (aOptions->ExcludeItems()) |
michael@0 | 1296 | return false; |
michael@0 | 1297 | |
michael@0 | 1298 | if (aOptions->IncludeHidden()) |
michael@0 | 1299 | return false; |
michael@0 | 1300 | |
michael@0 | 1301 | if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) |
michael@0 | 1302 | return false; |
michael@0 | 1303 | |
michael@0 | 1304 | if (aQuery->BeginTime() || aQuery->BeginTimeReference()) |
michael@0 | 1305 | return false; |
michael@0 | 1306 | |
michael@0 | 1307 | if (aQuery->EndTime() || aQuery->EndTimeReference()) |
michael@0 | 1308 | return false; |
michael@0 | 1309 | |
michael@0 | 1310 | if (!aQuery->SearchTerms().IsEmpty()) |
michael@0 | 1311 | return false; |
michael@0 | 1312 | |
michael@0 | 1313 | if (aQuery->OnlyBookmarked()) |
michael@0 | 1314 | return false; |
michael@0 | 1315 | |
michael@0 | 1316 | if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) |
michael@0 | 1317 | return false; |
michael@0 | 1318 | |
michael@0 | 1319 | if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty()) |
michael@0 | 1320 | return false; |
michael@0 | 1321 | |
michael@0 | 1322 | if (aQuery->UriIsPrefix() || aQuery->Uri()) |
michael@0 | 1323 | return false; |
michael@0 | 1324 | |
michael@0 | 1325 | if (aQuery->Folders().Length() > 0) |
michael@0 | 1326 | return false; |
michael@0 | 1327 | |
michael@0 | 1328 | if (aQuery->Tags().Length() > 0) |
michael@0 | 1329 | return false; |
michael@0 | 1330 | |
michael@0 | 1331 | if (aQuery->Transitions().Length() > 0) |
michael@0 | 1332 | return false; |
michael@0 | 1333 | |
michael@0 | 1334 | return true; |
michael@0 | 1335 | } |
michael@0 | 1336 | |
michael@0 | 1337 | static |
michael@0 | 1338 | bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 1339 | nsNavHistoryQueryOptions *aOptions) |
michael@0 | 1340 | { |
michael@0 | 1341 | uint16_t resultType = aOptions->ResultType(); |
michael@0 | 1342 | return resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS; |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | // ** Helper class for ConstructQueryString **/ |
michael@0 | 1346 | |
michael@0 | 1347 | class PlacesSQLQueryBuilder |
michael@0 | 1348 | { |
michael@0 | 1349 | public: |
michael@0 | 1350 | PlacesSQLQueryBuilder(const nsCString& aConditions, |
michael@0 | 1351 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 1352 | bool aUseLimit, |
michael@0 | 1353 | nsNavHistory::StringHash& aAddParams, |
michael@0 | 1354 | bool aHasSearchTerms); |
michael@0 | 1355 | |
michael@0 | 1356 | nsresult GetQueryString(nsCString& aQueryString); |
michael@0 | 1357 | |
michael@0 | 1358 | private: |
michael@0 | 1359 | nsresult Select(); |
michael@0 | 1360 | |
michael@0 | 1361 | nsresult SelectAsURI(); |
michael@0 | 1362 | nsresult SelectAsVisit(); |
michael@0 | 1363 | nsresult SelectAsDay(); |
michael@0 | 1364 | nsresult SelectAsSite(); |
michael@0 | 1365 | nsresult SelectAsTag(); |
michael@0 | 1366 | |
michael@0 | 1367 | nsresult Where(); |
michael@0 | 1368 | nsresult GroupBy(); |
michael@0 | 1369 | nsresult OrderBy(); |
michael@0 | 1370 | nsresult Limit(); |
michael@0 | 1371 | |
michael@0 | 1372 | void OrderByColumnIndexAsc(int32_t aIndex); |
michael@0 | 1373 | void OrderByColumnIndexDesc(int32_t aIndex); |
michael@0 | 1374 | // Use these if you want a case insensitive sorting. |
michael@0 | 1375 | void OrderByTextColumnIndexAsc(int32_t aIndex); |
michael@0 | 1376 | void OrderByTextColumnIndexDesc(int32_t aIndex); |
michael@0 | 1377 | |
michael@0 | 1378 | const nsCString& mConditions; |
michael@0 | 1379 | bool mUseLimit; |
michael@0 | 1380 | bool mHasSearchTerms; |
michael@0 | 1381 | |
michael@0 | 1382 | uint16_t mResultType; |
michael@0 | 1383 | uint16_t mQueryType; |
michael@0 | 1384 | bool mIncludeHidden; |
michael@0 | 1385 | uint16_t mSortingMode; |
michael@0 | 1386 | uint32_t mMaxResults; |
michael@0 | 1387 | |
michael@0 | 1388 | nsCString mQueryString; |
michael@0 | 1389 | nsCString mGroupBy; |
michael@0 | 1390 | bool mHasDateColumns; |
michael@0 | 1391 | bool mSkipOrderBy; |
michael@0 | 1392 | nsNavHistory::StringHash& mAddParams; |
michael@0 | 1393 | }; |
michael@0 | 1394 | |
michael@0 | 1395 | PlacesSQLQueryBuilder::PlacesSQLQueryBuilder( |
michael@0 | 1396 | const nsCString& aConditions, |
michael@0 | 1397 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 1398 | bool aUseLimit, |
michael@0 | 1399 | nsNavHistory::StringHash& aAddParams, |
michael@0 | 1400 | bool aHasSearchTerms) |
michael@0 | 1401 | : mConditions(aConditions) |
michael@0 | 1402 | , mUseLimit(aUseLimit) |
michael@0 | 1403 | , mHasSearchTerms(aHasSearchTerms) |
michael@0 | 1404 | , mResultType(aOptions->ResultType()) |
michael@0 | 1405 | , mQueryType(aOptions->QueryType()) |
michael@0 | 1406 | , mIncludeHidden(aOptions->IncludeHidden()) |
michael@0 | 1407 | , mSortingMode(aOptions->SortingMode()) |
michael@0 | 1408 | , mMaxResults(aOptions->MaxResults()) |
michael@0 | 1409 | , mSkipOrderBy(false) |
michael@0 | 1410 | , mAddParams(aAddParams) |
michael@0 | 1411 | { |
michael@0 | 1412 | mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS); |
michael@0 | 1413 | } |
michael@0 | 1414 | |
michael@0 | 1415 | nsresult |
michael@0 | 1416 | PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) |
michael@0 | 1417 | { |
michael@0 | 1418 | nsresult rv = Select(); |
michael@0 | 1419 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1420 | rv = Where(); |
michael@0 | 1421 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1422 | rv = GroupBy(); |
michael@0 | 1423 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1424 | rv = OrderBy(); |
michael@0 | 1425 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1426 | rv = Limit(); |
michael@0 | 1427 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1428 | |
michael@0 | 1429 | aQueryString = mQueryString; |
michael@0 | 1430 | return NS_OK; |
michael@0 | 1431 | } |
michael@0 | 1432 | |
michael@0 | 1433 | nsresult |
michael@0 | 1434 | PlacesSQLQueryBuilder::Select() |
michael@0 | 1435 | { |
michael@0 | 1436 | nsresult rv; |
michael@0 | 1437 | |
michael@0 | 1438 | switch (mResultType) |
michael@0 | 1439 | { |
michael@0 | 1440 | case nsINavHistoryQueryOptions::RESULTS_AS_URI: |
michael@0 | 1441 | case nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS: |
michael@0 | 1442 | rv = SelectAsURI(); |
michael@0 | 1443 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1444 | break; |
michael@0 | 1445 | |
michael@0 | 1446 | case nsINavHistoryQueryOptions::RESULTS_AS_VISIT: |
michael@0 | 1447 | case nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: |
michael@0 | 1448 | rv = SelectAsVisit(); |
michael@0 | 1449 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1450 | break; |
michael@0 | 1451 | |
michael@0 | 1452 | case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY: |
michael@0 | 1453 | case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY: |
michael@0 | 1454 | rv = SelectAsDay(); |
michael@0 | 1455 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1456 | break; |
michael@0 | 1457 | |
michael@0 | 1458 | case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY: |
michael@0 | 1459 | rv = SelectAsSite(); |
michael@0 | 1460 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1461 | break; |
michael@0 | 1462 | |
michael@0 | 1463 | case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY: |
michael@0 | 1464 | rv = SelectAsTag(); |
michael@0 | 1465 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1466 | break; |
michael@0 | 1467 | |
michael@0 | 1468 | default: |
michael@0 | 1469 | NS_NOTREACHED("Invalid result type"); |
michael@0 | 1470 | } |
michael@0 | 1471 | return NS_OK; |
michael@0 | 1472 | } |
michael@0 | 1473 | |
michael@0 | 1474 | nsresult |
michael@0 | 1475 | PlacesSQLQueryBuilder::SelectAsURI() |
michael@0 | 1476 | { |
michael@0 | 1477 | nsNavHistory *history = nsNavHistory::GetHistoryService(); |
michael@0 | 1478 | NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 1479 | nsAutoCString tagsSqlFragment; |
michael@0 | 1480 | |
michael@0 | 1481 | switch (mQueryType) { |
michael@0 | 1482 | case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY: |
michael@0 | 1483 | GetTagsSqlFragment(history->GetTagsFolder(), |
michael@0 | 1484 | NS_LITERAL_CSTRING("h.id"), |
michael@0 | 1485 | mHasSearchTerms, |
michael@0 | 1486 | tagsSqlFragment); |
michael@0 | 1487 | |
michael@0 | 1488 | mQueryString = NS_LITERAL_CSTRING( |
michael@0 | 1489 | "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " |
michael@0 | 1490 | "h.last_visit_date, f.url, null, null, null, null, ") + |
michael@0 | 1491 | tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 1492 | "FROM moz_places h " |
michael@0 | 1493 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 1494 | // WHERE 1 is a no-op since additonal conditions will start with AND. |
michael@0 | 1495 | "WHERE 1 " |
michael@0 | 1496 | "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " |
michael@0 | 1497 | "{ADDITIONAL_CONDITIONS} "); |
michael@0 | 1498 | break; |
michael@0 | 1499 | |
michael@0 | 1500 | case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS: |
michael@0 | 1501 | if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { |
michael@0 | 1502 | // Order-by clause is hardcoded because we need to discard duplicates |
michael@0 | 1503 | // in FilterResultSet. We will retain only the last modified item, |
michael@0 | 1504 | // so we are ordering by place id and last modified to do a faster |
michael@0 | 1505 | // filtering. |
michael@0 | 1506 | mSkipOrderBy = true; |
michael@0 | 1507 | |
michael@0 | 1508 | GetTagsSqlFragment(history->GetTagsFolder(), |
michael@0 | 1509 | NS_LITERAL_CSTRING("b2.fk"), |
michael@0 | 1510 | mHasSearchTerms, |
michael@0 | 1511 | tagsSqlFragment); |
michael@0 | 1512 | |
michael@0 | 1513 | mQueryString = NS_LITERAL_CSTRING( |
michael@0 | 1514 | "SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, " |
michael@0 | 1515 | "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, " |
michael@0 | 1516 | "b2.dateAdded, b2.lastModified, b2.parent, ") + |
michael@0 | 1517 | tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 1518 | "FROM moz_bookmarks b2 " |
michael@0 | 1519 | "JOIN (SELECT b.fk " |
michael@0 | 1520 | "FROM moz_bookmarks b " |
michael@0 | 1521 | // ADDITIONAL_CONDITIONS will filter on parent. |
michael@0 | 1522 | "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} " |
michael@0 | 1523 | ") AS seed ON b2.fk = seed.fk " |
michael@0 | 1524 | "JOIN moz_places h ON h.id = b2.fk " |
michael@0 | 1525 | "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 1526 | "WHERE NOT EXISTS ( " |
michael@0 | 1527 | "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") + |
michael@0 | 1528 | nsPrintfCString("%lld", history->GetTagsFolder()) + |
michael@0 | 1529 | NS_LITERAL_CSTRING(") " |
michael@0 | 1530 | "ORDER BY b2.fk DESC, b2.lastModified DESC"); |
michael@0 | 1531 | } |
michael@0 | 1532 | else { |
michael@0 | 1533 | GetTagsSqlFragment(history->GetTagsFolder(), |
michael@0 | 1534 | NS_LITERAL_CSTRING("b.fk"), |
michael@0 | 1535 | mHasSearchTerms, |
michael@0 | 1536 | tagsSqlFragment); |
michael@0 | 1537 | mQueryString = NS_LITERAL_CSTRING( |
michael@0 | 1538 | "SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, " |
michael@0 | 1539 | "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " |
michael@0 | 1540 | "b.dateAdded, b.lastModified, b.parent, ") + |
michael@0 | 1541 | tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 1542 | "FROM moz_bookmarks b " |
michael@0 | 1543 | "JOIN moz_places h ON b.fk = h.id " |
michael@0 | 1544 | "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 1545 | "WHERE NOT EXISTS " |
michael@0 | 1546 | "(SELECT id FROM moz_bookmarks " |
michael@0 | 1547 | "WHERE id = b.parent AND parent = ") + |
michael@0 | 1548 | nsPrintfCString("%lld", history->GetTagsFolder()) + |
michael@0 | 1549 | NS_LITERAL_CSTRING(") " |
michael@0 | 1550 | "{ADDITIONAL_CONDITIONS}"); |
michael@0 | 1551 | } |
michael@0 | 1552 | break; |
michael@0 | 1553 | |
michael@0 | 1554 | default: |
michael@0 | 1555 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1556 | } |
michael@0 | 1557 | return NS_OK; |
michael@0 | 1558 | } |
michael@0 | 1559 | |
michael@0 | 1560 | nsresult |
michael@0 | 1561 | PlacesSQLQueryBuilder::SelectAsVisit() |
michael@0 | 1562 | { |
michael@0 | 1563 | nsNavHistory *history = nsNavHistory::GetHistoryService(); |
michael@0 | 1564 | NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 1565 | nsAutoCString tagsSqlFragment; |
michael@0 | 1566 | GetTagsSqlFragment(history->GetTagsFolder(), |
michael@0 | 1567 | NS_LITERAL_CSTRING("h.id"), |
michael@0 | 1568 | mHasSearchTerms, |
michael@0 | 1569 | tagsSqlFragment); |
michael@0 | 1570 | mQueryString = NS_LITERAL_CSTRING( |
michael@0 | 1571 | "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " |
michael@0 | 1572 | "v.visit_date, f.url, null, null, null, null, ") + |
michael@0 | 1573 | tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 1574 | "FROM moz_places h " |
michael@0 | 1575 | "JOIN moz_historyvisits v ON h.id = v.place_id " |
michael@0 | 1576 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 1577 | // WHERE 1 is a no-op since additonal conditions will start with AND. |
michael@0 | 1578 | "WHERE 1 " |
michael@0 | 1579 | "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " |
michael@0 | 1580 | "{ADDITIONAL_CONDITIONS} "); |
michael@0 | 1581 | |
michael@0 | 1582 | return NS_OK; |
michael@0 | 1583 | } |
michael@0 | 1584 | |
michael@0 | 1585 | nsresult |
michael@0 | 1586 | PlacesSQLQueryBuilder::SelectAsDay() |
michael@0 | 1587 | { |
michael@0 | 1588 | mSkipOrderBy = true; |
michael@0 | 1589 | |
michael@0 | 1590 | // Sort child queries based on sorting mode if it's provided, otherwise |
michael@0 | 1591 | // fallback to default sort by title ascending. |
michael@0 | 1592 | uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING; |
michael@0 | 1593 | if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE && |
michael@0 | 1594 | mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY) |
michael@0 | 1595 | sortingMode = mSortingMode; |
michael@0 | 1596 | |
michael@0 | 1597 | uint16_t resultType = |
michael@0 | 1598 | mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ? |
michael@0 | 1599 | (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI : |
michael@0 | 1600 | (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY; |
michael@0 | 1601 | |
michael@0 | 1602 | // beginTime will become the node's time property, we don't use endTime |
michael@0 | 1603 | // because it could overlap, and we use time to sort containers and find |
michael@0 | 1604 | // insert position in a result. |
michael@0 | 1605 | mQueryString = nsPrintfCString( |
michael@0 | 1606 | "SELECT null, " |
michael@0 | 1607 | "'place:type=%ld&sort=%ld&beginTime='||beginTime||'&endTime='||endTime, " |
michael@0 | 1608 | "dayTitle, null, null, beginTime, null, null, null, null, null, null " |
michael@0 | 1609 | "FROM (", // TOUTER BEGIN |
michael@0 | 1610 | resultType, |
michael@0 | 1611 | sortingMode); |
michael@0 | 1612 | |
michael@0 | 1613 | nsNavHistory *history = nsNavHistory::GetHistoryService(); |
michael@0 | 1614 | NS_ENSURE_STATE(history); |
michael@0 | 1615 | |
michael@0 | 1616 | int32_t daysOfHistory = history->GetDaysOfHistory(); |
michael@0 | 1617 | for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) { |
michael@0 | 1618 | nsAutoCString dateName; |
michael@0 | 1619 | // Timeframes are calculated as BeginTime <= container < EndTime. |
michael@0 | 1620 | // Notice times can't be relative to now, since to recognize a query we |
michael@0 | 1621 | // must ensure it won't change based on the time it is built. |
michael@0 | 1622 | // So, to select till now, we really select till start of tomorrow, that is |
michael@0 | 1623 | // a fixed timestamp. |
michael@0 | 1624 | // These are used as limits for the inside containers. |
michael@0 | 1625 | nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime; |
michael@0 | 1626 | // These are used to query if the container should be visible. |
michael@0 | 1627 | nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime; |
michael@0 | 1628 | switch(i) { |
michael@0 | 1629 | case 0: |
michael@0 | 1630 | // Today |
michael@0 | 1631 | history->GetStringFromName( |
michael@0 | 1632 | MOZ_UTF16("finduri-AgeInDays-is-0"), dateName); |
michael@0 | 1633 | // From start of today |
michael@0 | 1634 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1635 | "(strftime('%s','now','localtime','start of day','utc')*1000000)"); |
michael@0 | 1636 | // To now (tomorrow) |
michael@0 | 1637 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1638 | "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
michael@0 | 1639 | // Search for the same timeframe. |
michael@0 | 1640 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1641 | sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
michael@0 | 1642 | break; |
michael@0 | 1643 | case 1: |
michael@0 | 1644 | // Yesterday |
michael@0 | 1645 | history->GetStringFromName( |
michael@0 | 1646 | MOZ_UTF16("finduri-AgeInDays-is-1"), dateName); |
michael@0 | 1647 | // From start of yesterday |
michael@0 | 1648 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1649 | "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)"); |
michael@0 | 1650 | // To start of today |
michael@0 | 1651 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1652 | "(strftime('%s','now','localtime','start of day','utc')*1000000)"); |
michael@0 | 1653 | // Search for the same timeframe. |
michael@0 | 1654 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1655 | sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
michael@0 | 1656 | break; |
michael@0 | 1657 | case 2: |
michael@0 | 1658 | // Last 7 days |
michael@0 | 1659 | history->GetAgeInDaysString(7, |
michael@0 | 1660 | MOZ_UTF16("finduri-AgeInDays-last-is"), dateName); |
michael@0 | 1661 | // From start of 7 days ago |
michael@0 | 1662 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1663 | "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); |
michael@0 | 1664 | // To now (tomorrow) |
michael@0 | 1665 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1666 | "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
michael@0 | 1667 | // This is an overlapped container, but we show it only if there are |
michael@0 | 1668 | // visits older than yesterday. |
michael@0 | 1669 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1670 | sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1671 | "(strftime('%s','now','localtime','start of day','-2 days','utc')*1000000)"); |
michael@0 | 1672 | break; |
michael@0 | 1673 | case 3: |
michael@0 | 1674 | // This month |
michael@0 | 1675 | history->GetStringFromName( |
michael@0 | 1676 | MOZ_UTF16("finduri-AgeInMonths-is-0"), dateName); |
michael@0 | 1677 | // From start of this month |
michael@0 | 1678 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1679 | "(strftime('%s','now','localtime','start of month','utc')*1000000)"); |
michael@0 | 1680 | // To now (tomorrow) |
michael@0 | 1681 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1682 | "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
michael@0 | 1683 | // This is an overlapped container, but we show it only if there are |
michael@0 | 1684 | // visits older than 7 days ago. |
michael@0 | 1685 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1686 | sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1687 | "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); |
michael@0 | 1688 | break; |
michael@0 | 1689 | default: |
michael@0 | 1690 | if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) { |
michael@0 | 1691 | // Older than 6 months |
michael@0 | 1692 | history->GetAgeInDaysString(6, |
michael@0 | 1693 | MOZ_UTF16("finduri-AgeInMonths-isgreater"), dateName); |
michael@0 | 1694 | // From start of epoch |
michael@0 | 1695 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1696 | "(datetime(0, 'unixepoch')*1000000)"); |
michael@0 | 1697 | // To start of 6 months ago ( 5 months + this month). |
michael@0 | 1698 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1699 | "(strftime('%s','now','localtime','start of month','-5 months','utc')*1000000)"); |
michael@0 | 1700 | // Search for the same timeframe. |
michael@0 | 1701 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1702 | sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
michael@0 | 1703 | break; |
michael@0 | 1704 | } |
michael@0 | 1705 | int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM; |
michael@0 | 1706 | // Previous months' titles are month's name if inside this year, |
michael@0 | 1707 | // month's name and year for previous years. |
michael@0 | 1708 | PRExplodedTime tm; |
michael@0 | 1709 | PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm); |
michael@0 | 1710 | uint16_t currentYear = tm.tm_year; |
michael@0 | 1711 | // Set day before month, setting month without day could cause issues. |
michael@0 | 1712 | // For example setting month to February when today is 30, since |
michael@0 | 1713 | // February has not 30 days, will return March instead. |
michael@0 | 1714 | // Also, we use day 2 instead of day 1, so that the GMT month is always |
michael@0 | 1715 | // the same as the local month. (Bug 603002) |
michael@0 | 1716 | tm.tm_mday = 2; |
michael@0 | 1717 | tm.tm_month -= MonthIndex; |
michael@0 | 1718 | // Notice we use GMTParameters because we just want to get the first |
michael@0 | 1719 | // day of each month. Using LocalTimeParameters would instead force us |
michael@0 | 1720 | // to apply a DST correction that we don't really need here. |
michael@0 | 1721 | PR_NormalizeTime(&tm, PR_GMTParameters); |
michael@0 | 1722 | // If the container is for a past year, add the year to its title, |
michael@0 | 1723 | // otherwise just show the month name. |
michael@0 | 1724 | // Note that tm_month starts from 0, while we need a 1-based index. |
michael@0 | 1725 | if (tm.tm_year < currentYear) { |
michael@0 | 1726 | history->GetMonthYear(tm.tm_month + 1, tm.tm_year, dateName); |
michael@0 | 1727 | } |
michael@0 | 1728 | else { |
michael@0 | 1729 | history->GetMonthName(tm.tm_month + 1, dateName); |
michael@0 | 1730 | } |
michael@0 | 1731 | |
michael@0 | 1732 | // From start of MonthIndex + 1 months ago |
michael@0 | 1733 | sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
michael@0 | 1734 | "(strftime('%s','now','localtime','start of month','-"); |
michael@0 | 1735 | sqlFragmentContainerBeginTime.AppendInt(MonthIndex); |
michael@0 | 1736 | sqlFragmentContainerBeginTime.Append(NS_LITERAL_CSTRING( |
michael@0 | 1737 | " months','utc')*1000000)")); |
michael@0 | 1738 | // To start of MonthIndex months ago |
michael@0 | 1739 | sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
michael@0 | 1740 | "(strftime('%s','now','localtime','start of month','-"); |
michael@0 | 1741 | sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1); |
michael@0 | 1742 | sqlFragmentContainerEndTime.Append(NS_LITERAL_CSTRING( |
michael@0 | 1743 | " months','utc')*1000000)")); |
michael@0 | 1744 | // Search for the same timeframe. |
michael@0 | 1745 | sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
michael@0 | 1746 | sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
michael@0 | 1747 | break; |
michael@0 | 1748 | } |
michael@0 | 1749 | |
michael@0 | 1750 | nsPrintfCString dateParam("dayTitle%d", i); |
michael@0 | 1751 | mAddParams.Put(dateParam, dateName); |
michael@0 | 1752 | |
michael@0 | 1753 | nsPrintfCString dayRange( |
michael@0 | 1754 | "SELECT :%s AS dayTitle, " |
michael@0 | 1755 | "%s AS beginTime, " |
michael@0 | 1756 | "%s AS endTime " |
michael@0 | 1757 | "WHERE EXISTS ( " |
michael@0 | 1758 | "SELECT id FROM moz_historyvisits " |
michael@0 | 1759 | "WHERE visit_date >= %s " |
michael@0 | 1760 | "AND visit_date < %s " |
michael@0 | 1761 | "AND visit_type NOT IN (0,%d,%d) " |
michael@0 | 1762 | "{QUERY_OPTIONS_VISITS} " |
michael@0 | 1763 | "LIMIT 1 " |
michael@0 | 1764 | ") ", |
michael@0 | 1765 | dateParam.get(), |
michael@0 | 1766 | sqlFragmentContainerBeginTime.get(), |
michael@0 | 1767 | sqlFragmentContainerEndTime.get(), |
michael@0 | 1768 | sqlFragmentSearchBeginTime.get(), |
michael@0 | 1769 | sqlFragmentSearchEndTime.get(), |
michael@0 | 1770 | nsINavHistoryService::TRANSITION_EMBED, |
michael@0 | 1771 | nsINavHistoryService::TRANSITION_FRAMED_LINK |
michael@0 | 1772 | ); |
michael@0 | 1773 | |
michael@0 | 1774 | mQueryString.Append(dayRange); |
michael@0 | 1775 | |
michael@0 | 1776 | if (i < HISTORY_DATE_CONT_NUM(daysOfHistory)) |
michael@0 | 1777 | mQueryString.Append(NS_LITERAL_CSTRING(" UNION ALL ")); |
michael@0 | 1778 | } |
michael@0 | 1779 | |
michael@0 | 1780 | mQueryString.Append(NS_LITERAL_CSTRING(") ")); // TOUTER END |
michael@0 | 1781 | |
michael@0 | 1782 | return NS_OK; |
michael@0 | 1783 | } |
michael@0 | 1784 | |
michael@0 | 1785 | nsresult |
michael@0 | 1786 | PlacesSQLQueryBuilder::SelectAsSite() |
michael@0 | 1787 | { |
michael@0 | 1788 | nsAutoCString localFiles; |
michael@0 | 1789 | |
michael@0 | 1790 | nsNavHistory *history = nsNavHistory::GetHistoryService(); |
michael@0 | 1791 | NS_ENSURE_STATE(history); |
michael@0 | 1792 | |
michael@0 | 1793 | history->GetStringFromName(MOZ_UTF16("localhost"), localFiles); |
michael@0 | 1794 | mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles); |
michael@0 | 1795 | |
michael@0 | 1796 | // If there are additional conditions the query has to join on visits too. |
michael@0 | 1797 | nsAutoCString visitsJoin; |
michael@0 | 1798 | nsAutoCString additionalConditions; |
michael@0 | 1799 | nsAutoCString timeConstraints; |
michael@0 | 1800 | if (!mConditions.IsEmpty()) { |
michael@0 | 1801 | visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id "); |
michael@0 | 1802 | additionalConditions.AssignLiteral("{QUERY_OPTIONS_VISITS} " |
michael@0 | 1803 | "{QUERY_OPTIONS_PLACES} " |
michael@0 | 1804 | "{ADDITIONAL_CONDITIONS} "); |
michael@0 | 1805 | timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||" |
michael@0 | 1806 | "'&endTime='||:end_time"); |
michael@0 | 1807 | } |
michael@0 | 1808 | |
michael@0 | 1809 | mQueryString = nsPrintfCString( |
michael@0 | 1810 | "SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, " |
michael@0 | 1811 | ":localhost, :localhost, null, null, null, null, null, null, null " |
michael@0 | 1812 | "WHERE EXISTS ( " |
michael@0 | 1813 | "SELECT h.id FROM moz_places h " |
michael@0 | 1814 | "%s " |
michael@0 | 1815 | "WHERE h.hidden = 0 " |
michael@0 | 1816 | "AND h.visit_count > 0 " |
michael@0 | 1817 | "AND h.url BETWEEN 'file://' AND 'file:/~' " |
michael@0 | 1818 | "%s " |
michael@0 | 1819 | "LIMIT 1 " |
michael@0 | 1820 | ") " |
michael@0 | 1821 | "UNION ALL " |
michael@0 | 1822 | "SELECT null, " |
michael@0 | 1823 | "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, " |
michael@0 | 1824 | "host, host, null, null, null, null, null, null, null " |
michael@0 | 1825 | "FROM ( " |
michael@0 | 1826 | "SELECT get_unreversed_host(h.rev_host) AS host " |
michael@0 | 1827 | "FROM moz_places h " |
michael@0 | 1828 | "%s " |
michael@0 | 1829 | "WHERE h.hidden = 0 " |
michael@0 | 1830 | "AND h.rev_host <> '.' " |
michael@0 | 1831 | "AND h.visit_count > 0 " |
michael@0 | 1832 | "%s " |
michael@0 | 1833 | "GROUP BY h.rev_host " |
michael@0 | 1834 | "ORDER BY host ASC " |
michael@0 | 1835 | ") ", |
michael@0 | 1836 | nsINavHistoryQueryOptions::RESULTS_AS_URI, |
michael@0 | 1837 | mSortingMode, |
michael@0 | 1838 | timeConstraints.get(), |
michael@0 | 1839 | visitsJoin.get(), |
michael@0 | 1840 | additionalConditions.get(), |
michael@0 | 1841 | nsINavHistoryQueryOptions::RESULTS_AS_URI, |
michael@0 | 1842 | mSortingMode, |
michael@0 | 1843 | timeConstraints.get(), |
michael@0 | 1844 | visitsJoin.get(), |
michael@0 | 1845 | additionalConditions.get() |
michael@0 | 1846 | ); |
michael@0 | 1847 | |
michael@0 | 1848 | return NS_OK; |
michael@0 | 1849 | } |
michael@0 | 1850 | |
michael@0 | 1851 | nsresult |
michael@0 | 1852 | PlacesSQLQueryBuilder::SelectAsTag() |
michael@0 | 1853 | { |
michael@0 | 1854 | nsNavHistory *history = nsNavHistory::GetHistoryService(); |
michael@0 | 1855 | NS_ENSURE_STATE(history); |
michael@0 | 1856 | |
michael@0 | 1857 | // This allows sorting by date fields what is not possible with |
michael@0 | 1858 | // other history queries. |
michael@0 | 1859 | mHasDateColumns = true; |
michael@0 | 1860 | |
michael@0 | 1861 | mQueryString = nsPrintfCString( |
michael@0 | 1862 | "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', " |
michael@0 | 1863 | "title, null, null, null, null, null, dateAdded, " |
michael@0 | 1864 | "lastModified, null, null, null " |
michael@0 | 1865 | "FROM moz_bookmarks " |
michael@0 | 1866 | "WHERE parent = %lld", |
michael@0 | 1867 | nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS, |
michael@0 | 1868 | nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS, |
michael@0 | 1869 | history->GetTagsFolder() |
michael@0 | 1870 | ); |
michael@0 | 1871 | |
michael@0 | 1872 | return NS_OK; |
michael@0 | 1873 | } |
michael@0 | 1874 | |
michael@0 | 1875 | nsresult |
michael@0 | 1876 | PlacesSQLQueryBuilder::Where() |
michael@0 | 1877 | { |
michael@0 | 1878 | |
michael@0 | 1879 | // Set query options |
michael@0 | 1880 | nsAutoCString additionalVisitsConditions; |
michael@0 | 1881 | nsAutoCString additionalPlacesConditions; |
michael@0 | 1882 | |
michael@0 | 1883 | if (!mIncludeHidden) { |
michael@0 | 1884 | additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 "); |
michael@0 | 1885 | } |
michael@0 | 1886 | |
michael@0 | 1887 | if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) { |
michael@0 | 1888 | // last_visit_date is updated for any kind of visit, so it's a good |
michael@0 | 1889 | // indicator whether the page has visits. |
michael@0 | 1890 | additionalPlacesConditions += NS_LITERAL_CSTRING( |
michael@0 | 1891 | "AND last_visit_date NOTNULL " |
michael@0 | 1892 | ); |
michael@0 | 1893 | } |
michael@0 | 1894 | |
michael@0 | 1895 | if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI && |
michael@0 | 1896 | !additionalVisitsConditions.IsEmpty()) { |
michael@0 | 1897 | // URI results don't join on visits. |
michael@0 | 1898 | nsAutoCString tmp = additionalVisitsConditions; |
michael@0 | 1899 | additionalVisitsConditions = "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id "; |
michael@0 | 1900 | additionalVisitsConditions.Append(tmp); |
michael@0 | 1901 | additionalVisitsConditions.Append("LIMIT 1)"); |
michael@0 | 1902 | } |
michael@0 | 1903 | |
michael@0 | 1904 | mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}", |
michael@0 | 1905 | additionalVisitsConditions.get()); |
michael@0 | 1906 | mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}", |
michael@0 | 1907 | additionalPlacesConditions.get()); |
michael@0 | 1908 | |
michael@0 | 1909 | // If we used WHERE already, we inject the conditions |
michael@0 | 1910 | // in place of {ADDITIONAL_CONDITIONS} |
michael@0 | 1911 | if (mQueryString.Find("{ADDITIONAL_CONDITIONS}", 0) != kNotFound) { |
michael@0 | 1912 | nsAutoCString innerCondition; |
michael@0 | 1913 | // If we have condition AND it |
michael@0 | 1914 | if (!mConditions.IsEmpty()) { |
michael@0 | 1915 | innerCondition = " AND ("; |
michael@0 | 1916 | innerCondition += mConditions; |
michael@0 | 1917 | innerCondition += ")"; |
michael@0 | 1918 | } |
michael@0 | 1919 | mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}", |
michael@0 | 1920 | innerCondition.get()); |
michael@0 | 1921 | |
michael@0 | 1922 | } else if (!mConditions.IsEmpty()) { |
michael@0 | 1923 | |
michael@0 | 1924 | mQueryString += "WHERE "; |
michael@0 | 1925 | mQueryString += mConditions; |
michael@0 | 1926 | |
michael@0 | 1927 | } |
michael@0 | 1928 | return NS_OK; |
michael@0 | 1929 | } |
michael@0 | 1930 | |
michael@0 | 1931 | nsresult |
michael@0 | 1932 | PlacesSQLQueryBuilder::GroupBy() |
michael@0 | 1933 | { |
michael@0 | 1934 | mQueryString += mGroupBy; |
michael@0 | 1935 | return NS_OK; |
michael@0 | 1936 | } |
michael@0 | 1937 | |
michael@0 | 1938 | nsresult |
michael@0 | 1939 | PlacesSQLQueryBuilder::OrderBy() |
michael@0 | 1940 | { |
michael@0 | 1941 | if (mSkipOrderBy) |
michael@0 | 1942 | return NS_OK; |
michael@0 | 1943 | |
michael@0 | 1944 | // Sort clause: we will sort later, but if it comes out of the DB sorted, |
michael@0 | 1945 | // our later sort will be basically free. The DB can sort these for free |
michael@0 | 1946 | // most of the time anyway, because it has indices over these items. |
michael@0 | 1947 | switch(mSortingMode) |
michael@0 | 1948 | { |
michael@0 | 1949 | case nsINavHistoryQueryOptions::SORT_BY_NONE: |
michael@0 | 1950 | // Ensure sorting does not change based on tables status. |
michael@0 | 1951 | if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) { |
michael@0 | 1952 | if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS) |
michael@0 | 1953 | mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC "); |
michael@0 | 1954 | else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) |
michael@0 | 1955 | mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC "); |
michael@0 | 1956 | } |
michael@0 | 1957 | break; |
michael@0 | 1958 | case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING: |
michael@0 | 1959 | case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING: |
michael@0 | 1960 | // If the user wants few results, we limit them by date, necessitating |
michael@0 | 1961 | // a sort by date here (see the IDL definition for maxResults). |
michael@0 | 1962 | // Otherwise we will do actual sorting by title, but since we could need |
michael@0 | 1963 | // to special sort for some locale we will repeat a second sorting at the |
michael@0 | 1964 | // end in nsNavHistoryResult, that should be faster since the list will be |
michael@0 | 1965 | // almost ordered. |
michael@0 | 1966 | if (mMaxResults > 0) |
michael@0 | 1967 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); |
michael@0 | 1968 | else if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING) |
michael@0 | 1969 | OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title); |
michael@0 | 1970 | else |
michael@0 | 1971 | OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title); |
michael@0 | 1972 | break; |
michael@0 | 1973 | case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING: |
michael@0 | 1974 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate); |
michael@0 | 1975 | break; |
michael@0 | 1976 | case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING: |
michael@0 | 1977 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); |
michael@0 | 1978 | break; |
michael@0 | 1979 | case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING: |
michael@0 | 1980 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL); |
michael@0 | 1981 | break; |
michael@0 | 1982 | case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING: |
michael@0 | 1983 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL); |
michael@0 | 1984 | break; |
michael@0 | 1985 | case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING: |
michael@0 | 1986 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount); |
michael@0 | 1987 | break; |
michael@0 | 1988 | case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING: |
michael@0 | 1989 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount); |
michael@0 | 1990 | break; |
michael@0 | 1991 | case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING: |
michael@0 | 1992 | if (mHasDateColumns) |
michael@0 | 1993 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded); |
michael@0 | 1994 | break; |
michael@0 | 1995 | case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING: |
michael@0 | 1996 | if (mHasDateColumns) |
michael@0 | 1997 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded); |
michael@0 | 1998 | break; |
michael@0 | 1999 | case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING: |
michael@0 | 2000 | if (mHasDateColumns) |
michael@0 | 2001 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified); |
michael@0 | 2002 | break; |
michael@0 | 2003 | case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING: |
michael@0 | 2004 | if (mHasDateColumns) |
michael@0 | 2005 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified); |
michael@0 | 2006 | break; |
michael@0 | 2007 | case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING: |
michael@0 | 2008 | case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING: |
michael@0 | 2009 | case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING: |
michael@0 | 2010 | case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING: |
michael@0 | 2011 | break; // Sort later in nsNavHistoryQueryResultNode::FillChildren() |
michael@0 | 2012 | case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING: |
michael@0 | 2013 | OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency); |
michael@0 | 2014 | break; |
michael@0 | 2015 | case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING: |
michael@0 | 2016 | OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency); |
michael@0 | 2017 | break; |
michael@0 | 2018 | default: |
michael@0 | 2019 | NS_NOTREACHED("Invalid sorting mode"); |
michael@0 | 2020 | } |
michael@0 | 2021 | return NS_OK; |
michael@0 | 2022 | } |
michael@0 | 2023 | |
michael@0 | 2024 | void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex) |
michael@0 | 2025 | { |
michael@0 | 2026 | mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex+1); |
michael@0 | 2027 | } |
michael@0 | 2028 | |
michael@0 | 2029 | void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex) |
michael@0 | 2030 | { |
michael@0 | 2031 | mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex+1); |
michael@0 | 2032 | } |
michael@0 | 2033 | |
michael@0 | 2034 | void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex) |
michael@0 | 2035 | { |
michael@0 | 2036 | mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC", |
michael@0 | 2037 | aIndex+1); |
michael@0 | 2038 | } |
michael@0 | 2039 | |
michael@0 | 2040 | void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex) |
michael@0 | 2041 | { |
michael@0 | 2042 | mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC", |
michael@0 | 2043 | aIndex+1); |
michael@0 | 2044 | } |
michael@0 | 2045 | |
michael@0 | 2046 | nsresult |
michael@0 | 2047 | PlacesSQLQueryBuilder::Limit() |
michael@0 | 2048 | { |
michael@0 | 2049 | if (mUseLimit && mMaxResults > 0) { |
michael@0 | 2050 | mQueryString += NS_LITERAL_CSTRING(" LIMIT "); |
michael@0 | 2051 | mQueryString.AppendInt(mMaxResults); |
michael@0 | 2052 | mQueryString.AppendLiteral(" "); |
michael@0 | 2053 | } |
michael@0 | 2054 | return NS_OK; |
michael@0 | 2055 | } |
michael@0 | 2056 | |
michael@0 | 2057 | nsresult |
michael@0 | 2058 | nsNavHistory::ConstructQueryString( |
michael@0 | 2059 | const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 2060 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 2061 | nsCString& queryString, |
michael@0 | 2062 | bool& aParamsPresent, |
michael@0 | 2063 | nsNavHistory::StringHash& aAddParams) |
michael@0 | 2064 | { |
michael@0 | 2065 | // For information about visit_type see nsINavHistoryService.idl. |
michael@0 | 2066 | // visitType == 0 is undefined (see bug #375777 for details). |
michael@0 | 2067 | // Some sites, especially Javascript-heavy ones, load things in frames to |
michael@0 | 2068 | // display them, resulting in a lot of these entries. This is the reason |
michael@0 | 2069 | // why such visits are filtered out. |
michael@0 | 2070 | nsresult rv; |
michael@0 | 2071 | aParamsPresent = false; |
michael@0 | 2072 | |
michael@0 | 2073 | int32_t sortingMode = aOptions->SortingMode(); |
michael@0 | 2074 | NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE && |
michael@0 | 2075 | sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING, |
michael@0 | 2076 | "Invalid sortingMode found while building query!"); |
michael@0 | 2077 | |
michael@0 | 2078 | bool hasSearchTerms = false; |
michael@0 | 2079 | for (int32_t i = 0; i < aQueries.Count() && !hasSearchTerms; i++) { |
michael@0 | 2080 | aQueries[i]->GetHasSearchTerms(&hasSearchTerms); |
michael@0 | 2081 | } |
michael@0 | 2082 | |
michael@0 | 2083 | nsAutoCString tagsSqlFragment; |
michael@0 | 2084 | GetTagsSqlFragment(GetTagsFolder(), |
michael@0 | 2085 | NS_LITERAL_CSTRING("h.id"), |
michael@0 | 2086 | hasSearchTerms, |
michael@0 | 2087 | tagsSqlFragment); |
michael@0 | 2088 | |
michael@0 | 2089 | if (IsOptimizableHistoryQuery(aQueries, aOptions, |
michael@0 | 2090 | nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) || |
michael@0 | 2091 | IsOptimizableHistoryQuery(aQueries, aOptions, |
michael@0 | 2092 | nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) { |
michael@0 | 2093 | // Generate an optimized query for the history menu and most visited |
michael@0 | 2094 | // smart bookmark. |
michael@0 | 2095 | queryString = NS_LITERAL_CSTRING( |
michael@0 | 2096 | "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, " |
michael@0 | 2097 | "f.url, null, null, null, null, ") + |
michael@0 | 2098 | tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 2099 | "FROM moz_places h " |
michael@0 | 2100 | "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 2101 | "WHERE h.hidden = 0 " |
michael@0 | 2102 | "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id " |
michael@0 | 2103 | "AND visit_type NOT IN ") + |
michael@0 | 2104 | nsPrintfCString("(0,%d,%d) ", |
michael@0 | 2105 | nsINavHistoryService::TRANSITION_EMBED, |
michael@0 | 2106 | nsINavHistoryService::TRANSITION_FRAMED_LINK) + |
michael@0 | 2107 | NS_LITERAL_CSTRING("LIMIT 1) " |
michael@0 | 2108 | "{QUERY_OPTIONS} " |
michael@0 | 2109 | ); |
michael@0 | 2110 | |
michael@0 | 2111 | queryString.Append(NS_LITERAL_CSTRING("ORDER BY ")); |
michael@0 | 2112 | if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) |
michael@0 | 2113 | queryString.Append(NS_LITERAL_CSTRING("last_visit_date DESC ")); |
michael@0 | 2114 | else |
michael@0 | 2115 | queryString.Append(NS_LITERAL_CSTRING("visit_count DESC ")); |
michael@0 | 2116 | |
michael@0 | 2117 | queryString.Append(NS_LITERAL_CSTRING("LIMIT ")); |
michael@0 | 2118 | queryString.AppendInt(aOptions->MaxResults()); |
michael@0 | 2119 | |
michael@0 | 2120 | nsAutoCString additionalQueryOptions; |
michael@0 | 2121 | |
michael@0 | 2122 | queryString.ReplaceSubstring("{QUERY_OPTIONS}", |
michael@0 | 2123 | additionalQueryOptions.get()); |
michael@0 | 2124 | return NS_OK; |
michael@0 | 2125 | } |
michael@0 | 2126 | |
michael@0 | 2127 | nsAutoCString conditions; |
michael@0 | 2128 | for (int32_t i = 0; i < aQueries.Count(); i++) { |
michael@0 | 2129 | nsCString queryClause; |
michael@0 | 2130 | rv = QueryToSelectClause(aQueries[i], aOptions, i, &queryClause); |
michael@0 | 2131 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2132 | if (! queryClause.IsEmpty()) { |
michael@0 | 2133 | aParamsPresent = true; |
michael@0 | 2134 | if (! conditions.IsEmpty()) // exists previous clause: multiple ones are ORed |
michael@0 | 2135 | conditions += NS_LITERAL_CSTRING(" OR "); |
michael@0 | 2136 | conditions += NS_LITERAL_CSTRING("(") + queryClause + |
michael@0 | 2137 | NS_LITERAL_CSTRING(")"); |
michael@0 | 2138 | } |
michael@0 | 2139 | } |
michael@0 | 2140 | |
michael@0 | 2141 | // Determine whether we can push maxResults constraints into the queries |
michael@0 | 2142 | // as LIMIT, or if we need to do result count clamping later |
michael@0 | 2143 | // using FilterResultSet() |
michael@0 | 2144 | bool useLimitClause = !NeedToFilterResultSet(aQueries, aOptions); |
michael@0 | 2145 | |
michael@0 | 2146 | PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions, |
michael@0 | 2147 | useLimitClause, aAddParams, |
michael@0 | 2148 | hasSearchTerms); |
michael@0 | 2149 | rv = queryStringBuilder.GetQueryString(queryString); |
michael@0 | 2150 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2151 | |
michael@0 | 2152 | return NS_OK; |
michael@0 | 2153 | } |
michael@0 | 2154 | |
michael@0 | 2155 | PLDHashOperator BindAdditionalParameter(nsNavHistory::StringHash::KeyType aParamName, |
michael@0 | 2156 | nsCString aParamValue, |
michael@0 | 2157 | void* aStatement) |
michael@0 | 2158 | { |
michael@0 | 2159 | mozIStorageStatement* stmt = static_cast<mozIStorageStatement*>(aStatement); |
michael@0 | 2160 | |
michael@0 | 2161 | nsresult rv = stmt->BindUTF8StringByName(aParamName, aParamValue); |
michael@0 | 2162 | if (NS_FAILED(rv)) |
michael@0 | 2163 | return PL_DHASH_STOP; |
michael@0 | 2164 | |
michael@0 | 2165 | return PL_DHASH_NEXT; |
michael@0 | 2166 | } |
michael@0 | 2167 | |
michael@0 | 2168 | // nsNavHistory::GetQueryResults |
michael@0 | 2169 | // |
michael@0 | 2170 | // Call this to get the results from a complex query. This is used by |
michael@0 | 2171 | // nsNavHistoryQueryResultNode to populate its children. For simple bookmark |
michael@0 | 2172 | // queries, use nsNavBookmarks::QueryFolderChildren. |
michael@0 | 2173 | // |
michael@0 | 2174 | // THIS DOES NOT DO SORTING. You will need to sort the container yourself |
michael@0 | 2175 | // when you get the results. This is because sorting depends on tree |
michael@0 | 2176 | // statistics that will be built from the perspective of the tree. See |
michael@0 | 2177 | // nsNavHistoryQueryResultNode::FillChildren |
michael@0 | 2178 | // |
michael@0 | 2179 | // FIXME: This only does keyword searching for the first query, and does |
michael@0 | 2180 | // it ANDed with the all the rest of the queries. |
michael@0 | 2181 | |
michael@0 | 2182 | nsresult |
michael@0 | 2183 | nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode, |
michael@0 | 2184 | const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 2185 | nsNavHistoryQueryOptions *aOptions, |
michael@0 | 2186 | nsCOMArray<nsNavHistoryResultNode>* aResults) |
michael@0 | 2187 | { |
michael@0 | 2188 | NS_ENSURE_ARG_POINTER(aOptions); |
michael@0 | 2189 | NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty"); |
michael@0 | 2190 | if (! aQueries.Count()) |
michael@0 | 2191 | return NS_ERROR_INVALID_ARG; |
michael@0 | 2192 | |
michael@0 | 2193 | nsCString queryString; |
michael@0 | 2194 | bool paramsPresent = false; |
michael@0 | 2195 | nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); |
michael@0 | 2196 | nsresult rv = ConstructQueryString(aQueries, aOptions, queryString, |
michael@0 | 2197 | paramsPresent, addParams); |
michael@0 | 2198 | NS_ENSURE_SUCCESS(rv,rv); |
michael@0 | 2199 | |
michael@0 | 2200 | // create statement |
michael@0 | 2201 | nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString); |
michael@0 | 2202 | #ifdef DEBUG |
michael@0 | 2203 | if (!statement) { |
michael@0 | 2204 | nsAutoCString lastErrorString; |
michael@0 | 2205 | (void)mDB->MainConn()->GetLastErrorString(lastErrorString); |
michael@0 | 2206 | int32_t lastError = 0; |
michael@0 | 2207 | (void)mDB->MainConn()->GetLastError(&lastError); |
michael@0 | 2208 | printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", |
michael@0 | 2209 | queryString.get(), lastError, lastErrorString.get()); |
michael@0 | 2210 | } |
michael@0 | 2211 | #endif |
michael@0 | 2212 | NS_ENSURE_STATE(statement); |
michael@0 | 2213 | mozStorageStatementScoper scoper(statement); |
michael@0 | 2214 | |
michael@0 | 2215 | if (paramsPresent) { |
michael@0 | 2216 | // bind parameters |
michael@0 | 2217 | int32_t i; |
michael@0 | 2218 | for (i = 0; i < aQueries.Count(); i++) { |
michael@0 | 2219 | rv = BindQueryClauseParameters(statement, i, aQueries[i], aOptions); |
michael@0 | 2220 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2221 | } |
michael@0 | 2222 | } |
michael@0 | 2223 | |
michael@0 | 2224 | addParams.EnumerateRead(BindAdditionalParameter, statement.get()); |
michael@0 | 2225 | |
michael@0 | 2226 | // Optimize the case where there is no need for any post-query filtering. |
michael@0 | 2227 | if (NeedToFilterResultSet(aQueries, aOptions)) { |
michael@0 | 2228 | // Generate the top-level results. |
michael@0 | 2229 | nsCOMArray<nsNavHistoryResultNode> toplevel; |
michael@0 | 2230 | rv = ResultsAsList(statement, aOptions, &toplevel); |
michael@0 | 2231 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2232 | |
michael@0 | 2233 | FilterResultSet(aResultNode, toplevel, aResults, aQueries, aOptions); |
michael@0 | 2234 | } else { |
michael@0 | 2235 | rv = ResultsAsList(statement, aOptions, aResults); |
michael@0 | 2236 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2237 | } |
michael@0 | 2238 | |
michael@0 | 2239 | return NS_OK; |
michael@0 | 2240 | } |
michael@0 | 2241 | |
michael@0 | 2242 | |
michael@0 | 2243 | // nsNavHistory::AddObserver |
michael@0 | 2244 | |
michael@0 | 2245 | NS_IMETHODIMP |
michael@0 | 2246 | nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak) |
michael@0 | 2247 | { |
michael@0 | 2248 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2249 | NS_ENSURE_ARG(aObserver); |
michael@0 | 2250 | |
michael@0 | 2251 | return mObservers.AppendWeakElement(aObserver, aOwnsWeak); |
michael@0 | 2252 | } |
michael@0 | 2253 | |
michael@0 | 2254 | |
michael@0 | 2255 | // nsNavHistory::RemoveObserver |
michael@0 | 2256 | |
michael@0 | 2257 | NS_IMETHODIMP |
michael@0 | 2258 | nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver) |
michael@0 | 2259 | { |
michael@0 | 2260 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2261 | NS_ENSURE_ARG(aObserver); |
michael@0 | 2262 | |
michael@0 | 2263 | return mObservers.RemoveWeakElement(aObserver); |
michael@0 | 2264 | } |
michael@0 | 2265 | |
michael@0 | 2266 | // nsNavHistory::BeginUpdateBatch |
michael@0 | 2267 | // See RunInBatchMode |
michael@0 | 2268 | nsresult |
michael@0 | 2269 | nsNavHistory::BeginUpdateBatch() |
michael@0 | 2270 | { |
michael@0 | 2271 | if (mBatchLevel++ == 0) { |
michael@0 | 2272 | mBatchDBTransaction = new mozStorageTransaction(mDB->MainConn(), false); |
michael@0 | 2273 | |
michael@0 | 2274 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 2275 | nsINavHistoryObserver, OnBeginUpdateBatch()); |
michael@0 | 2276 | } |
michael@0 | 2277 | return NS_OK; |
michael@0 | 2278 | } |
michael@0 | 2279 | |
michael@0 | 2280 | // nsNavHistory::EndUpdateBatch |
michael@0 | 2281 | nsresult |
michael@0 | 2282 | nsNavHistory::EndUpdateBatch() |
michael@0 | 2283 | { |
michael@0 | 2284 | if (--mBatchLevel == 0) { |
michael@0 | 2285 | if (mBatchDBTransaction) { |
michael@0 | 2286 | DebugOnly<nsresult> rv = mBatchDBTransaction->Commit(); |
michael@0 | 2287 | NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Batch failed to commit transaction"); |
michael@0 | 2288 | delete mBatchDBTransaction; |
michael@0 | 2289 | mBatchDBTransaction = nullptr; |
michael@0 | 2290 | } |
michael@0 | 2291 | |
michael@0 | 2292 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 2293 | nsINavHistoryObserver, OnEndUpdateBatch()); |
michael@0 | 2294 | } |
michael@0 | 2295 | return NS_OK; |
michael@0 | 2296 | } |
michael@0 | 2297 | |
michael@0 | 2298 | NS_IMETHODIMP |
michael@0 | 2299 | nsNavHistory::RunInBatchMode(nsINavHistoryBatchCallback* aCallback, |
michael@0 | 2300 | nsISupports* aUserData) |
michael@0 | 2301 | { |
michael@0 | 2302 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2303 | NS_ENSURE_ARG(aCallback); |
michael@0 | 2304 | |
michael@0 | 2305 | UpdateBatchScoper batch(*this); |
michael@0 | 2306 | return aCallback->RunBatched(aUserData); |
michael@0 | 2307 | } |
michael@0 | 2308 | |
michael@0 | 2309 | NS_IMETHODIMP |
michael@0 | 2310 | nsNavHistory::GetHistoryDisabled(bool *_retval) |
michael@0 | 2311 | { |
michael@0 | 2312 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2313 | NS_ENSURE_ARG_POINTER(_retval); |
michael@0 | 2314 | |
michael@0 | 2315 | *_retval = IsHistoryDisabled(); |
michael@0 | 2316 | return NS_OK; |
michael@0 | 2317 | } |
michael@0 | 2318 | |
michael@0 | 2319 | // Browser history ************************************************************* |
michael@0 | 2320 | |
michael@0 | 2321 | |
michael@0 | 2322 | // nsNavHistory::RemovePagesInternal |
michael@0 | 2323 | // |
michael@0 | 2324 | // Deletes a list of placeIds from history. |
michael@0 | 2325 | // This is an internal method used by RemovePages, RemovePagesFromHost and |
michael@0 | 2326 | // RemovePagesByTimeframe. |
michael@0 | 2327 | // Takes a comma separated list of place ids. |
michael@0 | 2328 | // This method does not do any observer notification. |
michael@0 | 2329 | |
michael@0 | 2330 | nsresult |
michael@0 | 2331 | nsNavHistory::RemovePagesInternal(const nsCString& aPlaceIdsQueryString) |
michael@0 | 2332 | { |
michael@0 | 2333 | // Return early if there is nothing to delete. |
michael@0 | 2334 | if (aPlaceIdsQueryString.IsEmpty()) |
michael@0 | 2335 | return NS_OK; |
michael@0 | 2336 | |
michael@0 | 2337 | mozStorageTransaction transaction(mDB->MainConn(), false); |
michael@0 | 2338 | |
michael@0 | 2339 | // Delete all visits for the specified place ids. |
michael@0 | 2340 | nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( |
michael@0 | 2341 | NS_LITERAL_CSTRING( |
michael@0 | 2342 | "DELETE FROM moz_historyvisits WHERE place_id IN (") + |
michael@0 | 2343 | aPlaceIdsQueryString + |
michael@0 | 2344 | NS_LITERAL_CSTRING(")") |
michael@0 | 2345 | ); |
michael@0 | 2346 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2347 | |
michael@0 | 2348 | rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString); |
michael@0 | 2349 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2350 | |
michael@0 | 2351 | // Invalidate the cached value for whether there's history or not. |
michael@0 | 2352 | mDaysOfHistory = -1; |
michael@0 | 2353 | |
michael@0 | 2354 | return transaction.Commit(); |
michael@0 | 2355 | } |
michael@0 | 2356 | |
michael@0 | 2357 | |
michael@0 | 2358 | /** |
michael@0 | 2359 | * Performs cleanup on places that just had all their visits removed, including |
michael@0 | 2360 | * deletion of those places. This is an internal method used by |
michael@0 | 2361 | * RemovePagesInternal and RemoveVisitsByTimeframe. This method does not |
michael@0 | 2362 | * execute in a transaction, so callers should make sure they begin one if |
michael@0 | 2363 | * needed. |
michael@0 | 2364 | * |
michael@0 | 2365 | * @param aPlaceIdsQueryString |
michael@0 | 2366 | * A comma-separated list of place IDs, each of which just had all its |
michael@0 | 2367 | * visits removed |
michael@0 | 2368 | */ |
michael@0 | 2369 | nsresult |
michael@0 | 2370 | nsNavHistory::CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString) |
michael@0 | 2371 | { |
michael@0 | 2372 | // Return early if there is nothing to delete. |
michael@0 | 2373 | if (aPlaceIdsQueryString.IsEmpty()) |
michael@0 | 2374 | return NS_OK; |
michael@0 | 2375 | |
michael@0 | 2376 | // Collect about-to-be-deleted URIs to notify onDeleteURI. |
michael@0 | 2377 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
michael@0 | 2378 | "SELECT h.id, h.url, h.guid, " |
michael@0 | 2379 | "(SUBSTR(h.url, 1, 6) <> 'place:' " |
michael@0 | 2380 | " AND NOT EXISTS (SELECT b.id FROM moz_bookmarks b " |
michael@0 | 2381 | "WHERE b.fk = h.id LIMIT 1)) as whole_entry " |
michael@0 | 2382 | "FROM moz_places h " |
michael@0 | 2383 | "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(")") |
michael@0 | 2384 | ); |
michael@0 | 2385 | NS_ENSURE_STATE(stmt); |
michael@0 | 2386 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 2387 | |
michael@0 | 2388 | nsCString filteredPlaceIds; |
michael@0 | 2389 | nsCOMArray<nsIURI> URIs; |
michael@0 | 2390 | nsTArray<nsCString> GUIDs; |
michael@0 | 2391 | bool hasMore; |
michael@0 | 2392 | while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) { |
michael@0 | 2393 | int64_t placeId; |
michael@0 | 2394 | nsresult rv = stmt->GetInt64(0, &placeId); |
michael@0 | 2395 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2396 | nsAutoCString URLString; |
michael@0 | 2397 | rv = stmt->GetUTF8String(1, URLString); |
michael@0 | 2398 | nsCString guid; |
michael@0 | 2399 | rv = stmt->GetUTF8String(2, guid); |
michael@0 | 2400 | int32_t wholeEntry; |
michael@0 | 2401 | rv = stmt->GetInt32(3, &wholeEntry); |
michael@0 | 2402 | nsCOMPtr<nsIURI> uri; |
michael@0 | 2403 | rv = NS_NewURI(getter_AddRefs(uri), URLString); |
michael@0 | 2404 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2405 | if (wholeEntry) { |
michael@0 | 2406 | if (!filteredPlaceIds.IsEmpty()) { |
michael@0 | 2407 | filteredPlaceIds.AppendLiteral(","); |
michael@0 | 2408 | } |
michael@0 | 2409 | filteredPlaceIds.AppendInt(placeId); |
michael@0 | 2410 | URIs.AppendObject(uri); |
michael@0 | 2411 | GUIDs.AppendElement(guid); |
michael@0 | 2412 | } |
michael@0 | 2413 | else { |
michael@0 | 2414 | // Notify that we will delete all visits for this page, but not the page |
michael@0 | 2415 | // itself, since it's bookmarked or a place: query. |
michael@0 | 2416 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 2417 | nsINavHistoryObserver, |
michael@0 | 2418 | OnDeleteVisits(uri, 0, guid, nsINavHistoryObserver::REASON_DELETED, 0)); |
michael@0 | 2419 | } |
michael@0 | 2420 | } |
michael@0 | 2421 | |
michael@0 | 2422 | // if the entry is not bookmarked and is not a place: uri |
michael@0 | 2423 | // then we can remove it from moz_places. |
michael@0 | 2424 | // Note that we do NOT delete favicons. Any unreferenced favicons will be |
michael@0 | 2425 | // deleted next time the browser is shut down. |
michael@0 | 2426 | nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( |
michael@0 | 2427 | NS_LITERAL_CSTRING( |
michael@0 | 2428 | "DELETE FROM moz_places WHERE id IN ( " |
michael@0 | 2429 | ) + filteredPlaceIds + NS_LITERAL_CSTRING( |
michael@0 | 2430 | ") " |
michael@0 | 2431 | ) |
michael@0 | 2432 | ); |
michael@0 | 2433 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2434 | |
michael@0 | 2435 | // Invalidate frecencies of touched places, since they need recalculation. |
michael@0 | 2436 | rv = invalidateFrecencies(aPlaceIdsQueryString); |
michael@0 | 2437 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2438 | |
michael@0 | 2439 | // Finally notify about the removed URIs. |
michael@0 | 2440 | for (int32_t i = 0; i < URIs.Count(); ++i) { |
michael@0 | 2441 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 2442 | nsINavHistoryObserver, |
michael@0 | 2443 | OnDeleteURI(URIs[i], GUIDs[i], nsINavHistoryObserver::REASON_DELETED)); |
michael@0 | 2444 | } |
michael@0 | 2445 | |
michael@0 | 2446 | return NS_OK; |
michael@0 | 2447 | } |
michael@0 | 2448 | |
michael@0 | 2449 | |
michael@0 | 2450 | // nsNavHistory::RemovePages |
michael@0 | 2451 | // |
michael@0 | 2452 | // Removes a bunch of uris from history. |
michael@0 | 2453 | // Has better performance than RemovePage when deleting a lot of history. |
michael@0 | 2454 | // We don't do duplicates removal, URIs array should be cleaned-up before. |
michael@0 | 2455 | |
michael@0 | 2456 | NS_IMETHODIMP |
michael@0 | 2457 | nsNavHistory::RemovePages(nsIURI **aURIs, uint32_t aLength) |
michael@0 | 2458 | { |
michael@0 | 2459 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2460 | NS_ENSURE_ARG(aURIs); |
michael@0 | 2461 | |
michael@0 | 2462 | nsresult rv; |
michael@0 | 2463 | // build a list of place ids to delete |
michael@0 | 2464 | nsCString deletePlaceIdsQueryString; |
michael@0 | 2465 | for (uint32_t i = 0; i < aLength; i++) { |
michael@0 | 2466 | int64_t placeId; |
michael@0 | 2467 | nsAutoCString guid; |
michael@0 | 2468 | rv = GetIdForPage(aURIs[i], &placeId, guid); |
michael@0 | 2469 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2470 | if (placeId != 0) { |
michael@0 | 2471 | if (!deletePlaceIdsQueryString.IsEmpty()) |
michael@0 | 2472 | deletePlaceIdsQueryString.AppendLiteral(","); |
michael@0 | 2473 | deletePlaceIdsQueryString.AppendInt(placeId); |
michael@0 | 2474 | } |
michael@0 | 2475 | } |
michael@0 | 2476 | |
michael@0 | 2477 | UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
michael@0 | 2478 | |
michael@0 | 2479 | rv = RemovePagesInternal(deletePlaceIdsQueryString); |
michael@0 | 2480 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2481 | |
michael@0 | 2482 | // Clear the registered embed visits. |
michael@0 | 2483 | clearEmbedVisits(); |
michael@0 | 2484 | |
michael@0 | 2485 | return NS_OK; |
michael@0 | 2486 | } |
michael@0 | 2487 | |
michael@0 | 2488 | |
michael@0 | 2489 | // nsNavHistory::RemovePage |
michael@0 | 2490 | // |
michael@0 | 2491 | // Removes all visits and the main history entry for the given URI. |
michael@0 | 2492 | // Silently fails if we have no knowledge of the page. |
michael@0 | 2493 | |
michael@0 | 2494 | NS_IMETHODIMP |
michael@0 | 2495 | nsNavHistory::RemovePage(nsIURI *aURI) |
michael@0 | 2496 | { |
michael@0 | 2497 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2498 | NS_ENSURE_ARG(aURI); |
michael@0 | 2499 | |
michael@0 | 2500 | // Build a list of place ids to delete. |
michael@0 | 2501 | int64_t placeId; |
michael@0 | 2502 | nsAutoCString guid; |
michael@0 | 2503 | nsresult rv = GetIdForPage(aURI, &placeId, guid); |
michael@0 | 2504 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2505 | if (placeId == 0) { |
michael@0 | 2506 | return NS_OK; |
michael@0 | 2507 | } |
michael@0 | 2508 | nsAutoCString deletePlaceIdQueryString; |
michael@0 | 2509 | deletePlaceIdQueryString.AppendInt(placeId); |
michael@0 | 2510 | |
michael@0 | 2511 | rv = RemovePagesInternal(deletePlaceIdQueryString); |
michael@0 | 2512 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2513 | |
michael@0 | 2514 | // Clear the registered embed visits. |
michael@0 | 2515 | clearEmbedVisits(); |
michael@0 | 2516 | |
michael@0 | 2517 | return NS_OK; |
michael@0 | 2518 | } |
michael@0 | 2519 | |
michael@0 | 2520 | |
michael@0 | 2521 | // nsNavHistory::RemovePagesFromHost |
michael@0 | 2522 | // |
michael@0 | 2523 | // This function will delete all history information about pages from a |
michael@0 | 2524 | // given host. If aEntireDomain is set, we will also delete pages from |
michael@0 | 2525 | // sub hosts (so if we are passed in "microsoft.com" we delete |
michael@0 | 2526 | // "www.microsoft.com", "msdn.microsoft.com", etc.). An empty host name |
michael@0 | 2527 | // means local files and anything else with no host name. You can also pass |
michael@0 | 2528 | // in the localized "(local files)" title given to you from a history query. |
michael@0 | 2529 | // |
michael@0 | 2530 | // Silently fails if we have no knowledge of the host. |
michael@0 | 2531 | // |
michael@0 | 2532 | // This sends onBeginUpdateBatch/onEndUpdateBatch to observers |
michael@0 | 2533 | |
michael@0 | 2534 | NS_IMETHODIMP |
michael@0 | 2535 | nsNavHistory::RemovePagesFromHost(const nsACString& aHost, bool aEntireDomain) |
michael@0 | 2536 | { |
michael@0 | 2537 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2538 | |
michael@0 | 2539 | nsresult rv; |
michael@0 | 2540 | // Local files don't have any host name. We don't want to delete all files in |
michael@0 | 2541 | // history when we get passed an empty string, so force to exact match |
michael@0 | 2542 | if (aHost.IsEmpty()) |
michael@0 | 2543 | aEntireDomain = false; |
michael@0 | 2544 | |
michael@0 | 2545 | // translate "(local files)" to an empty host name |
michael@0 | 2546 | // be sure to use the TitleForDomain to get the localized name |
michael@0 | 2547 | nsCString localFiles; |
michael@0 | 2548 | TitleForDomain(EmptyCString(), localFiles); |
michael@0 | 2549 | nsAutoString host16; |
michael@0 | 2550 | if (!aHost.Equals(localFiles)) |
michael@0 | 2551 | CopyUTF8toUTF16(aHost, host16); |
michael@0 | 2552 | |
michael@0 | 2553 | // nsISupports version of the host string for passing to observers |
michael@0 | 2554 | nsCOMPtr<nsISupportsString> hostSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); |
michael@0 | 2555 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2556 | rv = hostSupports->SetData(host16); |
michael@0 | 2557 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2558 | |
michael@0 | 2559 | // see BindQueryClauseParameters for how this host selection works |
michael@0 | 2560 | nsAutoString revHostDot; |
michael@0 | 2561 | GetReversedHostname(host16, revHostDot); |
michael@0 | 2562 | NS_ASSERTION(revHostDot[revHostDot.Length() - 1] == '.', "Invalid rev. host"); |
michael@0 | 2563 | nsAutoString revHostSlash(revHostDot); |
michael@0 | 2564 | revHostSlash.Truncate(revHostSlash.Length() - 1); |
michael@0 | 2565 | revHostSlash.Append(NS_LITERAL_STRING("/")); |
michael@0 | 2566 | |
michael@0 | 2567 | // build condition string based on host selection type |
michael@0 | 2568 | nsAutoCString conditionString; |
michael@0 | 2569 | if (aEntireDomain) |
michael@0 | 2570 | conditionString.AssignLiteral("rev_host >= ?1 AND rev_host < ?2 "); |
michael@0 | 2571 | else |
michael@0 | 2572 | conditionString.AssignLiteral("rev_host = ?1 "); |
michael@0 | 2573 | |
michael@0 | 2574 | // create statement depending on delete type |
michael@0 | 2575 | nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement( |
michael@0 | 2576 | NS_LITERAL_CSTRING("SELECT id FROM moz_places WHERE ") + conditionString |
michael@0 | 2577 | ); |
michael@0 | 2578 | NS_ENSURE_STATE(statement); |
michael@0 | 2579 | mozStorageStatementScoper scoper(statement); |
michael@0 | 2580 | |
michael@0 | 2581 | rv = statement->BindStringByIndex(0, revHostDot); |
michael@0 | 2582 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2583 | if (aEntireDomain) { |
michael@0 | 2584 | rv = statement->BindStringByIndex(1, revHostSlash); |
michael@0 | 2585 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2586 | } |
michael@0 | 2587 | |
michael@0 | 2588 | nsCString hostPlaceIds; |
michael@0 | 2589 | bool hasMore = false; |
michael@0 | 2590 | while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { |
michael@0 | 2591 | if (!hostPlaceIds.IsEmpty()) |
michael@0 | 2592 | hostPlaceIds.AppendLiteral(","); |
michael@0 | 2593 | int64_t placeId; |
michael@0 | 2594 | rv = statement->GetInt64(0, &placeId); |
michael@0 | 2595 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2596 | hostPlaceIds.AppendInt(placeId); |
michael@0 | 2597 | } |
michael@0 | 2598 | |
michael@0 | 2599 | // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
michael@0 | 2600 | UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
michael@0 | 2601 | |
michael@0 | 2602 | rv = RemovePagesInternal(hostPlaceIds); |
michael@0 | 2603 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2604 | |
michael@0 | 2605 | // Clear the registered embed visits. |
michael@0 | 2606 | clearEmbedVisits(); |
michael@0 | 2607 | |
michael@0 | 2608 | return NS_OK; |
michael@0 | 2609 | } |
michael@0 | 2610 | |
michael@0 | 2611 | |
michael@0 | 2612 | // nsNavHistory::RemovePagesByTimeframe |
michael@0 | 2613 | // |
michael@0 | 2614 | // This function will delete all history information about |
michael@0 | 2615 | // pages for a given timeframe. |
michael@0 | 2616 | // Limits are included: aBeginTime <= timeframe <= aEndTime |
michael@0 | 2617 | // |
michael@0 | 2618 | // This method sends onBeginUpdateBatch/onEndUpdateBatch to observers |
michael@0 | 2619 | |
michael@0 | 2620 | NS_IMETHODIMP |
michael@0 | 2621 | nsNavHistory::RemovePagesByTimeframe(PRTime aBeginTime, PRTime aEndTime) |
michael@0 | 2622 | { |
michael@0 | 2623 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2624 | |
michael@0 | 2625 | nsresult rv; |
michael@0 | 2626 | // build a list of place ids to delete |
michael@0 | 2627 | nsCString deletePlaceIdsQueryString; |
michael@0 | 2628 | |
michael@0 | 2629 | // we only need to know if a place has a visit into the given timeframe |
michael@0 | 2630 | // this query is faster than actually selecting in moz_historyvisits |
michael@0 | 2631 | nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( |
michael@0 | 2632 | "SELECT h.id FROM moz_places h WHERE " |
michael@0 | 2633 | "EXISTS " |
michael@0 | 2634 | "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id " |
michael@0 | 2635 | "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)" |
michael@0 | 2636 | ); |
michael@0 | 2637 | NS_ENSURE_STATE(selectByTime); |
michael@0 | 2638 | mozStorageStatementScoper selectByTimeScoper(selectByTime); |
michael@0 | 2639 | |
michael@0 | 2640 | rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
michael@0 | 2641 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2642 | rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
michael@0 | 2643 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2644 | |
michael@0 | 2645 | bool hasMore = false; |
michael@0 | 2646 | while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { |
michael@0 | 2647 | int64_t placeId; |
michael@0 | 2648 | rv = selectByTime->GetInt64(0, &placeId); |
michael@0 | 2649 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2650 | if (placeId != 0) { |
michael@0 | 2651 | if (!deletePlaceIdsQueryString.IsEmpty()) |
michael@0 | 2652 | deletePlaceIdsQueryString.AppendLiteral(","); |
michael@0 | 2653 | deletePlaceIdsQueryString.AppendInt(placeId); |
michael@0 | 2654 | } |
michael@0 | 2655 | } |
michael@0 | 2656 | |
michael@0 | 2657 | // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
michael@0 | 2658 | UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
michael@0 | 2659 | |
michael@0 | 2660 | rv = RemovePagesInternal(deletePlaceIdsQueryString); |
michael@0 | 2661 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2662 | |
michael@0 | 2663 | // Clear the registered embed visits. |
michael@0 | 2664 | clearEmbedVisits(); |
michael@0 | 2665 | |
michael@0 | 2666 | return NS_OK; |
michael@0 | 2667 | } |
michael@0 | 2668 | |
michael@0 | 2669 | |
michael@0 | 2670 | /** |
michael@0 | 2671 | * Removes all visits in a given timeframe. Limits are included: |
michael@0 | 2672 | * aBeginTime <= timeframe <= aEndTime. Any place that becomes unvisited |
michael@0 | 2673 | * as a result will also be deleted. |
michael@0 | 2674 | * |
michael@0 | 2675 | * Note that removal is performed in batch, so observers will not be |
michael@0 | 2676 | * notified of individual places that are deleted. Instead they will be |
michael@0 | 2677 | * notified onBeginUpdateBatch and onEndUpdateBatch. |
michael@0 | 2678 | * |
michael@0 | 2679 | * @param aBeginTime |
michael@0 | 2680 | * The start of the timeframe, inclusive |
michael@0 | 2681 | * @param aEndTime |
michael@0 | 2682 | * The end of the timeframe, inclusive |
michael@0 | 2683 | */ |
michael@0 | 2684 | NS_IMETHODIMP |
michael@0 | 2685 | nsNavHistory::RemoveVisitsByTimeframe(PRTime aBeginTime, PRTime aEndTime) |
michael@0 | 2686 | { |
michael@0 | 2687 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2688 | |
michael@0 | 2689 | nsresult rv; |
michael@0 | 2690 | |
michael@0 | 2691 | // Build a list of place IDs whose visits fall entirely within the timespan. |
michael@0 | 2692 | // These places will be deleted by the call to CleanupPlacesOnVisitsDelete |
michael@0 | 2693 | // below. |
michael@0 | 2694 | nsCString deletePlaceIdsQueryString; |
michael@0 | 2695 | { |
michael@0 | 2696 | nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( |
michael@0 | 2697 | "SELECT place_id " |
michael@0 | 2698 | "FROM moz_historyvisits " |
michael@0 | 2699 | "WHERE :from_date <= visit_date AND visit_date <= :to_date " |
michael@0 | 2700 | "EXCEPT " |
michael@0 | 2701 | "SELECT place_id " |
michael@0 | 2702 | "FROM moz_historyvisits " |
michael@0 | 2703 | "WHERE visit_date < :from_date OR :to_date < visit_date" |
michael@0 | 2704 | ); |
michael@0 | 2705 | NS_ENSURE_STATE(selectByTime); |
michael@0 | 2706 | mozStorageStatementScoper selectByTimeScoper(selectByTime); |
michael@0 | 2707 | rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
michael@0 | 2708 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2709 | rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
michael@0 | 2710 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2711 | |
michael@0 | 2712 | bool hasMore = false; |
michael@0 | 2713 | while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { |
michael@0 | 2714 | int64_t placeId; |
michael@0 | 2715 | rv = selectByTime->GetInt64(0, &placeId); |
michael@0 | 2716 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2717 | // placeId should not be <= 0, but be defensive. |
michael@0 | 2718 | if (placeId > 0) { |
michael@0 | 2719 | if (!deletePlaceIdsQueryString.IsEmpty()) |
michael@0 | 2720 | deletePlaceIdsQueryString.AppendLiteral(","); |
michael@0 | 2721 | deletePlaceIdsQueryString.AppendInt(placeId); |
michael@0 | 2722 | } |
michael@0 | 2723 | } |
michael@0 | 2724 | } |
michael@0 | 2725 | |
michael@0 | 2726 | // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
michael@0 | 2727 | UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
michael@0 | 2728 | |
michael@0 | 2729 | mozStorageTransaction transaction(mDB->MainConn(), false); |
michael@0 | 2730 | |
michael@0 | 2731 | // Delete all visits within the timeframe. |
michael@0 | 2732 | nsCOMPtr<mozIStorageStatement> deleteVisitsStmt = mDB->GetStatement( |
michael@0 | 2733 | "DELETE FROM moz_historyvisits " |
michael@0 | 2734 | "WHERE :from_date <= visit_date AND visit_date <= :to_date" |
michael@0 | 2735 | ); |
michael@0 | 2736 | NS_ENSURE_STATE(deleteVisitsStmt); |
michael@0 | 2737 | mozStorageStatementScoper deletevisitsScoper(deleteVisitsStmt); |
michael@0 | 2738 | |
michael@0 | 2739 | rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
michael@0 | 2740 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2741 | rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
michael@0 | 2742 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2743 | rv = deleteVisitsStmt->Execute(); |
michael@0 | 2744 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2745 | |
michael@0 | 2746 | rv = CleanupPlacesOnVisitsDelete(deletePlaceIdsQueryString); |
michael@0 | 2747 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2748 | |
michael@0 | 2749 | rv = transaction.Commit(); |
michael@0 | 2750 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2751 | |
michael@0 | 2752 | // Clear the registered embed visits. |
michael@0 | 2753 | clearEmbedVisits(); |
michael@0 | 2754 | |
michael@0 | 2755 | // Invalidate the cached value for whether there's history or not. |
michael@0 | 2756 | mDaysOfHistory = -1; |
michael@0 | 2757 | |
michael@0 | 2758 | return NS_OK; |
michael@0 | 2759 | } |
michael@0 | 2760 | |
michael@0 | 2761 | |
michael@0 | 2762 | // nsNavHistory::RemoveAllPages |
michael@0 | 2763 | // |
michael@0 | 2764 | // This function is used to clear history. |
michael@0 | 2765 | |
michael@0 | 2766 | NS_IMETHODIMP |
michael@0 | 2767 | nsNavHistory::RemoveAllPages() |
michael@0 | 2768 | { |
michael@0 | 2769 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2770 | |
michael@0 | 2771 | nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
michael@0 | 2772 | "DELETE FROM moz_historyvisits" |
michael@0 | 2773 | )); |
michael@0 | 2774 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2775 | |
michael@0 | 2776 | // Clear the registered embed visits. |
michael@0 | 2777 | clearEmbedVisits(); |
michael@0 | 2778 | |
michael@0 | 2779 | // Update the cached value for whether there's history or not. |
michael@0 | 2780 | mDaysOfHistory = 0; |
michael@0 | 2781 | |
michael@0 | 2782 | // Expiration will take care of orphans. |
michael@0 | 2783 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 2784 | nsINavHistoryObserver, OnClearHistory()); |
michael@0 | 2785 | |
michael@0 | 2786 | // Invalidate frecencies for the remaining places. This must happen |
michael@0 | 2787 | // after the notification to ensure it runs enqueued to expiration. |
michael@0 | 2788 | rv = invalidateFrecencies(EmptyCString()); |
michael@0 | 2789 | NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to fix invalid frecencies"); |
michael@0 | 2790 | |
michael@0 | 2791 | return NS_OK; |
michael@0 | 2792 | } |
michael@0 | 2793 | |
michael@0 | 2794 | |
michael@0 | 2795 | // Call this method before visiting a URL in order to help determine the |
michael@0 | 2796 | // transition type of the visit. |
michael@0 | 2797 | // |
michael@0 | 2798 | // @see MarkPageAsFollowedBookmark |
michael@0 | 2799 | |
michael@0 | 2800 | NS_IMETHODIMP |
michael@0 | 2801 | nsNavHistory::MarkPageAsTyped(nsIURI *aURI) |
michael@0 | 2802 | { |
michael@0 | 2803 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2804 | NS_ENSURE_ARG(aURI); |
michael@0 | 2805 | |
michael@0 | 2806 | // don't add when history is disabled |
michael@0 | 2807 | if (IsHistoryDisabled()) |
michael@0 | 2808 | return NS_OK; |
michael@0 | 2809 | |
michael@0 | 2810 | nsAutoCString uriString; |
michael@0 | 2811 | nsresult rv = aURI->GetSpec(uriString); |
michael@0 | 2812 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2813 | |
michael@0 | 2814 | // if URL is already in the typed queue, then we need to remove the old one |
michael@0 | 2815 | int64_t unusedEventTime; |
michael@0 | 2816 | if (mRecentTyped.Get(uriString, &unusedEventTime)) |
michael@0 | 2817 | mRecentTyped.Remove(uriString); |
michael@0 | 2818 | |
michael@0 | 2819 | if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
michael@0 | 2820 | ExpireNonrecentEvents(&mRecentTyped); |
michael@0 | 2821 | |
michael@0 | 2822 | mRecentTyped.Put(uriString, GetNow()); |
michael@0 | 2823 | return NS_OK; |
michael@0 | 2824 | } |
michael@0 | 2825 | |
michael@0 | 2826 | |
michael@0 | 2827 | // Call this method before visiting a URL in order to help determine the |
michael@0 | 2828 | // transition type of the visit. |
michael@0 | 2829 | // |
michael@0 | 2830 | // @see MarkPageAsTyped |
michael@0 | 2831 | |
michael@0 | 2832 | NS_IMETHODIMP |
michael@0 | 2833 | nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI) |
michael@0 | 2834 | { |
michael@0 | 2835 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2836 | NS_ENSURE_ARG(aURI); |
michael@0 | 2837 | |
michael@0 | 2838 | // don't add when history is disabled |
michael@0 | 2839 | if (IsHistoryDisabled()) |
michael@0 | 2840 | return NS_OK; |
michael@0 | 2841 | |
michael@0 | 2842 | nsAutoCString uriString; |
michael@0 | 2843 | nsresult rv = aURI->GetSpec(uriString); |
michael@0 | 2844 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2845 | |
michael@0 | 2846 | // if URL is already in the links queue, then we need to remove the old one |
michael@0 | 2847 | int64_t unusedEventTime; |
michael@0 | 2848 | if (mRecentLink.Get(uriString, &unusedEventTime)) |
michael@0 | 2849 | mRecentLink.Remove(uriString); |
michael@0 | 2850 | |
michael@0 | 2851 | if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
michael@0 | 2852 | ExpireNonrecentEvents(&mRecentLink); |
michael@0 | 2853 | |
michael@0 | 2854 | mRecentLink.Put(uriString, GetNow()); |
michael@0 | 2855 | return NS_OK; |
michael@0 | 2856 | } |
michael@0 | 2857 | |
michael@0 | 2858 | |
michael@0 | 2859 | // nsNavHistory::SetCharsetForURI |
michael@0 | 2860 | // |
michael@0 | 2861 | // Sets the character-set for a URI. |
michael@0 | 2862 | // If aCharset is empty remove character-set annotation for aURI. |
michael@0 | 2863 | |
michael@0 | 2864 | NS_IMETHODIMP |
michael@0 | 2865 | nsNavHistory::SetCharsetForURI(nsIURI* aURI, |
michael@0 | 2866 | const nsAString& aCharset) |
michael@0 | 2867 | { |
michael@0 | 2868 | PLACES_WARN_DEPRECATED(); |
michael@0 | 2869 | |
michael@0 | 2870 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2871 | NS_ENSURE_ARG(aURI); |
michael@0 | 2872 | |
michael@0 | 2873 | nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); |
michael@0 | 2874 | NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 2875 | |
michael@0 | 2876 | if (aCharset.IsEmpty()) { |
michael@0 | 2877 | // remove the current page character-set annotation |
michael@0 | 2878 | nsresult rv = annosvc->RemovePageAnnotation(aURI, CHARSET_ANNO); |
michael@0 | 2879 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2880 | } |
michael@0 | 2881 | else { |
michael@0 | 2882 | // Set page character-set annotation, silently overwrite if already exists |
michael@0 | 2883 | nsresult rv = annosvc->SetPageAnnotationString(aURI, CHARSET_ANNO, |
michael@0 | 2884 | aCharset, 0, |
michael@0 | 2885 | nsAnnotationService::EXPIRE_NEVER); |
michael@0 | 2886 | if (rv == NS_ERROR_INVALID_ARG) { |
michael@0 | 2887 | // We don't have this page. Silently fail. |
michael@0 | 2888 | return NS_OK; |
michael@0 | 2889 | } |
michael@0 | 2890 | else if (NS_FAILED(rv)) |
michael@0 | 2891 | return rv; |
michael@0 | 2892 | } |
michael@0 | 2893 | |
michael@0 | 2894 | return NS_OK; |
michael@0 | 2895 | } |
michael@0 | 2896 | |
michael@0 | 2897 | |
michael@0 | 2898 | // nsNavHistory::GetCharsetForURI |
michael@0 | 2899 | // |
michael@0 | 2900 | // Get the last saved character-set for a URI. |
michael@0 | 2901 | |
michael@0 | 2902 | NS_IMETHODIMP |
michael@0 | 2903 | nsNavHistory::GetCharsetForURI(nsIURI* aURI, |
michael@0 | 2904 | nsAString& aCharset) |
michael@0 | 2905 | { |
michael@0 | 2906 | PLACES_WARN_DEPRECATED(); |
michael@0 | 2907 | |
michael@0 | 2908 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2909 | NS_ENSURE_ARG(aURI); |
michael@0 | 2910 | |
michael@0 | 2911 | nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); |
michael@0 | 2912 | NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 2913 | |
michael@0 | 2914 | nsAutoString charset; |
michael@0 | 2915 | nsresult rv = annosvc->GetPageAnnotationString(aURI, CHARSET_ANNO, aCharset); |
michael@0 | 2916 | if (NS_FAILED(rv)) { |
michael@0 | 2917 | // be sure to return an empty string if character-set is not found |
michael@0 | 2918 | aCharset.Truncate(); |
michael@0 | 2919 | } |
michael@0 | 2920 | return NS_OK; |
michael@0 | 2921 | } |
michael@0 | 2922 | |
michael@0 | 2923 | |
michael@0 | 2924 | NS_IMETHODIMP |
michael@0 | 2925 | nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle) |
michael@0 | 2926 | { |
michael@0 | 2927 | PLACES_WARN_DEPRECATED(); |
michael@0 | 2928 | |
michael@0 | 2929 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 2930 | NS_ENSURE_ARG(aURI); |
michael@0 | 2931 | |
michael@0 | 2932 | aTitle.Truncate(0); |
michael@0 | 2933 | |
michael@0 | 2934 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
michael@0 | 2935 | "SELECT id, url, title, rev_host, visit_count, guid " |
michael@0 | 2936 | "FROM moz_places " |
michael@0 | 2937 | "WHERE url = :page_url " |
michael@0 | 2938 | ); |
michael@0 | 2939 | NS_ENSURE_STATE(stmt); |
michael@0 | 2940 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 2941 | |
michael@0 | 2942 | nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
michael@0 | 2943 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2944 | |
michael@0 | 2945 | bool hasResults = false; |
michael@0 | 2946 | rv = stmt->ExecuteStep(&hasResults); |
michael@0 | 2947 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2948 | |
michael@0 | 2949 | if (!hasResults) { |
michael@0 | 2950 | aTitle.SetIsVoid(true); |
michael@0 | 2951 | return NS_OK; // Not found, return a void string. |
michael@0 | 2952 | } |
michael@0 | 2953 | |
michael@0 | 2954 | rv = stmt->GetString(2, aTitle); |
michael@0 | 2955 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2956 | |
michael@0 | 2957 | return NS_OK; |
michael@0 | 2958 | } |
michael@0 | 2959 | |
michael@0 | 2960 | |
michael@0 | 2961 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 2962 | //// mozIStorageVacuumParticipant |
michael@0 | 2963 | |
michael@0 | 2964 | NS_IMETHODIMP |
michael@0 | 2965 | nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection) |
michael@0 | 2966 | { |
michael@0 | 2967 | return GetDBConnection(_DBConnection); |
michael@0 | 2968 | } |
michael@0 | 2969 | |
michael@0 | 2970 | |
michael@0 | 2971 | NS_IMETHODIMP |
michael@0 | 2972 | nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize) |
michael@0 | 2973 | { |
michael@0 | 2974 | NS_ENSURE_STATE(mDB); |
michael@0 | 2975 | NS_ENSURE_STATE(mDB->MainConn()); |
michael@0 | 2976 | return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize); |
michael@0 | 2977 | } |
michael@0 | 2978 | |
michael@0 | 2979 | |
michael@0 | 2980 | NS_IMETHODIMP |
michael@0 | 2981 | nsNavHistory::OnBeginVacuum(bool* _vacuumGranted) |
michael@0 | 2982 | { |
michael@0 | 2983 | // TODO: Check if we have to deny the vacuum in some heavy-load case. |
michael@0 | 2984 | // We could maybe want to do that during batches? |
michael@0 | 2985 | *_vacuumGranted = true; |
michael@0 | 2986 | return NS_OK; |
michael@0 | 2987 | } |
michael@0 | 2988 | |
michael@0 | 2989 | |
michael@0 | 2990 | NS_IMETHODIMP |
michael@0 | 2991 | nsNavHistory::OnEndVacuum(bool aSucceeded) |
michael@0 | 2992 | { |
michael@0 | 2993 | NS_WARN_IF_FALSE(aSucceeded, "Places.sqlite vacuum failed."); |
michael@0 | 2994 | return NS_OK; |
michael@0 | 2995 | } |
michael@0 | 2996 | |
michael@0 | 2997 | |
michael@0 | 2998 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 2999 | //// nsPIPlacesDatabase |
michael@0 | 3000 | |
michael@0 | 3001 | NS_IMETHODIMP |
michael@0 | 3002 | nsNavHistory::GetDBConnection(mozIStorageConnection **_DBConnection) |
michael@0 | 3003 | { |
michael@0 | 3004 | NS_ENSURE_ARG_POINTER(_DBConnection); |
michael@0 | 3005 | nsRefPtr<mozIStorageConnection> connection = mDB->MainConn(); |
michael@0 | 3006 | connection.forget(_DBConnection); |
michael@0 | 3007 | |
michael@0 | 3008 | return NS_OK; |
michael@0 | 3009 | } |
michael@0 | 3010 | |
michael@0 | 3011 | NS_IMETHODIMP |
michael@0 | 3012 | nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries, |
michael@0 | 3013 | uint32_t aQueryCount, |
michael@0 | 3014 | nsINavHistoryQueryOptions* aOptions, |
michael@0 | 3015 | mozIStorageStatementCallback* aCallback, |
michael@0 | 3016 | mozIStoragePendingStatement** _stmt) |
michael@0 | 3017 | { |
michael@0 | 3018 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 3019 | NS_ENSURE_ARG(aQueries); |
michael@0 | 3020 | NS_ENSURE_ARG(aOptions); |
michael@0 | 3021 | NS_ENSURE_ARG(aCallback); |
michael@0 | 3022 | NS_ENSURE_ARG_POINTER(_stmt); |
michael@0 | 3023 | |
michael@0 | 3024 | nsCOMArray<nsNavHistoryQuery> queries; |
michael@0 | 3025 | for (uint32_t i = 0; i < aQueryCount; i ++) { |
michael@0 | 3026 | nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i]); |
michael@0 | 3027 | NS_ENSURE_STATE(query); |
michael@0 | 3028 | queries.AppendObject(query); |
michael@0 | 3029 | } |
michael@0 | 3030 | NS_ENSURE_ARG_MIN(queries.Count(), 1); |
michael@0 | 3031 | |
michael@0 | 3032 | nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); |
michael@0 | 3033 | NS_ENSURE_ARG(options); |
michael@0 | 3034 | |
michael@0 | 3035 | nsCString queryString; |
michael@0 | 3036 | bool paramsPresent = false; |
michael@0 | 3037 | nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); |
michael@0 | 3038 | nsresult rv = ConstructQueryString(queries, options, queryString, |
michael@0 | 3039 | paramsPresent, addParams); |
michael@0 | 3040 | NS_ENSURE_SUCCESS(rv,rv); |
michael@0 | 3041 | |
michael@0 | 3042 | nsCOMPtr<mozIStorageAsyncStatement> statement = |
michael@0 | 3043 | mDB->GetAsyncStatement(queryString); |
michael@0 | 3044 | NS_ENSURE_STATE(statement); |
michael@0 | 3045 | |
michael@0 | 3046 | #ifdef DEBUG |
michael@0 | 3047 | if (NS_FAILED(rv)) { |
michael@0 | 3048 | nsAutoCString lastErrorString; |
michael@0 | 3049 | (void)mDB->MainConn()->GetLastErrorString(lastErrorString); |
michael@0 | 3050 | int32_t lastError = 0; |
michael@0 | 3051 | (void)mDB->MainConn()->GetLastError(&lastError); |
michael@0 | 3052 | printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", |
michael@0 | 3053 | queryString.get(), lastError, lastErrorString.get()); |
michael@0 | 3054 | } |
michael@0 | 3055 | #endif |
michael@0 | 3056 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3057 | |
michael@0 | 3058 | if (paramsPresent) { |
michael@0 | 3059 | // bind parameters |
michael@0 | 3060 | int32_t i; |
michael@0 | 3061 | for (i = 0; i < queries.Count(); i++) { |
michael@0 | 3062 | rv = BindQueryClauseParameters(statement, i, queries[i], options); |
michael@0 | 3063 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3064 | } |
michael@0 | 3065 | } |
michael@0 | 3066 | addParams.EnumerateRead(BindAdditionalParameter, statement.get()); |
michael@0 | 3067 | |
michael@0 | 3068 | rv = statement->ExecuteAsync(aCallback, _stmt); |
michael@0 | 3069 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3070 | |
michael@0 | 3071 | return NS_OK; |
michael@0 | 3072 | } |
michael@0 | 3073 | |
michael@0 | 3074 | |
michael@0 | 3075 | // nsPIPlacesHistoryListenersNotifier ****************************************** |
michael@0 | 3076 | |
michael@0 | 3077 | NS_IMETHODIMP |
michael@0 | 3078 | nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime, |
michael@0 | 3079 | bool aWholeEntry, const nsACString& aGUID, |
michael@0 | 3080 | uint16_t aReason, uint32_t aTransitionType) |
michael@0 | 3081 | { |
michael@0 | 3082 | // Invalidate the cached value for whether there's history or not. |
michael@0 | 3083 | mDaysOfHistory = -1; |
michael@0 | 3084 | |
michael@0 | 3085 | MOZ_ASSERT(!aGUID.IsEmpty()); |
michael@0 | 3086 | if (aWholeEntry) { |
michael@0 | 3087 | // Notify our observers that the page has been removed. |
michael@0 | 3088 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 3089 | nsINavHistoryObserver, OnDeleteURI(aURI, aGUID, aReason)); |
michael@0 | 3090 | } |
michael@0 | 3091 | else { |
michael@0 | 3092 | // Notify our observers that some visits for the page have been removed. |
michael@0 | 3093 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 3094 | nsINavHistoryObserver, |
michael@0 | 3095 | OnDeleteVisits(aURI, aVisitTime, aGUID, aReason, |
michael@0 | 3096 | aTransitionType)); |
michael@0 | 3097 | } |
michael@0 | 3098 | |
michael@0 | 3099 | return NS_OK; |
michael@0 | 3100 | } |
michael@0 | 3101 | |
michael@0 | 3102 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 3103 | //// nsIObserver |
michael@0 | 3104 | |
michael@0 | 3105 | NS_IMETHODIMP |
michael@0 | 3106 | nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic, |
michael@0 | 3107 | const char16_t *aData) |
michael@0 | 3108 | { |
michael@0 | 3109 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 3110 | |
michael@0 | 3111 | if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 || |
michael@0 | 3112 | strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0) { |
michael@0 | 3113 | // These notifications are used by tests to simulate a Places shutdown. |
michael@0 | 3114 | // They should just be forwarded to the Database handle. |
michael@0 | 3115 | mDB->Observe(aSubject, aTopic, aData); |
michael@0 | 3116 | } |
michael@0 | 3117 | |
michael@0 | 3118 | else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) { |
michael@0 | 3119 | // Don't even try to notify observers from this point on, the category |
michael@0 | 3120 | // cache would init services that could try to use our APIs. |
michael@0 | 3121 | mCanNotify = false; |
michael@0 | 3122 | } |
michael@0 | 3123 | |
michael@0 | 3124 | #ifdef MOZ_XUL |
michael@0 | 3125 | else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) { |
michael@0 | 3126 | nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject); |
michael@0 | 3127 | if (!input) |
michael@0 | 3128 | return NS_OK; |
michael@0 | 3129 | |
michael@0 | 3130 | // If the source is a private window, don't add any input history. |
michael@0 | 3131 | bool isPrivate; |
michael@0 | 3132 | nsresult rv = input->GetInPrivateContext(&isPrivate); |
michael@0 | 3133 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3134 | if (isPrivate) |
michael@0 | 3135 | return NS_OK; |
michael@0 | 3136 | |
michael@0 | 3137 | nsCOMPtr<nsIAutoCompletePopup> popup; |
michael@0 | 3138 | input->GetPopup(getter_AddRefs(popup)); |
michael@0 | 3139 | if (!popup) |
michael@0 | 3140 | return NS_OK; |
michael@0 | 3141 | |
michael@0 | 3142 | nsCOMPtr<nsIAutoCompleteController> controller; |
michael@0 | 3143 | input->GetController(getter_AddRefs(controller)); |
michael@0 | 3144 | if (!controller) |
michael@0 | 3145 | return NS_OK; |
michael@0 | 3146 | |
michael@0 | 3147 | // Don't bother if the popup is closed |
michael@0 | 3148 | bool open; |
michael@0 | 3149 | rv = popup->GetPopupOpen(&open); |
michael@0 | 3150 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3151 | if (!open) |
michael@0 | 3152 | return NS_OK; |
michael@0 | 3153 | |
michael@0 | 3154 | // Ignore if nothing selected from the popup |
michael@0 | 3155 | int32_t selectedIndex; |
michael@0 | 3156 | rv = popup->GetSelectedIndex(&selectedIndex); |
michael@0 | 3157 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3158 | if (selectedIndex == -1) |
michael@0 | 3159 | return NS_OK; |
michael@0 | 3160 | |
michael@0 | 3161 | rv = AutoCompleteFeedback(selectedIndex, controller); |
michael@0 | 3162 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3163 | } |
michael@0 | 3164 | |
michael@0 | 3165 | #endif |
michael@0 | 3166 | else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) { |
michael@0 | 3167 | LoadPrefs(); |
michael@0 | 3168 | } |
michael@0 | 3169 | |
michael@0 | 3170 | else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) { |
michael@0 | 3171 | (void)DecayFrecency(); |
michael@0 | 3172 | } |
michael@0 | 3173 | |
michael@0 | 3174 | return NS_OK; |
michael@0 | 3175 | } |
michael@0 | 3176 | |
michael@0 | 3177 | |
michael@0 | 3178 | namespace { |
michael@0 | 3179 | |
michael@0 | 3180 | class DecayFrecencyCallback : public AsyncStatementTelemetryTimer |
michael@0 | 3181 | { |
michael@0 | 3182 | public: |
michael@0 | 3183 | DecayFrecencyCallback() |
michael@0 | 3184 | : AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS) |
michael@0 | 3185 | { |
michael@0 | 3186 | } |
michael@0 | 3187 | |
michael@0 | 3188 | NS_IMETHOD HandleCompletion(uint16_t aReason) |
michael@0 | 3189 | { |
michael@0 | 3190 | (void)AsyncStatementTelemetryTimer::HandleCompletion(aReason); |
michael@0 | 3191 | if (aReason == REASON_FINISHED) { |
michael@0 | 3192 | nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
michael@0 | 3193 | NS_ENSURE_STATE(navHistory); |
michael@0 | 3194 | navHistory->NotifyManyFrecenciesChanged(); |
michael@0 | 3195 | } |
michael@0 | 3196 | return NS_OK; |
michael@0 | 3197 | } |
michael@0 | 3198 | }; |
michael@0 | 3199 | |
michael@0 | 3200 | } // anonymous namespace |
michael@0 | 3201 | |
michael@0 | 3202 | nsresult |
michael@0 | 3203 | nsNavHistory::DecayFrecency() |
michael@0 | 3204 | { |
michael@0 | 3205 | nsresult rv = FixInvalidFrecencies(); |
michael@0 | 3206 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3207 | |
michael@0 | 3208 | // Globally decay places frecency rankings to estimate reduced frecency |
michael@0 | 3209 | // values of pages that haven't been visited for a while, i.e., they do |
michael@0 | 3210 | // not get an updated frecency. A scaling factor of .975 results in .5 the |
michael@0 | 3211 | // original value after 28 days. |
michael@0 | 3212 | // When changing the scaling factor, ensure that the barrier in |
michael@0 | 3213 | // moz_places_afterupdate_frecency_trigger still ignores these changes. |
michael@0 | 3214 | nsCOMPtr<mozIStorageAsyncStatement> decayFrecency = mDB->GetAsyncStatement( |
michael@0 | 3215 | "UPDATE moz_places SET frecency = ROUND(frecency * .975) " |
michael@0 | 3216 | "WHERE frecency > 0" |
michael@0 | 3217 | ); |
michael@0 | 3218 | NS_ENSURE_STATE(decayFrecency); |
michael@0 | 3219 | |
michael@0 | 3220 | // Decay potentially unused adaptive entries (e.g. those that are at 1) |
michael@0 | 3221 | // to allow better chances for new entries that will start at 1. |
michael@0 | 3222 | nsCOMPtr<mozIStorageAsyncStatement> decayAdaptive = mDB->GetAsyncStatement( |
michael@0 | 3223 | "UPDATE moz_inputhistory SET use_count = use_count * .975" |
michael@0 | 3224 | ); |
michael@0 | 3225 | NS_ENSURE_STATE(decayAdaptive); |
michael@0 | 3226 | |
michael@0 | 3227 | // Delete any adaptive entries that won't help in ordering anymore. |
michael@0 | 3228 | nsCOMPtr<mozIStorageAsyncStatement> deleteAdaptive = mDB->GetAsyncStatement( |
michael@0 | 3229 | "DELETE FROM moz_inputhistory WHERE use_count < .01" |
michael@0 | 3230 | ); |
michael@0 | 3231 | NS_ENSURE_STATE(deleteAdaptive); |
michael@0 | 3232 | |
michael@0 | 3233 | mozIStorageBaseStatement *stmts[] = { |
michael@0 | 3234 | decayFrecency.get(), |
michael@0 | 3235 | decayAdaptive.get(), |
michael@0 | 3236 | deleteAdaptive.get() |
michael@0 | 3237 | }; |
michael@0 | 3238 | nsCOMPtr<mozIStoragePendingStatement> ps; |
michael@0 | 3239 | nsRefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback(); |
michael@0 | 3240 | rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, |
michael@0 | 3241 | getter_AddRefs(ps)); |
michael@0 | 3242 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3243 | |
michael@0 | 3244 | return NS_OK; |
michael@0 | 3245 | } |
michael@0 | 3246 | |
michael@0 | 3247 | |
michael@0 | 3248 | // Query stuff ***************************************************************** |
michael@0 | 3249 | |
michael@0 | 3250 | // Helper class for QueryToSelectClause |
michael@0 | 3251 | // |
michael@0 | 3252 | // This class helps to build part of the WHERE clause. It supports |
michael@0 | 3253 | // multiple queries by appending the query index to the parameter name. |
michael@0 | 3254 | // For the query with index 0 the parameter name is not altered what |
michael@0 | 3255 | // allows using this parameter in other situations (see SelectAsSite). |
michael@0 | 3256 | |
michael@0 | 3257 | class ConditionBuilder |
michael@0 | 3258 | { |
michael@0 | 3259 | public: |
michael@0 | 3260 | |
michael@0 | 3261 | ConditionBuilder(int32_t aQueryIndex): mQueryIndex(aQueryIndex) |
michael@0 | 3262 | { } |
michael@0 | 3263 | |
michael@0 | 3264 | ConditionBuilder& Condition(const char* aStr) |
michael@0 | 3265 | { |
michael@0 | 3266 | if (!mClause.IsEmpty()) |
michael@0 | 3267 | mClause.AppendLiteral(" AND "); |
michael@0 | 3268 | Str(aStr); |
michael@0 | 3269 | return *this; |
michael@0 | 3270 | } |
michael@0 | 3271 | |
michael@0 | 3272 | ConditionBuilder& Str(const char* aStr) |
michael@0 | 3273 | { |
michael@0 | 3274 | mClause.Append(' '); |
michael@0 | 3275 | mClause.Append(aStr); |
michael@0 | 3276 | mClause.Append(' '); |
michael@0 | 3277 | return *this; |
michael@0 | 3278 | } |
michael@0 | 3279 | |
michael@0 | 3280 | ConditionBuilder& Param(const char* aParam) |
michael@0 | 3281 | { |
michael@0 | 3282 | mClause.Append(' '); |
michael@0 | 3283 | if (!mQueryIndex) |
michael@0 | 3284 | mClause.Append(aParam); |
michael@0 | 3285 | else |
michael@0 | 3286 | mClause += nsPrintfCString("%s%d", aParam, mQueryIndex); |
michael@0 | 3287 | |
michael@0 | 3288 | mClause.Append(' '); |
michael@0 | 3289 | return *this; |
michael@0 | 3290 | } |
michael@0 | 3291 | |
michael@0 | 3292 | void GetClauseString(nsCString& aResult) |
michael@0 | 3293 | { |
michael@0 | 3294 | aResult = mClause; |
michael@0 | 3295 | } |
michael@0 | 3296 | |
michael@0 | 3297 | private: |
michael@0 | 3298 | |
michael@0 | 3299 | int32_t mQueryIndex; |
michael@0 | 3300 | nsCString mClause; |
michael@0 | 3301 | }; |
michael@0 | 3302 | |
michael@0 | 3303 | |
michael@0 | 3304 | // nsNavHistory::QueryToSelectClause |
michael@0 | 3305 | // |
michael@0 | 3306 | // THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters |
michael@0 | 3307 | // |
michael@0 | 3308 | // I don't check return values from the query object getters because there's |
michael@0 | 3309 | // no way for those to fail. |
michael@0 | 3310 | |
michael@0 | 3311 | nsresult |
michael@0 | 3312 | nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const |
michael@0 | 3313 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 3314 | int32_t aQueryIndex, |
michael@0 | 3315 | nsCString* aClause) |
michael@0 | 3316 | { |
michael@0 | 3317 | bool hasIt; |
michael@0 | 3318 | bool excludeQueries = aOptions->ExcludeQueries(); |
michael@0 | 3319 | |
michael@0 | 3320 | ConditionBuilder clause(aQueryIndex); |
michael@0 | 3321 | |
michael@0 | 3322 | if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) || |
michael@0 | 3323 | (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) { |
michael@0 | 3324 | clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits " |
michael@0 | 3325 | "WHERE place_id = h.id"); |
michael@0 | 3326 | // begin time |
michael@0 | 3327 | if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) |
michael@0 | 3328 | clause.Condition("visit_date >=").Param(":begin_time"); |
michael@0 | 3329 | // end time |
michael@0 | 3330 | if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) |
michael@0 | 3331 | clause.Condition("visit_date <=").Param(":end_time"); |
michael@0 | 3332 | clause.Str(" LIMIT 1)"); |
michael@0 | 3333 | } |
michael@0 | 3334 | |
michael@0 | 3335 | // search terms |
michael@0 | 3336 | bool hasSearchTerms; |
michael@0 | 3337 | if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasSearchTerms)) && hasSearchTerms) { |
michael@0 | 3338 | // Re-use the autocomplete_match function. Setting the behavior to 0 |
michael@0 | 3339 | // it can match everything and work as a nice case insensitive comparator. |
michael@0 | 3340 | clause.Condition("AUTOCOMPLETE_MATCH(").Param(":search_string") |
michael@0 | 3341 | .Str(", h.url, page_title, tags, ") |
michael@0 | 3342 | .Str(nsPrintfCString("0, 0, 0, 0, %d, 0)", |
michael@0 | 3343 | mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED).get()); |
michael@0 | 3344 | // Serching by terms implicitly exclude queries. |
michael@0 | 3345 | excludeQueries = true; |
michael@0 | 3346 | } |
michael@0 | 3347 | |
michael@0 | 3348 | // min and max visit count |
michael@0 | 3349 | if (aQuery->MinVisits() >= 0) |
michael@0 | 3350 | clause.Condition("h.visit_count >=").Param(":min_visits"); |
michael@0 | 3351 | |
michael@0 | 3352 | if (aQuery->MaxVisits() >= 0) |
michael@0 | 3353 | clause.Condition("h.visit_count <=").Param(":max_visits"); |
michael@0 | 3354 | |
michael@0 | 3355 | // only bookmarked, has no affect on bookmarks-only queries |
michael@0 | 3356 | if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS && |
michael@0 | 3357 | aQuery->OnlyBookmarked()) |
michael@0 | 3358 | clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ") |
michael@0 | 3359 | .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get()) |
michael@0 | 3360 | .Str("AND b.fk = h.id)"); |
michael@0 | 3361 | |
michael@0 | 3362 | // domain |
michael@0 | 3363 | if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { |
michael@0 | 3364 | bool domainIsHost = false; |
michael@0 | 3365 | aQuery->GetDomainIsHost(&domainIsHost); |
michael@0 | 3366 | if (domainIsHost) |
michael@0 | 3367 | clause.Condition("h.rev_host =").Param(":domain_lower"); |
michael@0 | 3368 | else |
michael@0 | 3369 | // see domain setting in BindQueryClauseParameters for why we do this |
michael@0 | 3370 | clause.Condition("h.rev_host >=").Param(":domain_lower") |
michael@0 | 3371 | .Condition("h.rev_host <").Param(":domain_upper"); |
michael@0 | 3372 | } |
michael@0 | 3373 | |
michael@0 | 3374 | // URI |
michael@0 | 3375 | if (NS_SUCCEEDED(aQuery->GetHasUri(&hasIt)) && hasIt) { |
michael@0 | 3376 | if (aQuery->UriIsPrefix()) { |
michael@0 | 3377 | clause.Condition("h.url >= ").Param(":uri") |
michael@0 | 3378 | .Condition("h.url <= ").Param(":uri_upper"); |
michael@0 | 3379 | } |
michael@0 | 3380 | else |
michael@0 | 3381 | clause.Condition("h.url =").Param(":uri"); |
michael@0 | 3382 | } |
michael@0 | 3383 | |
michael@0 | 3384 | // annotation |
michael@0 | 3385 | aQuery->GetHasAnnotation(&hasIt); |
michael@0 | 3386 | if (hasIt) { |
michael@0 | 3387 | clause.Condition(""); |
michael@0 | 3388 | if (aQuery->AnnotationIsNot()) |
michael@0 | 3389 | clause.Str("NOT"); |
michael@0 | 3390 | clause.Str( |
michael@0 | 3391 | "EXISTS " |
michael@0 | 3392 | "(SELECT h.id " |
michael@0 | 3393 | "FROM moz_annos anno " |
michael@0 | 3394 | "JOIN moz_anno_attributes annoname " |
michael@0 | 3395 | "ON anno.anno_attribute_id = annoname.id " |
michael@0 | 3396 | "WHERE anno.place_id = h.id " |
michael@0 | 3397 | "AND annoname.name = ").Param(":anno").Str(")"); |
michael@0 | 3398 | // annotation-based queries don't get the common conditions, so you get |
michael@0 | 3399 | // all URLs with that annotation |
michael@0 | 3400 | } |
michael@0 | 3401 | |
michael@0 | 3402 | // tags |
michael@0 | 3403 | const nsTArray<nsString> &tags = aQuery->Tags(); |
michael@0 | 3404 | if (tags.Length() > 0) { |
michael@0 | 3405 | clause.Condition("h.id"); |
michael@0 | 3406 | if (aQuery->TagsAreNot()) |
michael@0 | 3407 | clause.Str("NOT"); |
michael@0 | 3408 | clause.Str( |
michael@0 | 3409 | "IN " |
michael@0 | 3410 | "(SELECT bms.fk " |
michael@0 | 3411 | "FROM moz_bookmarks bms " |
michael@0 | 3412 | "JOIN moz_bookmarks tags ON bms.parent = tags.id " |
michael@0 | 3413 | "WHERE tags.parent ="). |
michael@0 | 3414 | Param(":tags_folder"). |
michael@0 | 3415 | Str("AND tags.title IN ("); |
michael@0 | 3416 | for (uint32_t i = 0; i < tags.Length(); ++i) { |
michael@0 | 3417 | nsPrintfCString param(":tag%d_", i); |
michael@0 | 3418 | clause.Param(param.get()); |
michael@0 | 3419 | if (i < tags.Length() - 1) |
michael@0 | 3420 | clause.Str(","); |
michael@0 | 3421 | } |
michael@0 | 3422 | clause.Str(")"); |
michael@0 | 3423 | if (!aQuery->TagsAreNot()) |
michael@0 | 3424 | clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count"); |
michael@0 | 3425 | clause.Str(")"); |
michael@0 | 3426 | } |
michael@0 | 3427 | |
michael@0 | 3428 | // transitions |
michael@0 | 3429 | const nsTArray<uint32_t>& transitions = aQuery->Transitions(); |
michael@0 | 3430 | for (uint32_t i = 0; i < transitions.Length(); ++i) { |
michael@0 | 3431 | nsPrintfCString param(":transition%d_", i); |
michael@0 | 3432 | clause.Condition("h.id IN (SELECT place_id FROM moz_historyvisits " |
michael@0 | 3433 | "WHERE visit_type = ") |
michael@0 | 3434 | .Param(param.get()) |
michael@0 | 3435 | .Str(")"); |
michael@0 | 3436 | } |
michael@0 | 3437 | |
michael@0 | 3438 | // folders |
michael@0 | 3439 | const nsTArray<int64_t>& folders = aQuery->Folders(); |
michael@0 | 3440 | if (folders.Length() > 0) { |
michael@0 | 3441 | nsTArray<int64_t> includeFolders; |
michael@0 | 3442 | includeFolders.AppendElements(folders); |
michael@0 | 3443 | |
michael@0 | 3444 | nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 3445 | NS_ENSURE_STATE(bookmarks); |
michael@0 | 3446 | |
michael@0 | 3447 | for (nsTArray<int64_t>::size_type i = 0; i < folders.Length(); ++i) { |
michael@0 | 3448 | nsTArray<int64_t> subFolders; |
michael@0 | 3449 | if (NS_FAILED(bookmarks->GetDescendantFolders(folders[i], subFolders))) |
michael@0 | 3450 | continue; |
michael@0 | 3451 | includeFolders.AppendElements(subFolders); |
michael@0 | 3452 | } |
michael@0 | 3453 | |
michael@0 | 3454 | clause.Condition("b.parent IN("); |
michael@0 | 3455 | for (nsTArray<int64_t>::size_type i = 0; i < includeFolders.Length(); ++i) { |
michael@0 | 3456 | clause.Str(nsPrintfCString("%lld", includeFolders[i]).get()); |
michael@0 | 3457 | if (i < includeFolders.Length() - 1) { |
michael@0 | 3458 | clause.Str(","); |
michael@0 | 3459 | } |
michael@0 | 3460 | } |
michael@0 | 3461 | clause.Str(")"); |
michael@0 | 3462 | } |
michael@0 | 3463 | |
michael@0 | 3464 | if (excludeQueries) { |
michael@0 | 3465 | // Serching by terms implicitly exclude queries. |
michael@0 | 3466 | clause.Condition("NOT h.url BETWEEN 'place:' AND 'place;'"); |
michael@0 | 3467 | } |
michael@0 | 3468 | |
michael@0 | 3469 | clause.GetClauseString(*aClause); |
michael@0 | 3470 | return NS_OK; |
michael@0 | 3471 | } |
michael@0 | 3472 | |
michael@0 | 3473 | |
michael@0 | 3474 | // nsNavHistory::BindQueryClauseParameters |
michael@0 | 3475 | // |
michael@0 | 3476 | // THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause |
michael@0 | 3477 | |
michael@0 | 3478 | nsresult |
michael@0 | 3479 | nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement, |
michael@0 | 3480 | int32_t aQueryIndex, |
michael@0 | 3481 | nsNavHistoryQuery* aQuery, // const |
michael@0 | 3482 | nsNavHistoryQueryOptions* aOptions) |
michael@0 | 3483 | { |
michael@0 | 3484 | nsresult rv; |
michael@0 | 3485 | |
michael@0 | 3486 | bool hasIt; |
michael@0 | 3487 | // Append numbered index to param names, to replace them correctly in |
michael@0 | 3488 | // case of multiple queries. If we have just one query we don't change the |
michael@0 | 3489 | // param name though. |
michael@0 | 3490 | nsAutoCString qIndex; |
michael@0 | 3491 | if (aQueryIndex > 0) |
michael@0 | 3492 | qIndex.AppendInt(aQueryIndex); |
michael@0 | 3493 | |
michael@0 | 3494 | // begin time |
michael@0 | 3495 | if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) { |
michael@0 | 3496 | PRTime time = NormalizeTime(aQuery->BeginTimeReference(), |
michael@0 | 3497 | aQuery->BeginTime()); |
michael@0 | 3498 | rv = statement->BindInt64ByName( |
michael@0 | 3499 | NS_LITERAL_CSTRING("begin_time") + qIndex, time); |
michael@0 | 3500 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3501 | } |
michael@0 | 3502 | |
michael@0 | 3503 | // end time |
michael@0 | 3504 | if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) { |
michael@0 | 3505 | PRTime time = NormalizeTime(aQuery->EndTimeReference(), |
michael@0 | 3506 | aQuery->EndTime()); |
michael@0 | 3507 | rv = statement->BindInt64ByName( |
michael@0 | 3508 | NS_LITERAL_CSTRING("end_time") + qIndex, time |
michael@0 | 3509 | ); |
michael@0 | 3510 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3511 | } |
michael@0 | 3512 | |
michael@0 | 3513 | // search terms |
michael@0 | 3514 | if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasIt)) && hasIt) { |
michael@0 | 3515 | rv = statement->BindStringByName( |
michael@0 | 3516 | NS_LITERAL_CSTRING("search_string") + qIndex, |
michael@0 | 3517 | aQuery->SearchTerms() |
michael@0 | 3518 | ); |
michael@0 | 3519 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3520 | } |
michael@0 | 3521 | |
michael@0 | 3522 | // min and max visit count |
michael@0 | 3523 | int32_t visits = aQuery->MinVisits(); |
michael@0 | 3524 | if (visits >= 0) { |
michael@0 | 3525 | rv = statement->BindInt32ByName( |
michael@0 | 3526 | NS_LITERAL_CSTRING("min_visits") + qIndex, visits |
michael@0 | 3527 | ); |
michael@0 | 3528 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3529 | } |
michael@0 | 3530 | |
michael@0 | 3531 | visits = aQuery->MaxVisits(); |
michael@0 | 3532 | if (visits >= 0) { |
michael@0 | 3533 | rv = statement->BindInt32ByName( |
michael@0 | 3534 | NS_LITERAL_CSTRING("max_visits") + qIndex, visits |
michael@0 | 3535 | ); |
michael@0 | 3536 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3537 | } |
michael@0 | 3538 | |
michael@0 | 3539 | // domain (see GetReversedHostname for more info on reversed host names) |
michael@0 | 3540 | if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { |
michael@0 | 3541 | nsString revDomain; |
michael@0 | 3542 | GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain); |
michael@0 | 3543 | |
michael@0 | 3544 | if (aQuery->DomainIsHost()) { |
michael@0 | 3545 | rv = statement->BindStringByName( |
michael@0 | 3546 | NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain |
michael@0 | 3547 | ); |
michael@0 | 3548 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3549 | } else { |
michael@0 | 3550 | // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/" |
michael@0 | 3551 | // which will get everything starting with "gro.allizom." while using the |
michael@0 | 3552 | // index (using SUBSTRING() causes indexes to be discarded). |
michael@0 | 3553 | NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.', "Invalid rev. host"); |
michael@0 | 3554 | rv = statement->BindStringByName( |
michael@0 | 3555 | NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain |
michael@0 | 3556 | ); |
michael@0 | 3557 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3558 | revDomain.Truncate(revDomain.Length() - 1); |
michael@0 | 3559 | revDomain.Append(char16_t('/')); |
michael@0 | 3560 | rv = statement->BindStringByName( |
michael@0 | 3561 | NS_LITERAL_CSTRING("domain_upper") + qIndex, revDomain |
michael@0 | 3562 | ); |
michael@0 | 3563 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3564 | } |
michael@0 | 3565 | } |
michael@0 | 3566 | |
michael@0 | 3567 | // URI |
michael@0 | 3568 | if (aQuery->Uri()) { |
michael@0 | 3569 | rv = URIBinder::Bind( |
michael@0 | 3570 | statement, NS_LITERAL_CSTRING("uri") + qIndex, aQuery->Uri() |
michael@0 | 3571 | ); |
michael@0 | 3572 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3573 | if (aQuery->UriIsPrefix()) { |
michael@0 | 3574 | nsAutoCString uriString; |
michael@0 | 3575 | aQuery->Uri()->GetSpec(uriString); |
michael@0 | 3576 | uriString.Append(char(0x7F)); // MAX_UTF8 |
michael@0 | 3577 | rv = URIBinder::Bind( |
michael@0 | 3578 | statement, NS_LITERAL_CSTRING("uri_upper") + qIndex, uriString |
michael@0 | 3579 | ); |
michael@0 | 3580 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3581 | } |
michael@0 | 3582 | } |
michael@0 | 3583 | |
michael@0 | 3584 | // annotation |
michael@0 | 3585 | if (!aQuery->Annotation().IsEmpty()) { |
michael@0 | 3586 | rv = statement->BindUTF8StringByName( |
michael@0 | 3587 | NS_LITERAL_CSTRING("anno") + qIndex, aQuery->Annotation() |
michael@0 | 3588 | ); |
michael@0 | 3589 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3590 | } |
michael@0 | 3591 | |
michael@0 | 3592 | // tags |
michael@0 | 3593 | const nsTArray<nsString> &tags = aQuery->Tags(); |
michael@0 | 3594 | if (tags.Length() > 0) { |
michael@0 | 3595 | for (uint32_t i = 0; i < tags.Length(); ++i) { |
michael@0 | 3596 | nsPrintfCString paramName("tag%d_", i); |
michael@0 | 3597 | NS_ConvertUTF16toUTF8 tag(tags[i]); |
michael@0 | 3598 | rv = statement->BindUTF8StringByName(paramName + qIndex, tag); |
michael@0 | 3599 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3600 | } |
michael@0 | 3601 | int64_t tagsFolder = GetTagsFolder(); |
michael@0 | 3602 | rv = statement->BindInt64ByName( |
michael@0 | 3603 | NS_LITERAL_CSTRING("tags_folder") + qIndex, tagsFolder |
michael@0 | 3604 | ); |
michael@0 | 3605 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3606 | if (!aQuery->TagsAreNot()) { |
michael@0 | 3607 | rv = statement->BindInt32ByName( |
michael@0 | 3608 | NS_LITERAL_CSTRING("tag_count") + qIndex, tags.Length() |
michael@0 | 3609 | ); |
michael@0 | 3610 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3611 | } |
michael@0 | 3612 | } |
michael@0 | 3613 | |
michael@0 | 3614 | // transitions |
michael@0 | 3615 | const nsTArray<uint32_t>& transitions = aQuery->Transitions(); |
michael@0 | 3616 | if (transitions.Length() > 0) { |
michael@0 | 3617 | for (uint32_t i = 0; i < transitions.Length(); ++i) { |
michael@0 | 3618 | nsPrintfCString paramName("transition%d_", i); |
michael@0 | 3619 | rv = statement->BindInt64ByName(paramName + qIndex, transitions[i]); |
michael@0 | 3620 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3621 | } |
michael@0 | 3622 | } |
michael@0 | 3623 | |
michael@0 | 3624 | return NS_OK; |
michael@0 | 3625 | } |
michael@0 | 3626 | |
michael@0 | 3627 | |
michael@0 | 3628 | // nsNavHistory::ResultsAsList |
michael@0 | 3629 | // |
michael@0 | 3630 | |
michael@0 | 3631 | nsresult |
michael@0 | 3632 | nsNavHistory::ResultsAsList(mozIStorageStatement* statement, |
michael@0 | 3633 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 3634 | nsCOMArray<nsNavHistoryResultNode>* aResults) |
michael@0 | 3635 | { |
michael@0 | 3636 | nsresult rv; |
michael@0 | 3637 | nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); |
michael@0 | 3638 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3639 | |
michael@0 | 3640 | bool hasMore = false; |
michael@0 | 3641 | while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { |
michael@0 | 3642 | nsRefPtr<nsNavHistoryResultNode> result; |
michael@0 | 3643 | rv = RowToResult(row, aOptions, getter_AddRefs(result)); |
michael@0 | 3644 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3645 | aResults->AppendObject(result); |
michael@0 | 3646 | } |
michael@0 | 3647 | return NS_OK; |
michael@0 | 3648 | } |
michael@0 | 3649 | |
michael@0 | 3650 | const int64_t UNDEFINED_URN_VALUE = -1; |
michael@0 | 3651 | |
michael@0 | 3652 | // Create a urn (like |
michael@0 | 3653 | // urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29) |
michael@0 | 3654 | // to be used to persist the open state of this container in localstore.rdf |
michael@0 | 3655 | nsresult |
michael@0 | 3656 | CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode, |
michael@0 | 3657 | int64_t aValue, const nsCString& aTitle, nsCString& aURN) |
michael@0 | 3658 | { |
michael@0 | 3659 | nsAutoCString uri; |
michael@0 | 3660 | nsresult rv = aResultNode->GetUri(uri); |
michael@0 | 3661 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3662 | |
michael@0 | 3663 | aURN.Assign(NS_LITERAL_CSTRING("urn:places-persist:")); |
michael@0 | 3664 | aURN.Append(uri); |
michael@0 | 3665 | |
michael@0 | 3666 | aURN.Append(NS_LITERAL_CSTRING(",")); |
michael@0 | 3667 | if (aValue != UNDEFINED_URN_VALUE) |
michael@0 | 3668 | aURN.AppendInt(aValue); |
michael@0 | 3669 | |
michael@0 | 3670 | aURN.Append(NS_LITERAL_CSTRING(",")); |
michael@0 | 3671 | if (!aTitle.IsEmpty()) { |
michael@0 | 3672 | nsAutoCString escapedTitle; |
michael@0 | 3673 | bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas); |
michael@0 | 3674 | NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 3675 | aURN.Append(escapedTitle); |
michael@0 | 3676 | } |
michael@0 | 3677 | |
michael@0 | 3678 | return NS_OK; |
michael@0 | 3679 | } |
michael@0 | 3680 | |
michael@0 | 3681 | int64_t |
michael@0 | 3682 | nsNavHistory::GetTagsFolder() |
michael@0 | 3683 | { |
michael@0 | 3684 | // cache our tags folder |
michael@0 | 3685 | // note, we can't do this in nsNavHistory::Init(), |
michael@0 | 3686 | // as getting the bookmarks service would initialize it. |
michael@0 | 3687 | if (mTagsFolder == -1) { |
michael@0 | 3688 | nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 3689 | NS_ENSURE_TRUE(bookmarks, -1); |
michael@0 | 3690 | |
michael@0 | 3691 | nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder); |
michael@0 | 3692 | NS_ENSURE_SUCCESS(rv, -1); |
michael@0 | 3693 | } |
michael@0 | 3694 | return mTagsFolder; |
michael@0 | 3695 | } |
michael@0 | 3696 | |
michael@0 | 3697 | // nsNavHistory::FilterResultSet |
michael@0 | 3698 | // |
michael@0 | 3699 | // This does some post-query-execution filtering: |
michael@0 | 3700 | // - searching on title, url and tags |
michael@0 | 3701 | // - limit count |
michael@0 | 3702 | // |
michael@0 | 3703 | // Note: changes to filtering in FilterResultSet() |
michael@0 | 3704 | // may require changes to NeedToFilterResultSet() |
michael@0 | 3705 | |
michael@0 | 3706 | nsresult |
michael@0 | 3707 | nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode, |
michael@0 | 3708 | const nsCOMArray<nsNavHistoryResultNode>& aSet, |
michael@0 | 3709 | nsCOMArray<nsNavHistoryResultNode>* aFiltered, |
michael@0 | 3710 | const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 3711 | nsNavHistoryQueryOptions *aOptions) |
michael@0 | 3712 | { |
michael@0 | 3713 | // get the bookmarks service |
michael@0 | 3714 | nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 3715 | NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 3716 | |
michael@0 | 3717 | // parse the search terms |
michael@0 | 3718 | nsTArray<nsTArray<nsString>*> terms; |
michael@0 | 3719 | ParseSearchTermsFromQueries(aQueries, &terms); |
michael@0 | 3720 | |
michael@0 | 3721 | uint16_t resultType = aOptions->ResultType(); |
michael@0 | 3722 | for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) { |
michael@0 | 3723 | // exclude-queries is implicit when searching, we're only looking at |
michael@0 | 3724 | // plan URI nodes |
michael@0 | 3725 | if (!aSet[nodeIndex]->IsURI()) |
michael@0 | 3726 | continue; |
michael@0 | 3727 | |
michael@0 | 3728 | // RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and |
michael@0 | 3729 | // lastModified. So, to remove duplicates, we can retain the first result |
michael@0 | 3730 | // for each uri. |
michael@0 | 3731 | if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS && |
michael@0 | 3732 | nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI) |
michael@0 | 3733 | continue; |
michael@0 | 3734 | |
michael@0 | 3735 | if (aSet[nodeIndex]->mItemId != -1 && aQueryNode && |
michael@0 | 3736 | aQueryNode->mItemId == aSet[nodeIndex]->mItemId) { |
michael@0 | 3737 | continue; |
michael@0 | 3738 | } |
michael@0 | 3739 | |
michael@0 | 3740 | // Append the node only if it matches one of the queries. |
michael@0 | 3741 | bool appendNode = false; |
michael@0 | 3742 | for (int32_t queryIndex = 0; |
michael@0 | 3743 | queryIndex < aQueries.Count() && !appendNode; queryIndex++) { |
michael@0 | 3744 | |
michael@0 | 3745 | if (terms[queryIndex]->Length()) { |
michael@0 | 3746 | // Filter based on search terms. |
michael@0 | 3747 | // Convert title and url for the current node to UTF16 strings. |
michael@0 | 3748 | NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle); |
michael@0 | 3749 | // Unescape the URL for search terms matching. |
michael@0 | 3750 | nsAutoCString cNodeURL(aSet[nodeIndex]->mURI); |
michael@0 | 3751 | NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL)); |
michael@0 | 3752 | |
michael@0 | 3753 | // Determine if every search term matches anywhere in the title, url or |
michael@0 | 3754 | // tag. |
michael@0 | 3755 | bool matchAll = true; |
michael@0 | 3756 | for (int32_t termIndex = terms[queryIndex]->Length() - 1; |
michael@0 | 3757 | termIndex >= 0 && matchAll; |
michael@0 | 3758 | termIndex--) { |
michael@0 | 3759 | nsString& term = terms[queryIndex]->ElementAt(termIndex); |
michael@0 | 3760 | |
michael@0 | 3761 | // True if any of them match; false makes us quit the loop |
michael@0 | 3762 | matchAll = CaseInsensitiveFindInReadable(term, nodeTitle) || |
michael@0 | 3763 | CaseInsensitiveFindInReadable(term, nodeURL) || |
michael@0 | 3764 | CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags); |
michael@0 | 3765 | } |
michael@0 | 3766 | |
michael@0 | 3767 | // Skip the node if we don't match all terms in the title, url or tag |
michael@0 | 3768 | if (!matchAll) |
michael@0 | 3769 | continue; |
michael@0 | 3770 | } |
michael@0 | 3771 | |
michael@0 | 3772 | // We passed all filters, so we can append the node to filtered results. |
michael@0 | 3773 | appendNode = true; |
michael@0 | 3774 | } |
michael@0 | 3775 | |
michael@0 | 3776 | if (appendNode) |
michael@0 | 3777 | aFiltered->AppendObject(aSet[nodeIndex]); |
michael@0 | 3778 | |
michael@0 | 3779 | // Stop once we have reached max results. |
michael@0 | 3780 | if (aOptions->MaxResults() > 0 && |
michael@0 | 3781 | (uint32_t)aFiltered->Count() >= aOptions->MaxResults()) |
michael@0 | 3782 | break; |
michael@0 | 3783 | } |
michael@0 | 3784 | |
michael@0 | 3785 | // De-allocate the temporary matrixes. |
michael@0 | 3786 | for (int32_t i = 0; i < aQueries.Count(); i++) { |
michael@0 | 3787 | delete terms[i]; |
michael@0 | 3788 | } |
michael@0 | 3789 | |
michael@0 | 3790 | return NS_OK; |
michael@0 | 3791 | } |
michael@0 | 3792 | |
michael@0 | 3793 | void |
michael@0 | 3794 | nsNavHistory::registerEmbedVisit(nsIURI* aURI, |
michael@0 | 3795 | int64_t aTime) |
michael@0 | 3796 | { |
michael@0 | 3797 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 3798 | |
michael@0 | 3799 | VisitHashKey* visit = mEmbedVisits.PutEntry(aURI); |
michael@0 | 3800 | if (!visit) { |
michael@0 | 3801 | NS_WARNING("Unable to register a EMBED visit."); |
michael@0 | 3802 | return; |
michael@0 | 3803 | } |
michael@0 | 3804 | visit->visitTime = aTime; |
michael@0 | 3805 | } |
michael@0 | 3806 | |
michael@0 | 3807 | bool |
michael@0 | 3808 | nsNavHistory::hasEmbedVisit(nsIURI* aURI) { |
michael@0 | 3809 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 3810 | |
michael@0 | 3811 | return !!mEmbedVisits.GetEntry(aURI); |
michael@0 | 3812 | } |
michael@0 | 3813 | |
michael@0 | 3814 | void |
michael@0 | 3815 | nsNavHistory::clearEmbedVisits() { |
michael@0 | 3816 | NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
michael@0 | 3817 | |
michael@0 | 3818 | mEmbedVisits.Clear(); |
michael@0 | 3819 | } |
michael@0 | 3820 | |
michael@0 | 3821 | // nsNavHistory::CheckIsRecentEvent |
michael@0 | 3822 | // |
michael@0 | 3823 | // Sees if this URL happened "recently." |
michael@0 | 3824 | // |
michael@0 | 3825 | // It is always removed from our recent list no matter what. It only counts |
michael@0 | 3826 | // as "recent" if the event happened more recently than our event |
michael@0 | 3827 | // threshold ago. |
michael@0 | 3828 | |
michael@0 | 3829 | bool |
michael@0 | 3830 | nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable, |
michael@0 | 3831 | const nsACString& url) |
michael@0 | 3832 | { |
michael@0 | 3833 | PRTime eventTime; |
michael@0 | 3834 | if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) { |
michael@0 | 3835 | hashTable->Remove(url); |
michael@0 | 3836 | if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD) |
michael@0 | 3837 | return true; |
michael@0 | 3838 | return false; |
michael@0 | 3839 | } |
michael@0 | 3840 | return false; |
michael@0 | 3841 | } |
michael@0 | 3842 | |
michael@0 | 3843 | |
michael@0 | 3844 | // nsNavHistory::ExpireNonrecentEvents |
michael@0 | 3845 | // |
michael@0 | 3846 | // This goes through our |
michael@0 | 3847 | |
michael@0 | 3848 | static PLDHashOperator |
michael@0 | 3849 | ExpireNonrecentEventsCallback(nsCStringHashKey::KeyType aKey, |
michael@0 | 3850 | int64_t& aData, |
michael@0 | 3851 | void* userArg) |
michael@0 | 3852 | { |
michael@0 | 3853 | int64_t* threshold = reinterpret_cast<int64_t*>(userArg); |
michael@0 | 3854 | if (aData < *threshold) |
michael@0 | 3855 | return PL_DHASH_REMOVE; |
michael@0 | 3856 | return PL_DHASH_NEXT; |
michael@0 | 3857 | } |
michael@0 | 3858 | void |
michael@0 | 3859 | nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable) |
michael@0 | 3860 | { |
michael@0 | 3861 | int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD; |
michael@0 | 3862 | hashTable->Enumerate(ExpireNonrecentEventsCallback, |
michael@0 | 3863 | reinterpret_cast<void*>(&threshold)); |
michael@0 | 3864 | } |
michael@0 | 3865 | |
michael@0 | 3866 | |
michael@0 | 3867 | // nsNavHistory::RowToResult |
michael@0 | 3868 | // |
michael@0 | 3869 | // Here, we just have a generic row. It could be a query, URL, visit, |
michael@0 | 3870 | // or full visit. |
michael@0 | 3871 | |
michael@0 | 3872 | nsresult |
michael@0 | 3873 | nsNavHistory::RowToResult(mozIStorageValueArray* aRow, |
michael@0 | 3874 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 3875 | nsNavHistoryResultNode** aResult) |
michael@0 | 3876 | { |
michael@0 | 3877 | NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult"); |
michael@0 | 3878 | |
michael@0 | 3879 | // URL |
michael@0 | 3880 | nsAutoCString url; |
michael@0 | 3881 | nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url); |
michael@0 | 3882 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3883 | |
michael@0 | 3884 | // title |
michael@0 | 3885 | nsAutoCString title; |
michael@0 | 3886 | rv = aRow->GetUTF8String(kGetInfoIndex_Title, title); |
michael@0 | 3887 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3888 | |
michael@0 | 3889 | uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount); |
michael@0 | 3890 | PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate); |
michael@0 | 3891 | |
michael@0 | 3892 | // favicon |
michael@0 | 3893 | nsAutoCString favicon; |
michael@0 | 3894 | rv = aRow->GetUTF8String(kGetInfoIndex_FaviconURL, favicon); |
michael@0 | 3895 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3896 | |
michael@0 | 3897 | // itemId |
michael@0 | 3898 | int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId); |
michael@0 | 3899 | int64_t parentId = -1; |
michael@0 | 3900 | if (itemId == 0) { |
michael@0 | 3901 | // This is not a bookmark. For non-bookmarks we use a -1 itemId value. |
michael@0 | 3902 | // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0. |
michael@0 | 3903 | itemId = -1; |
michael@0 | 3904 | } |
michael@0 | 3905 | else { |
michael@0 | 3906 | // This is a bookmark, so it has a parent. |
michael@0 | 3907 | int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId); |
michael@0 | 3908 | if (itemParentId > 0) { |
michael@0 | 3909 | // The Places root has parent == 0, but that item id does not really |
michael@0 | 3910 | // exist. We want to set the parent only if it's a real one. |
michael@0 | 3911 | parentId = itemParentId; |
michael@0 | 3912 | } |
michael@0 | 3913 | } |
michael@0 | 3914 | |
michael@0 | 3915 | if (IsQueryURI(url)) { |
michael@0 | 3916 | // special case "place:" URIs: turn them into containers |
michael@0 | 3917 | |
michael@0 | 3918 | // We should never expose the history title for query nodes if the |
michael@0 | 3919 | // bookmark-item's title is set to null (the history title may be the |
michael@0 | 3920 | // query string without the place: prefix). Thus we call getItemTitle |
michael@0 | 3921 | // explicitly. Doing this in the SQL query would be less performant since |
michael@0 | 3922 | // it should be done for all results rather than only for queries. |
michael@0 | 3923 | if (itemId != -1) { |
michael@0 | 3924 | nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 3925 | NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 3926 | |
michael@0 | 3927 | rv = bookmarks->GetItemTitle(itemId, title); |
michael@0 | 3928 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3929 | } |
michael@0 | 3930 | |
michael@0 | 3931 | nsRefPtr<nsNavHistoryResultNode> resultNode; |
michael@0 | 3932 | rv = QueryRowToResult(itemId, url, title, accessCount, time, favicon, |
michael@0 | 3933 | getter_AddRefs(resultNode)); |
michael@0 | 3934 | NS_ENSURE_SUCCESS(rv,rv); |
michael@0 | 3935 | |
michael@0 | 3936 | if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) { |
michael@0 | 3937 | // RESULTS_AS_TAG_QUERY has date columns |
michael@0 | 3938 | resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); |
michael@0 | 3939 | resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); |
michael@0 | 3940 | } |
michael@0 | 3941 | else if (resultNode->IsFolder()) { |
michael@0 | 3942 | // If it's a simple folder node (i.e. a shortcut to another folder), apply |
michael@0 | 3943 | // our options for it. However, if the parent type was tag query, we do not |
michael@0 | 3944 | // apply them, because it would not yield any results. |
michael@0 | 3945 | resultNode->GetAsContainer()->mOptions = aOptions; |
michael@0 | 3946 | } |
michael@0 | 3947 | |
michael@0 | 3948 | resultNode.forget(aResult); |
michael@0 | 3949 | return rv; |
michael@0 | 3950 | } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI || |
michael@0 | 3951 | aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { |
michael@0 | 3952 | nsRefPtr<nsNavHistoryResultNode> resultNode = |
michael@0 | 3953 | new nsNavHistoryResultNode(url, title, accessCount, time, favicon); |
michael@0 | 3954 | |
michael@0 | 3955 | if (itemId != -1) { |
michael@0 | 3956 | resultNode->mItemId = itemId; |
michael@0 | 3957 | resultNode->mFolderId = parentId; |
michael@0 | 3958 | resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); |
michael@0 | 3959 | resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); |
michael@0 | 3960 | } |
michael@0 | 3961 | |
michael@0 | 3962 | resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency); |
michael@0 | 3963 | resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden); |
michael@0 | 3964 | |
michael@0 | 3965 | nsAutoString tags; |
michael@0 | 3966 | rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); |
michael@0 | 3967 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3968 | if (!tags.IsVoid()) { |
michael@0 | 3969 | resultNode->mTags.Assign(tags); |
michael@0 | 3970 | } |
michael@0 | 3971 | |
michael@0 | 3972 | rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); |
michael@0 | 3973 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3974 | |
michael@0 | 3975 | resultNode.forget(aResult); |
michael@0 | 3976 | return NS_OK; |
michael@0 | 3977 | } |
michael@0 | 3978 | |
michael@0 | 3979 | if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) { |
michael@0 | 3980 | nsRefPtr<nsNavHistoryResultNode> resultNode = |
michael@0 | 3981 | new nsNavHistoryResultNode(url, title, accessCount, time, favicon); |
michael@0 | 3982 | |
michael@0 | 3983 | nsAutoString tags; |
michael@0 | 3984 | rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); |
michael@0 | 3985 | if (!tags.IsVoid()) |
michael@0 | 3986 | resultNode->mTags.Assign(tags); |
michael@0 | 3987 | |
michael@0 | 3988 | rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); |
michael@0 | 3989 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3990 | |
michael@0 | 3991 | resultNode.forget(aResult); |
michael@0 | 3992 | return NS_OK; |
michael@0 | 3993 | } |
michael@0 | 3994 | |
michael@0 | 3995 | return NS_ERROR_FAILURE; |
michael@0 | 3996 | } |
michael@0 | 3997 | |
michael@0 | 3998 | |
michael@0 | 3999 | // nsNavHistory::QueryRowToResult |
michael@0 | 4000 | // |
michael@0 | 4001 | // Called by RowToResult when the URI is a place: URI to generate the proper |
michael@0 | 4002 | // folder or query node. |
michael@0 | 4003 | |
michael@0 | 4004 | nsresult |
michael@0 | 4005 | nsNavHistory::QueryRowToResult(int64_t itemId, const nsACString& aURI, |
michael@0 | 4006 | const nsACString& aTitle, |
michael@0 | 4007 | uint32_t aAccessCount, PRTime aTime, |
michael@0 | 4008 | const nsACString& aFavicon, |
michael@0 | 4009 | nsNavHistoryResultNode** aNode) |
michael@0 | 4010 | { |
michael@0 | 4011 | nsCOMArray<nsNavHistoryQuery> queries; |
michael@0 | 4012 | nsCOMPtr<nsNavHistoryQueryOptions> options; |
michael@0 | 4013 | nsresult rv = QueryStringToQueryArray(aURI, &queries, |
michael@0 | 4014 | getter_AddRefs(options)); |
michael@0 | 4015 | |
michael@0 | 4016 | nsRefPtr<nsNavHistoryResultNode> resultNode; |
michael@0 | 4017 | // If this failed the query does not parse correctly, let the error pass and |
michael@0 | 4018 | // handle it later. |
michael@0 | 4019 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 4020 | // Check if this is a folder shortcut, so we can take a faster path. |
michael@0 | 4021 | int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); |
michael@0 | 4022 | if (folderId) { |
michael@0 | 4023 | nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
michael@0 | 4024 | NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 4025 | |
michael@0 | 4026 | rv = bookmarks->ResultNodeForContainer(folderId, options, |
michael@0 | 4027 | getter_AddRefs(resultNode)); |
michael@0 | 4028 | // If this failed the shortcut is pointing to nowhere, let the error pass |
michael@0 | 4029 | // and handle it later. |
michael@0 | 4030 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 4031 | // This is the query itemId, and is what is exposed by node.itemId. |
michael@0 | 4032 | resultNode->GetAsFolder()->mQueryItemId = itemId; |
michael@0 | 4033 | |
michael@0 | 4034 | // Use the query item title, unless it's void (in that case use the |
michael@0 | 4035 | // concrete folder title). |
michael@0 | 4036 | if (!aTitle.IsVoid()) { |
michael@0 | 4037 | resultNode->mTitle = aTitle; |
michael@0 | 4038 | } |
michael@0 | 4039 | } |
michael@0 | 4040 | } |
michael@0 | 4041 | else { |
michael@0 | 4042 | // This is a regular query. |
michael@0 | 4043 | resultNode = new nsNavHistoryQueryResultNode(aTitle, EmptyCString(), |
michael@0 | 4044 | aTime, queries, options); |
michael@0 | 4045 | resultNode->mItemId = itemId; |
michael@0 | 4046 | } |
michael@0 | 4047 | } |
michael@0 | 4048 | |
michael@0 | 4049 | if (NS_FAILED(rv)) { |
michael@0 | 4050 | NS_WARNING("Generating a generic empty node for a broken query!"); |
michael@0 | 4051 | // This is a broken query, that either did not parse or points to not |
michael@0 | 4052 | // existing data. We don't want to return failure since that will kill the |
michael@0 | 4053 | // whole result. Instead make a generic empty query node. |
michael@0 | 4054 | resultNode = new nsNavHistoryQueryResultNode(aTitle, aFavicon, aURI); |
michael@0 | 4055 | resultNode->mItemId = itemId; |
michael@0 | 4056 | // This is a perf hack to generate an empty query that skips filtering. |
michael@0 | 4057 | resultNode->GetAsQuery()->Options()->SetExcludeItems(true); |
michael@0 | 4058 | } |
michael@0 | 4059 | |
michael@0 | 4060 | resultNode.forget(aNode); |
michael@0 | 4061 | return NS_OK; |
michael@0 | 4062 | } |
michael@0 | 4063 | |
michael@0 | 4064 | |
michael@0 | 4065 | // nsNavHistory::VisitIdToResultNode |
michael@0 | 4066 | // |
michael@0 | 4067 | // Used by the query results to create new nodes on the fly when |
michael@0 | 4068 | // notifications come in. This just creates a node for the given visit ID. |
michael@0 | 4069 | |
michael@0 | 4070 | nsresult |
michael@0 | 4071 | nsNavHistory::VisitIdToResultNode(int64_t visitId, |
michael@0 | 4072 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 4073 | nsNavHistoryResultNode** aResult) |
michael@0 | 4074 | { |
michael@0 | 4075 | nsAutoCString tagsFragment; |
michael@0 | 4076 | GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
michael@0 | 4077 | true, tagsFragment); |
michael@0 | 4078 | |
michael@0 | 4079 | nsCOMPtr<mozIStorageStatement> statement; |
michael@0 | 4080 | switch (aOptions->ResultType()) |
michael@0 | 4081 | { |
michael@0 | 4082 | case nsNavHistoryQueryOptions::RESULTS_AS_VISIT: |
michael@0 | 4083 | case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: |
michael@0 | 4084 | // visit query - want exact visit time |
michael@0 | 4085 | // Should match kGetInfoIndex_* (see GetQueryResults) |
michael@0 | 4086 | statement = mDB->GetStatement(NS_LITERAL_CSTRING( |
michael@0 | 4087 | "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " |
michael@0 | 4088 | "v.visit_date, f.url, null, null, null, null, " |
michael@0 | 4089 | ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 4090 | "FROM moz_places h " |
michael@0 | 4091 | "JOIN moz_historyvisits v ON h.id = v.place_id " |
michael@0 | 4092 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 4093 | "WHERE v.id = :visit_id ") |
michael@0 | 4094 | ); |
michael@0 | 4095 | break; |
michael@0 | 4096 | |
michael@0 | 4097 | case nsNavHistoryQueryOptions::RESULTS_AS_URI: |
michael@0 | 4098 | // URL results - want last visit time |
michael@0 | 4099 | // Should match kGetInfoIndex_* (see GetQueryResults) |
michael@0 | 4100 | statement = mDB->GetStatement(NS_LITERAL_CSTRING( |
michael@0 | 4101 | "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " |
michael@0 | 4102 | "h.last_visit_date, f.url, null, null, null, null, " |
michael@0 | 4103 | ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 4104 | "FROM moz_places h " |
michael@0 | 4105 | "JOIN moz_historyvisits v ON h.id = v.place_id " |
michael@0 | 4106 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 4107 | "WHERE v.id = :visit_id ") |
michael@0 | 4108 | ); |
michael@0 | 4109 | break; |
michael@0 | 4110 | |
michael@0 | 4111 | default: |
michael@0 | 4112 | // Query base types like RESULTS_AS_*_QUERY handle additions |
michael@0 | 4113 | // by registering their own observers when they are expanded. |
michael@0 | 4114 | return NS_OK; |
michael@0 | 4115 | } |
michael@0 | 4116 | NS_ENSURE_STATE(statement); |
michael@0 | 4117 | mozStorageStatementScoper scoper(statement); |
michael@0 | 4118 | |
michael@0 | 4119 | nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"), |
michael@0 | 4120 | visitId); |
michael@0 | 4121 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4122 | |
michael@0 | 4123 | bool hasMore = false; |
michael@0 | 4124 | rv = statement->ExecuteStep(&hasMore); |
michael@0 | 4125 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4126 | if (! hasMore) { |
michael@0 | 4127 | NS_NOTREACHED("Trying to get a result node for an invalid visit"); |
michael@0 | 4128 | return NS_ERROR_INVALID_ARG; |
michael@0 | 4129 | } |
michael@0 | 4130 | |
michael@0 | 4131 | nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); |
michael@0 | 4132 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4133 | |
michael@0 | 4134 | return RowToResult(row, aOptions, aResult); |
michael@0 | 4135 | } |
michael@0 | 4136 | |
michael@0 | 4137 | nsresult |
michael@0 | 4138 | nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions, |
michael@0 | 4139 | nsNavHistoryResultNode** aResult) |
michael@0 | 4140 | { |
michael@0 | 4141 | nsAutoCString tagsFragment; |
michael@0 | 4142 | GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
michael@0 | 4143 | true, tagsFragment); |
michael@0 | 4144 | // Should match kGetInfoIndex_* |
michael@0 | 4145 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
michael@0 | 4146 | "SELECT b.fk, h.url, COALESCE(b.title, h.title), " |
michael@0 | 4147 | "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " |
michael@0 | 4148 | "b.dateAdded, b.lastModified, b.parent, " |
michael@0 | 4149 | ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 4150 | "FROM moz_bookmarks b " |
michael@0 | 4151 | "JOIN moz_places h ON b.fk = h.id " |
michael@0 | 4152 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 4153 | "WHERE b.id = :item_id ") |
michael@0 | 4154 | ); |
michael@0 | 4155 | NS_ENSURE_STATE(stmt); |
michael@0 | 4156 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 4157 | |
michael@0 | 4158 | nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), |
michael@0 | 4159 | aBookmarkId); |
michael@0 | 4160 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4161 | |
michael@0 | 4162 | bool hasMore = false; |
michael@0 | 4163 | rv = stmt->ExecuteStep(&hasMore); |
michael@0 | 4164 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4165 | if (!hasMore) { |
michael@0 | 4166 | NS_NOTREACHED("Trying to get a result node for an invalid bookmark identifier"); |
michael@0 | 4167 | return NS_ERROR_INVALID_ARG; |
michael@0 | 4168 | } |
michael@0 | 4169 | |
michael@0 | 4170 | nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); |
michael@0 | 4171 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4172 | |
michael@0 | 4173 | return RowToResult(row, aOptions, aResult); |
michael@0 | 4174 | } |
michael@0 | 4175 | |
michael@0 | 4176 | nsresult |
michael@0 | 4177 | nsNavHistory::URIToResultNode(nsIURI* aURI, |
michael@0 | 4178 | nsNavHistoryQueryOptions* aOptions, |
michael@0 | 4179 | nsNavHistoryResultNode** aResult) |
michael@0 | 4180 | { |
michael@0 | 4181 | nsAutoCString tagsFragment; |
michael@0 | 4182 | GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
michael@0 | 4183 | true, tagsFragment); |
michael@0 | 4184 | // Should match kGetInfoIndex_* |
michael@0 | 4185 | nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
michael@0 | 4186 | "SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, " |
michael@0 | 4187 | "h.last_visit_date, f.url, null, null, null, null, " |
michael@0 | 4188 | ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
michael@0 | 4189 | "FROM moz_places h " |
michael@0 | 4190 | "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
michael@0 | 4191 | "WHERE h.url = :page_url ") |
michael@0 | 4192 | ); |
michael@0 | 4193 | NS_ENSURE_STATE(stmt); |
michael@0 | 4194 | mozStorageStatementScoper scoper(stmt); |
michael@0 | 4195 | |
michael@0 | 4196 | nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
michael@0 | 4197 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4198 | |
michael@0 | 4199 | bool hasMore = false; |
michael@0 | 4200 | rv = stmt->ExecuteStep(&hasMore); |
michael@0 | 4201 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4202 | if (!hasMore) { |
michael@0 | 4203 | NS_NOTREACHED("Trying to get a result node for an invalid url"); |
michael@0 | 4204 | return NS_ERROR_INVALID_ARG; |
michael@0 | 4205 | } |
michael@0 | 4206 | |
michael@0 | 4207 | nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); |
michael@0 | 4208 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4209 | |
michael@0 | 4210 | return RowToResult(row, aOptions, aResult); |
michael@0 | 4211 | } |
michael@0 | 4212 | |
michael@0 | 4213 | void |
michael@0 | 4214 | nsNavHistory::SendPageChangedNotification(nsIURI* aURI, |
michael@0 | 4215 | uint32_t aChangedAttribute, |
michael@0 | 4216 | const nsAString& aNewValue, |
michael@0 | 4217 | const nsACString& aGUID) |
michael@0 | 4218 | { |
michael@0 | 4219 | MOZ_ASSERT(!aGUID.IsEmpty()); |
michael@0 | 4220 | NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
michael@0 | 4221 | nsINavHistoryObserver, |
michael@0 | 4222 | OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID)); |
michael@0 | 4223 | } |
michael@0 | 4224 | |
michael@0 | 4225 | // nsNavHistory::TitleForDomain |
michael@0 | 4226 | // |
michael@0 | 4227 | // This computes the title for a given domain. Normally, this is just the |
michael@0 | 4228 | // domain name, but we specially handle empty cases to give you a nice |
michael@0 | 4229 | // localized string. |
michael@0 | 4230 | |
michael@0 | 4231 | void |
michael@0 | 4232 | nsNavHistory::TitleForDomain(const nsCString& domain, nsACString& aTitle) |
michael@0 | 4233 | { |
michael@0 | 4234 | if (! domain.IsEmpty()) { |
michael@0 | 4235 | aTitle = domain; |
michael@0 | 4236 | return; |
michael@0 | 4237 | } |
michael@0 | 4238 | |
michael@0 | 4239 | // use the localized one instead |
michael@0 | 4240 | GetStringFromName(MOZ_UTF16("localhost"), aTitle); |
michael@0 | 4241 | } |
michael@0 | 4242 | |
michael@0 | 4243 | void |
michael@0 | 4244 | nsNavHistory::GetAgeInDaysString(int32_t aInt, const char16_t *aName, |
michael@0 | 4245 | nsACString& aResult) |
michael@0 | 4246 | { |
michael@0 | 4247 | nsIStringBundle *bundle = GetBundle(); |
michael@0 | 4248 | if (bundle) { |
michael@0 | 4249 | nsAutoString intString; |
michael@0 | 4250 | intString.AppendInt(aInt); |
michael@0 | 4251 | const char16_t* strings[1] = { intString.get() }; |
michael@0 | 4252 | nsXPIDLString value; |
michael@0 | 4253 | nsresult rv = bundle->FormatStringFromName(aName, strings, |
michael@0 | 4254 | 1, getter_Copies(value)); |
michael@0 | 4255 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 4256 | CopyUTF16toUTF8(value, aResult); |
michael@0 | 4257 | return; |
michael@0 | 4258 | } |
michael@0 | 4259 | } |
michael@0 | 4260 | CopyUTF16toUTF8(nsDependentString(aName), aResult); |
michael@0 | 4261 | } |
michael@0 | 4262 | |
michael@0 | 4263 | void |
michael@0 | 4264 | nsNavHistory::GetStringFromName(const char16_t *aName, nsACString& aResult) |
michael@0 | 4265 | { |
michael@0 | 4266 | nsIStringBundle *bundle = GetBundle(); |
michael@0 | 4267 | if (bundle) { |
michael@0 | 4268 | nsXPIDLString value; |
michael@0 | 4269 | nsresult rv = bundle->GetStringFromName(aName, getter_Copies(value)); |
michael@0 | 4270 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 4271 | CopyUTF16toUTF8(value, aResult); |
michael@0 | 4272 | return; |
michael@0 | 4273 | } |
michael@0 | 4274 | } |
michael@0 | 4275 | CopyUTF16toUTF8(nsDependentString(aName), aResult); |
michael@0 | 4276 | } |
michael@0 | 4277 | |
michael@0 | 4278 | void |
michael@0 | 4279 | nsNavHistory::GetMonthName(int32_t aIndex, nsACString& aResult) |
michael@0 | 4280 | { |
michael@0 | 4281 | nsIStringBundle *bundle = GetDateFormatBundle(); |
michael@0 | 4282 | if (bundle) { |
michael@0 | 4283 | nsCString name = nsPrintfCString("month.%d.name", aIndex); |
michael@0 | 4284 | nsXPIDLString value; |
michael@0 | 4285 | nsresult rv = bundle->GetStringFromName(NS_ConvertUTF8toUTF16(name).get(), |
michael@0 | 4286 | getter_Copies(value)); |
michael@0 | 4287 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 4288 | CopyUTF16toUTF8(value, aResult); |
michael@0 | 4289 | return; |
michael@0 | 4290 | } |
michael@0 | 4291 | } |
michael@0 | 4292 | aResult = nsPrintfCString("[%d]", aIndex); |
michael@0 | 4293 | } |
michael@0 | 4294 | |
michael@0 | 4295 | void |
michael@0 | 4296 | nsNavHistory::GetMonthYear(int32_t aMonth, int32_t aYear, nsACString& aResult) |
michael@0 | 4297 | { |
michael@0 | 4298 | nsIStringBundle *bundle = GetBundle(); |
michael@0 | 4299 | if (bundle) { |
michael@0 | 4300 | nsAutoCString monthName; |
michael@0 | 4301 | GetMonthName(aMonth, monthName); |
michael@0 | 4302 | nsAutoString yearString; |
michael@0 | 4303 | yearString.AppendInt(aYear); |
michael@0 | 4304 | const char16_t* strings[2] = { |
michael@0 | 4305 | NS_ConvertUTF8toUTF16(monthName).get() |
michael@0 | 4306 | , yearString.get() |
michael@0 | 4307 | }; |
michael@0 | 4308 | nsXPIDLString value; |
michael@0 | 4309 | if (NS_SUCCEEDED(bundle->FormatStringFromName( |
michael@0 | 4310 | MOZ_UTF16("finduri-MonthYear"), strings, 2, |
michael@0 | 4311 | getter_Copies(value) |
michael@0 | 4312 | ))) { |
michael@0 | 4313 | CopyUTF16toUTF8(value, aResult); |
michael@0 | 4314 | return; |
michael@0 | 4315 | } |
michael@0 | 4316 | } |
michael@0 | 4317 | aResult.AppendLiteral("finduri-MonthYear"); |
michael@0 | 4318 | } |
michael@0 | 4319 | |
michael@0 | 4320 | |
michael@0 | 4321 | namespace { |
michael@0 | 4322 | |
michael@0 | 4323 | // GetSimpleBookmarksQueryFolder |
michael@0 | 4324 | // |
michael@0 | 4325 | // Determines if this set of queries is a simple bookmarks query for a |
michael@0 | 4326 | // folder with no other constraints. In these common cases, we can more |
michael@0 | 4327 | // efficiently compute the results. |
michael@0 | 4328 | // |
michael@0 | 4329 | // A simple bookmarks query will result in a hierarchical tree of |
michael@0 | 4330 | // bookmark items, folders and separators. |
michael@0 | 4331 | // |
michael@0 | 4332 | // Returns the folder ID if it is a simple folder query, 0 if not. |
michael@0 | 4333 | static int64_t |
michael@0 | 4334 | GetSimpleBookmarksQueryFolder(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 4335 | nsNavHistoryQueryOptions* aOptions) |
michael@0 | 4336 | { |
michael@0 | 4337 | if (aQueries.Count() != 1) |
michael@0 | 4338 | return 0; |
michael@0 | 4339 | |
michael@0 | 4340 | nsNavHistoryQuery* query = aQueries[0]; |
michael@0 | 4341 | if (query->Folders().Length() != 1) |
michael@0 | 4342 | return 0; |
michael@0 | 4343 | |
michael@0 | 4344 | bool hasIt; |
michael@0 | 4345 | query->GetHasBeginTime(&hasIt); |
michael@0 | 4346 | if (hasIt) |
michael@0 | 4347 | return 0; |
michael@0 | 4348 | query->GetHasEndTime(&hasIt); |
michael@0 | 4349 | if (hasIt) |
michael@0 | 4350 | return 0; |
michael@0 | 4351 | query->GetHasDomain(&hasIt); |
michael@0 | 4352 | if (hasIt) |
michael@0 | 4353 | return 0; |
michael@0 | 4354 | query->GetHasUri(&hasIt); |
michael@0 | 4355 | if (hasIt) |
michael@0 | 4356 | return 0; |
michael@0 | 4357 | (void)query->GetHasSearchTerms(&hasIt); |
michael@0 | 4358 | if (hasIt) |
michael@0 | 4359 | return 0; |
michael@0 | 4360 | if (query->Tags().Length() > 0) |
michael@0 | 4361 | return 0; |
michael@0 | 4362 | if (aOptions->MaxResults() > 0) |
michael@0 | 4363 | return 0; |
michael@0 | 4364 | |
michael@0 | 4365 | // RESULTS_AS_TAG_CONTENTS is quite similar to a folder shortcut, but it must |
michael@0 | 4366 | // not be treated like that, since it needs all query options. |
michael@0 | 4367 | if(aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) |
michael@0 | 4368 | return 0; |
michael@0 | 4369 | |
michael@0 | 4370 | // Don't care about onlyBookmarked flag, since specifying a bookmark |
michael@0 | 4371 | // folder is inferring onlyBookmarked. |
michael@0 | 4372 | |
michael@0 | 4373 | return query->Folders()[0]; |
michael@0 | 4374 | } |
michael@0 | 4375 | |
michael@0 | 4376 | |
michael@0 | 4377 | // ParseSearchTermsFromQueries |
michael@0 | 4378 | // |
michael@0 | 4379 | // Construct a matrix of search terms from the given queries array. |
michael@0 | 4380 | // All of the query objects are ORed together. Within a query, all the terms |
michael@0 | 4381 | // are ANDed together. See nsINavHistoryService.idl. |
michael@0 | 4382 | // |
michael@0 | 4383 | // This just breaks the query up into words. We don't do anything fancy, |
michael@0 | 4384 | // not even quoting. We do, however, strip quotes, because people might |
michael@0 | 4385 | // try to input quotes expecting them to do something and get no results |
michael@0 | 4386 | // back. |
michael@0 | 4387 | |
michael@0 | 4388 | inline bool isQueryWhitespace(char16_t ch) |
michael@0 | 4389 | { |
michael@0 | 4390 | return ch == ' '; |
michael@0 | 4391 | } |
michael@0 | 4392 | |
michael@0 | 4393 | void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
michael@0 | 4394 | nsTArray<nsTArray<nsString>*>* aTerms) |
michael@0 | 4395 | { |
michael@0 | 4396 | int32_t lastBegin = -1; |
michael@0 | 4397 | for (int32_t i = 0; i < aQueries.Count(); i++) { |
michael@0 | 4398 | nsTArray<nsString> *queryTerms = new nsTArray<nsString>(); |
michael@0 | 4399 | bool hasSearchTerms; |
michael@0 | 4400 | if (NS_SUCCEEDED(aQueries[i]->GetHasSearchTerms(&hasSearchTerms)) && |
michael@0 | 4401 | hasSearchTerms) { |
michael@0 | 4402 | const nsString& searchTerms = aQueries[i]->SearchTerms(); |
michael@0 | 4403 | for (uint32_t j = 0; j < searchTerms.Length(); j++) { |
michael@0 | 4404 | if (isQueryWhitespace(searchTerms[j]) || |
michael@0 | 4405 | searchTerms[j] == '"') { |
michael@0 | 4406 | if (lastBegin >= 0) { |
michael@0 | 4407 | // found the end of a word |
michael@0 | 4408 | queryTerms->AppendElement(Substring(searchTerms, lastBegin, |
michael@0 | 4409 | j - lastBegin)); |
michael@0 | 4410 | lastBegin = -1; |
michael@0 | 4411 | } |
michael@0 | 4412 | } else { |
michael@0 | 4413 | if (lastBegin < 0) { |
michael@0 | 4414 | // found the beginning of a word |
michael@0 | 4415 | lastBegin = j; |
michael@0 | 4416 | } |
michael@0 | 4417 | } |
michael@0 | 4418 | } |
michael@0 | 4419 | // last word |
michael@0 | 4420 | if (lastBegin >= 0) |
michael@0 | 4421 | queryTerms->AppendElement(Substring(searchTerms, lastBegin)); |
michael@0 | 4422 | } |
michael@0 | 4423 | aTerms->AppendElement(queryTerms); |
michael@0 | 4424 | } |
michael@0 | 4425 | } |
michael@0 | 4426 | |
michael@0 | 4427 | } // anonymous namespace |
michael@0 | 4428 | |
michael@0 | 4429 | |
michael@0 | 4430 | nsresult |
michael@0 | 4431 | nsNavHistory::UpdateFrecency(int64_t aPlaceId) |
michael@0 | 4432 | { |
michael@0 | 4433 | nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement( |
michael@0 | 4434 | "UPDATE moz_places " |
michael@0 | 4435 | "SET frecency = NOTIFY_FRECENCY(" |
michael@0 | 4436 | "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date" |
michael@0 | 4437 | ") " |
michael@0 | 4438 | "WHERE id = :page_id" |
michael@0 | 4439 | ); |
michael@0 | 4440 | NS_ENSURE_STATE(updateFrecencyStmt); |
michael@0 | 4441 | nsresult rv = updateFrecencyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), |
michael@0 | 4442 | aPlaceId); |
michael@0 | 4443 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4444 | nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement( |
michael@0 | 4445 | "UPDATE moz_places " |
michael@0 | 4446 | "SET hidden = 0 " |
michael@0 | 4447 | "WHERE id = :page_id AND frecency <> 0" |
michael@0 | 4448 | ); |
michael@0 | 4449 | NS_ENSURE_STATE(updateHiddenStmt); |
michael@0 | 4450 | rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), |
michael@0 | 4451 | aPlaceId); |
michael@0 | 4452 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4453 | |
michael@0 | 4454 | mozIStorageBaseStatement *stmts[] = { |
michael@0 | 4455 | updateFrecencyStmt.get() |
michael@0 | 4456 | , updateHiddenStmt.get() |
michael@0 | 4457 | }; |
michael@0 | 4458 | |
michael@0 | 4459 | nsRefPtr<AsyncStatementCallbackNotifier> cb = |
michael@0 | 4460 | new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED); |
michael@0 | 4461 | nsCOMPtr<mozIStoragePendingStatement> ps; |
michael@0 | 4462 | rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, |
michael@0 | 4463 | getter_AddRefs(ps)); |
michael@0 | 4464 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4465 | |
michael@0 | 4466 | return NS_OK; |
michael@0 | 4467 | } |
michael@0 | 4468 | |
michael@0 | 4469 | |
michael@0 | 4470 | namespace { |
michael@0 | 4471 | |
michael@0 | 4472 | class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier |
michael@0 | 4473 | { |
michael@0 | 4474 | public: |
michael@0 | 4475 | FixInvalidFrecenciesCallback() |
michael@0 | 4476 | : AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED) |
michael@0 | 4477 | { |
michael@0 | 4478 | } |
michael@0 | 4479 | |
michael@0 | 4480 | NS_IMETHOD HandleCompletion(uint16_t aReason) |
michael@0 | 4481 | { |
michael@0 | 4482 | nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason); |
michael@0 | 4483 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4484 | if (aReason == REASON_FINISHED) { |
michael@0 | 4485 | nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
michael@0 | 4486 | NS_ENSURE_STATE(navHistory); |
michael@0 | 4487 | navHistory->NotifyManyFrecenciesChanged(); |
michael@0 | 4488 | } |
michael@0 | 4489 | return NS_OK; |
michael@0 | 4490 | } |
michael@0 | 4491 | }; |
michael@0 | 4492 | |
michael@0 | 4493 | } // anonymous namespace |
michael@0 | 4494 | |
michael@0 | 4495 | nsresult |
michael@0 | 4496 | nsNavHistory::FixInvalidFrecencies() |
michael@0 | 4497 | { |
michael@0 | 4498 | nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
michael@0 | 4499 | "UPDATE moz_places " |
michael@0 | 4500 | "SET frecency = CALCULATE_FRECENCY(id) " |
michael@0 | 4501 | "WHERE frecency < 0" |
michael@0 | 4502 | ); |
michael@0 | 4503 | NS_ENSURE_STATE(stmt); |
michael@0 | 4504 | |
michael@0 | 4505 | nsRefPtr<FixInvalidFrecenciesCallback> callback = |
michael@0 | 4506 | new FixInvalidFrecenciesCallback(); |
michael@0 | 4507 | nsCOMPtr<mozIStoragePendingStatement> ps; |
michael@0 | 4508 | (void)stmt->ExecuteAsync(callback, getter_AddRefs(ps)); |
michael@0 | 4509 | |
michael@0 | 4510 | return NS_OK; |
michael@0 | 4511 | } |
michael@0 | 4512 | |
michael@0 | 4513 | |
michael@0 | 4514 | #ifdef MOZ_XUL |
michael@0 | 4515 | |
michael@0 | 4516 | nsresult |
michael@0 | 4517 | nsNavHistory::AutoCompleteFeedback(int32_t aIndex, |
michael@0 | 4518 | nsIAutoCompleteController *aController) |
michael@0 | 4519 | { |
michael@0 | 4520 | nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
michael@0 | 4521 | "INSERT OR REPLACE INTO moz_inputhistory " |
michael@0 | 4522 | // use_count will asymptotically approach the max of 10. |
michael@0 | 4523 | "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 " |
michael@0 | 4524 | "FROM moz_places h " |
michael@0 | 4525 | "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text " |
michael@0 | 4526 | "WHERE url = :page_url " |
michael@0 | 4527 | ); |
michael@0 | 4528 | NS_ENSURE_STATE(stmt); |
michael@0 | 4529 | |
michael@0 | 4530 | nsAutoString input; |
michael@0 | 4531 | nsresult rv = aController->GetSearchString(input); |
michael@0 | 4532 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4533 | rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input); |
michael@0 | 4534 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4535 | |
michael@0 | 4536 | nsAutoString url; |
michael@0 | 4537 | rv = aController->GetValueAt(aIndex, url); |
michael@0 | 4538 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4539 | rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), |
michael@0 | 4540 | NS_ConvertUTF16toUTF8(url)); |
michael@0 | 4541 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4542 | |
michael@0 | 4543 | // We do the update asynchronously and we do not care about failures. |
michael@0 | 4544 | nsRefPtr<AsyncStatementCallbackNotifier> callback = |
michael@0 | 4545 | new AsyncStatementCallbackNotifier(TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED); |
michael@0 | 4546 | nsCOMPtr<mozIStoragePendingStatement> canceler; |
michael@0 | 4547 | rv = stmt->ExecuteAsync(callback, getter_AddRefs(canceler)); |
michael@0 | 4548 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4549 | |
michael@0 | 4550 | return NS_OK; |
michael@0 | 4551 | } |
michael@0 | 4552 | |
michael@0 | 4553 | #endif |
michael@0 | 4554 | |
michael@0 | 4555 | |
michael@0 | 4556 | nsICollation * |
michael@0 | 4557 | nsNavHistory::GetCollation() |
michael@0 | 4558 | { |
michael@0 | 4559 | if (mCollation) |
michael@0 | 4560 | return mCollation; |
michael@0 | 4561 | |
michael@0 | 4562 | // locale |
michael@0 | 4563 | nsCOMPtr<nsILocale> locale; |
michael@0 | 4564 | nsCOMPtr<nsILocaleService> ls(do_GetService(NS_LOCALESERVICE_CONTRACTID)); |
michael@0 | 4565 | NS_ENSURE_TRUE(ls, nullptr); |
michael@0 | 4566 | nsresult rv = ls->GetApplicationLocale(getter_AddRefs(locale)); |
michael@0 | 4567 | NS_ENSURE_SUCCESS(rv, nullptr); |
michael@0 | 4568 | |
michael@0 | 4569 | // collation |
michael@0 | 4570 | nsCOMPtr<nsICollationFactory> cfact = |
michael@0 | 4571 | do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); |
michael@0 | 4572 | NS_ENSURE_TRUE(cfact, nullptr); |
michael@0 | 4573 | rv = cfact->CreateCollation(locale, getter_AddRefs(mCollation)); |
michael@0 | 4574 | NS_ENSURE_SUCCESS(rv, nullptr); |
michael@0 | 4575 | |
michael@0 | 4576 | return mCollation; |
michael@0 | 4577 | } |
michael@0 | 4578 | |
michael@0 | 4579 | nsIStringBundle * |
michael@0 | 4580 | nsNavHistory::GetBundle() |
michael@0 | 4581 | { |
michael@0 | 4582 | if (!mBundle) { |
michael@0 | 4583 | nsCOMPtr<nsIStringBundleService> bundleService = |
michael@0 | 4584 | services::GetStringBundleService(); |
michael@0 | 4585 | NS_ENSURE_TRUE(bundleService, nullptr); |
michael@0 | 4586 | nsresult rv = bundleService->CreateBundle( |
michael@0 | 4587 | "chrome://places/locale/places.properties", |
michael@0 | 4588 | getter_AddRefs(mBundle)); |
michael@0 | 4589 | NS_ENSURE_SUCCESS(rv, nullptr); |
michael@0 | 4590 | } |
michael@0 | 4591 | return mBundle; |
michael@0 | 4592 | } |
michael@0 | 4593 | |
michael@0 | 4594 | nsIStringBundle * |
michael@0 | 4595 | nsNavHistory::GetDateFormatBundle() |
michael@0 | 4596 | { |
michael@0 | 4597 | if (!mDateFormatBundle) { |
michael@0 | 4598 | nsCOMPtr<nsIStringBundleService> bundleService = |
michael@0 | 4599 | services::GetStringBundleService(); |
michael@0 | 4600 | NS_ENSURE_TRUE(bundleService, nullptr); |
michael@0 | 4601 | nsresult rv = bundleService->CreateBundle( |
michael@0 | 4602 | "chrome://global/locale/dateFormat.properties", |
michael@0 | 4603 | getter_AddRefs(mDateFormatBundle)); |
michael@0 | 4604 | NS_ENSURE_SUCCESS(rv, nullptr); |
michael@0 | 4605 | } |
michael@0 | 4606 | return mDateFormatBundle; |
michael@0 | 4607 | } |