1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/history/history_backend.h" 6 7 #include <list> 8 #include <map> 9 #include <set> 10 #include <vector> 11 12 #include "base/command_line.h" 13 #include "base/compiler_specific.h" 14 #include "base/file_util.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/memory/scoped_vector.h" 17 #include "base/message_loop.h" 18 #include "base/metrics/histogram.h" 19 #include "base/string_util.h" 20 #include "base/time.h" 21 #include "chrome/browser/autocomplete/history_url_provider.h" 22 #include "chrome/browser/bookmarks/bookmark_service.h" 23 #include "chrome/browser/history/download_create_info.h" 24 #include "chrome/browser/history/history_notifications.h" 25 #include "chrome/browser/history/history_publisher.h" 26 #include "chrome/browser/history/in_memory_history_backend.h" 27 #include "chrome/browser/history/page_usage_data.h" 28 #include "chrome/browser/history/top_sites.h" 29 #include "chrome/common/chrome_constants.h" 30 #include "chrome/common/url_constants.h" 31 #include "content/common/notification_type.h" 32 #include "googleurl/src/gurl.h" 33 #include "grit/chromium_strings.h" 34 #include "grit/generated_resources.h" 35 #include "net/base/registry_controlled_domain.h" 36 37 using base::Time; 38 using base::TimeDelta; 39 using base::TimeTicks; 40 41 /* The HistoryBackend consists of a number of components: 42 43 HistoryDatabase (stores past 3 months of history) 44 URLDatabase (stores a list of URLs) 45 DownloadDatabase (stores a list of downloads) 46 VisitDatabase (stores a list of visits for the URLs) 47 VisitSegmentDatabase (stores groups of URLs for the most visited view). 48 49 ArchivedDatabase (stores history older than 3 months) 50 URLDatabase (stores a list of URLs) 51 DownloadDatabase (stores a list of downloads) 52 VisitDatabase (stores a list of visits for the URLs) 53 54 (this does not store visit segments as they expire after 3 mos.) 55 56 TextDatabaseManager (manages multiple text database for different times) 57 TextDatabase (represents a single month of full-text index). 58 ...more TextDatabase objects... 59 60 ExpireHistoryBackend (manages moving things from HistoryDatabase to 61 the ArchivedDatabase and deleting) 62 */ 63 64 namespace history { 65 66 // How long we keep segment data for in days. Currently 3 months. 67 // This value needs to be greater or equal to 68 // MostVisitedModel::kMostVisitedScope but we don't want to introduce a direct 69 // dependency between MostVisitedModel and the history backend. 70 static const int kSegmentDataRetention = 90; 71 72 // The number of milliseconds we'll wait to do a commit, so that things are 73 // batched together. 74 static const int kCommitIntervalMs = 10000; 75 76 // The amount of time before we re-fetch the favicon. 77 static const int kFaviconRefetchDays = 7; 78 79 // GetSessionTabs returns all open tabs, or tabs closed kSessionCloseTimeWindow 80 // seconds ago. 81 static const int kSessionCloseTimeWindowSecs = 10; 82 83 // The maximum number of items we'll allow in the redirect list before 84 // deleting some. 85 static const int kMaxRedirectCount = 32; 86 87 // The number of days old a history entry can be before it is considered "old" 88 // and is archived. 89 static const int kArchiveDaysThreshold = 90; 90 91 // Converts from PageUsageData to MostVisitedURL. |redirects| is a 92 // list of redirects for this URL. Empty list means no redirects. 93 MostVisitedURL MakeMostVisitedURL(const PageUsageData& page_data, 94 const RedirectList& redirects) { 95 MostVisitedURL mv; 96 mv.url = page_data.GetURL(); 97 mv.title = page_data.GetTitle(); 98 if (redirects.empty()) { 99 // Redirects must contain at least the target url. 100 mv.redirects.push_back(mv.url); 101 } else { 102 mv.redirects = redirects; 103 if (mv.redirects[mv.redirects.size() - 1] != mv.url) { 104 // The last url must be the target url. 105 mv.redirects.push_back(mv.url); 106 } 107 } 108 return mv; 109 } 110 111 // This task is run on a timer so that commits happen at regular intervals 112 // so they are batched together. The important thing about this class is that 113 // it supports canceling of the task so the reference to the backend will be 114 // freed. The problem is that when history is shutting down, there is likely 115 // to be one of these commits still pending and holding a reference. 116 // 117 // The backend can call Cancel to have this task release the reference. The 118 // task will still run (if we ever get to processing the event before 119 // shutdown), but it will not do anything. 120 // 121 // Note that this is a refcounted object and is not a task in itself. It should 122 // be assigned to a RunnableMethod. 123 // 124 // TODO(brettw): bug 1165182: This should be replaced with a 125 // ScopedRunnableMethodFactory which will handle everything automatically (like 126 // we do in ExpireHistoryBackend). 127 class CommitLaterTask : public base::RefCounted<CommitLaterTask> { 128 public: 129 explicit CommitLaterTask(HistoryBackend* history_backend) 130 : history_backend_(history_backend) { 131 } 132 133 // The backend will call this function if it is being destroyed so that we 134 // release our reference. 135 void Cancel() { 136 history_backend_ = NULL; 137 } 138 139 void RunCommit() { 140 if (history_backend_.get()) 141 history_backend_->Commit(); 142 } 143 144 private: 145 friend class base::RefCounted<CommitLaterTask>; 146 147 ~CommitLaterTask() {} 148 149 scoped_refptr<HistoryBackend> history_backend_; 150 }; 151 152 // Handles querying first the main database, then the full text database if that 153 // fails. It will optionally keep track of all URLs seen so duplicates can be 154 // eliminated. This is used by the querying sub-functions. 155 // 156 // TODO(brettw): This class may be able to be simplified or eliminated. After 157 // this was written, QueryResults can efficiently look up by URL, so the need 158 // for this extra set of previously queried URLs is less important. 159 class HistoryBackend::URLQuerier { 160 public: 161 URLQuerier(URLDatabase* main_db, URLDatabase* archived_db, bool track_unique) 162 : main_db_(main_db), 163 archived_db_(archived_db), 164 track_unique_(track_unique) { 165 } 166 167 // When we're tracking unique URLs, returns true if this URL has been 168 // previously queried. Only call when tracking unique URLs. 169 bool HasURL(const GURL& url) { 170 DCHECK(track_unique_); 171 return unique_urls_.find(url) != unique_urls_.end(); 172 } 173 174 bool GetRowForURL(const GURL& url, URLRow* row) { 175 if (!main_db_->GetRowForURL(url, row)) { 176 if (!archived_db_ || !archived_db_->GetRowForURL(url, row)) { 177 // This row is neither in the main nor the archived DB. 178 return false; 179 } 180 } 181 182 if (track_unique_) 183 unique_urls_.insert(url); 184 return true; 185 } 186 187 private: 188 URLDatabase* main_db_; // Guaranteed non-NULL. 189 URLDatabase* archived_db_; // Possibly NULL. 190 191 bool track_unique_; 192 193 // When track_unique_ is set, this is updated with every URL seen so far. 194 std::set<GURL> unique_urls_; 195 196 DISALLOW_COPY_AND_ASSIGN(URLQuerier); 197 }; 198 199 // HistoryBackend -------------------------------------------------------------- 200 201 HistoryBackend::HistoryBackend(const FilePath& history_dir, 202 Delegate* delegate, 203 BookmarkService* bookmark_service) 204 : delegate_(delegate), 205 history_dir_(history_dir), 206 ALLOW_THIS_IN_INITIALIZER_LIST(expirer_(this, bookmark_service)), 207 recent_redirects_(kMaxRedirectCount), 208 backend_destroy_message_loop_(NULL), 209 backend_destroy_task_(NULL), 210 segment_queried_(false), 211 bookmark_service_(bookmark_service) { 212 } 213 214 HistoryBackend::~HistoryBackend() { 215 DCHECK(!scheduled_commit_) << "Deleting without cleanup"; 216 ReleaseDBTasks(); 217 218 // First close the databases before optionally running the "destroy" task. 219 if (db_.get()) { 220 // Commit the long-running transaction. 221 db_->CommitTransaction(); 222 db_.reset(); 223 } 224 if (thumbnail_db_.get()) { 225 thumbnail_db_->CommitTransaction(); 226 thumbnail_db_.reset(); 227 } 228 if (archived_db_.get()) { 229 archived_db_->CommitTransaction(); 230 archived_db_.reset(); 231 } 232 if (text_database_.get()) { 233 text_database_->CommitTransaction(); 234 text_database_.reset(); 235 } 236 237 if (backend_destroy_task_) { 238 // Notify an interested party (typically a unit test) that we're done. 239 DCHECK(backend_destroy_message_loop_); 240 backend_destroy_message_loop_->PostTask(FROM_HERE, backend_destroy_task_); 241 } 242 } 243 244 void HistoryBackend::Init(const std::string& languages, bool force_fail) { 245 if (!force_fail) 246 InitImpl(languages); 247 delegate_->DBLoaded(); 248 } 249 250 void HistoryBackend::SetOnBackendDestroyTask(MessageLoop* message_loop, 251 Task* task) { 252 if (backend_destroy_task_) { 253 DLOG(WARNING) << "Setting more than one destroy task, overriding"; 254 delete backend_destroy_task_; 255 } 256 backend_destroy_message_loop_ = message_loop; 257 backend_destroy_task_ = task; 258 } 259 260 void HistoryBackend::Closing() { 261 // Any scheduled commit will have a reference to us, we must make it 262 // release that reference before we can be destroyed. 263 CancelScheduledCommit(); 264 265 // Release our reference to the delegate, this reference will be keeping the 266 // history service alive. 267 delegate_.reset(); 268 } 269 270 void HistoryBackend::NotifyRenderProcessHostDestruction(const void* host) { 271 tracker_.NotifyRenderProcessHostDestruction(host); 272 } 273 274 FilePath HistoryBackend::GetThumbnailFileName() const { 275 return history_dir_.Append(chrome::kThumbnailsFilename); 276 } 277 278 FilePath HistoryBackend::GetFaviconsFileName() const { 279 return history_dir_.Append(chrome::kFaviconsFilename); 280 } 281 282 FilePath HistoryBackend::GetArchivedFileName() const { 283 return history_dir_.Append(chrome::kArchivedHistoryFilename); 284 } 285 286 SegmentID HistoryBackend::GetLastSegmentID(VisitID from_visit) { 287 // Set is used to detect referrer loops. Should not happen, but can 288 // if the database is corrupt. 289 std::set<VisitID> visit_set; 290 VisitID visit_id = from_visit; 291 while (visit_id) { 292 VisitRow row; 293 if (!db_->GetRowForVisit(visit_id, &row)) 294 return 0; 295 if (row.segment_id) 296 return row.segment_id; // Found a visit in this change with a segment. 297 298 // Check the referrer of this visit, if any. 299 visit_id = row.referring_visit; 300 301 if (visit_set.find(visit_id) != visit_set.end()) { 302 NOTREACHED() << "Loop in referer chain, giving up"; 303 break; 304 } 305 visit_set.insert(visit_id); 306 } 307 return 0; 308 } 309 310 SegmentID HistoryBackend::UpdateSegments(const GURL& url, 311 VisitID from_visit, 312 VisitID visit_id, 313 PageTransition::Type transition_type, 314 const Time ts) { 315 if (!db_.get()) 316 return 0; 317 318 // We only consider main frames. 319 if (!PageTransition::IsMainFrame(transition_type)) 320 return 0; 321 322 SegmentID segment_id = 0; 323 PageTransition::Type t = PageTransition::StripQualifier(transition_type); 324 325 // Are we at the beginning of a new segment? 326 if (t == PageTransition::TYPED || t == PageTransition::AUTO_BOOKMARK) { 327 // If so, create or get the segment. 328 std::string segment_name = db_->ComputeSegmentName(url); 329 URLID url_id = db_->GetRowForURL(url, NULL); 330 if (!url_id) 331 return 0; 332 333 if (!(segment_id = db_->GetSegmentNamed(segment_name))) { 334 if (!(segment_id = db_->CreateSegment(url_id, segment_name))) { 335 NOTREACHED(); 336 return 0; 337 } 338 } else { 339 // Note: if we update an existing segment, we update the url used to 340 // represent that segment in order to minimize stale most visited 341 // images. 342 db_->UpdateSegmentRepresentationURL(segment_id, url_id); 343 } 344 } else { 345 // Note: it is possible there is no segment ID set for this visit chain. 346 // This can happen if the initial navigation wasn't AUTO_BOOKMARK or 347 // TYPED. (For example GENERATED). In this case this visit doesn't count 348 // toward any segment. 349 if (!(segment_id = GetLastSegmentID(from_visit))) 350 return 0; 351 } 352 353 // Set the segment in the visit. 354 if (!db_->SetSegmentID(visit_id, segment_id)) { 355 NOTREACHED(); 356 return 0; 357 } 358 359 // Finally, increase the counter for that segment / day. 360 if (!db_->IncreaseSegmentVisitCount(segment_id, ts, 1)) { 361 NOTREACHED(); 362 return 0; 363 } 364 return segment_id; 365 } 366 367 void HistoryBackend::AddPage(scoped_refptr<HistoryAddPageArgs> request) { 368 if (!db_.get()) 369 return; 370 371 // Will be filled with the URL ID and the visit ID of the last addition. 372 std::pair<URLID, VisitID> last_ids(0, tracker_.GetLastVisit( 373 request->id_scope, request->page_id, request->referrer)); 374 375 VisitID from_visit_id = last_ids.second; 376 377 // If a redirect chain is given, we expect the last item in that chain to be 378 // the final URL. 379 DCHECK(request->redirects.empty() || 380 request->redirects.back() == request->url); 381 382 // Avoid duplicating times in the database, at least as long as pages are 383 // added in order. However, we don't want to disallow pages from recording 384 // times earlier than our last_recorded_time_, because someone might set 385 // their machine's clock back. 386 if (last_requested_time_ == request->time) { 387 last_recorded_time_ = last_recorded_time_ + TimeDelta::FromMicroseconds(1); 388 } else { 389 last_requested_time_ = request->time; 390 last_recorded_time_ = last_requested_time_; 391 } 392 393 // If the user is adding older history, we need to make sure our times 394 // are correct. 395 if (request->time < first_recorded_time_) 396 first_recorded_time_ = request->time; 397 398 PageTransition::Type transition = 399 PageTransition::StripQualifier(request->transition); 400 bool is_keyword_generated = (transition == PageTransition::KEYWORD_GENERATED); 401 402 if (request->redirects.size() <= 1) { 403 // The single entry is both a chain start and end. 404 PageTransition::Type t = request->transition | 405 PageTransition::CHAIN_START | PageTransition::CHAIN_END; 406 407 // No redirect case (one element means just the page itself). 408 last_ids = AddPageVisit(request->url, last_recorded_time_, 409 last_ids.second, t, request->visit_source); 410 411 // Update the segment for this visit. KEYWORD_GENERATED visits should not 412 // result in changing most visited, so we don't update segments (most 413 // visited db). 414 if (!is_keyword_generated) { 415 UpdateSegments(request->url, from_visit_id, last_ids.second, t, 416 last_recorded_time_); 417 } 418 } else { 419 // Redirect case. Add the redirect chain. 420 421 PageTransition::Type redirect_info = PageTransition::CHAIN_START; 422 423 if (request->redirects[0].SchemeIs(chrome::kAboutScheme)) { 424 // When the redirect source + referrer is "about" we skip it. This 425 // happens when a page opens a new frame/window to about:blank and then 426 // script sets the URL to somewhere else (used to hide the referrer). It 427 // would be nice to keep all these redirects properly but we don't ever 428 // see the initial about:blank load, so we don't know where the 429 // subsequent client redirect came from. 430 // 431 // In this case, we just don't bother hooking up the source of the 432 // redirects, so we remove it. 433 request->redirects.erase(request->redirects.begin()); 434 } else if (request->transition & PageTransition::CLIENT_REDIRECT) { 435 redirect_info = PageTransition::CLIENT_REDIRECT; 436 // The first entry in the redirect chain initiated a client redirect. 437 // We don't add this to the database since the referrer is already 438 // there, so we skip over it but change the transition type of the first 439 // transition to client redirect. 440 // 441 // The referrer is invalid when restoring a session that features an 442 // https tab that redirects to a different host or to http. In this 443 // case we don't need to reconnect the new redirect with the existing 444 // chain. 445 if (request->referrer.is_valid()) { 446 DCHECK(request->referrer == request->redirects[0]); 447 request->redirects.erase(request->redirects.begin()); 448 449 // If the navigation entry for this visit has replaced that for the 450 // first visit, remove the CHAIN_END marker from the first visit. This 451 // can be called a lot, for example, the page cycler, and most of the 452 // time we won't have changed anything. 453 VisitRow visit_row; 454 if (request->did_replace_entry && 455 db_->GetRowForVisit(last_ids.second, &visit_row) && 456 visit_row.transition | PageTransition::CHAIN_END) { 457 visit_row.transition &= ~PageTransition::CHAIN_END; 458 db_->UpdateVisitRow(visit_row); 459 } 460 } 461 } 462 463 for (size_t redirect_index = 0; redirect_index < request->redirects.size(); 464 redirect_index++) { 465 PageTransition::Type t = transition | redirect_info; 466 467 // If this is the last transition, add a CHAIN_END marker 468 if (redirect_index == (request->redirects.size() - 1)) 469 t = t | PageTransition::CHAIN_END; 470 471 // Record all redirect visits with the same timestamp. We don't display 472 // them anyway, and if we ever decide to, we can reconstruct their order 473 // from the redirect chain. 474 last_ids = AddPageVisit(request->redirects[redirect_index], 475 last_recorded_time_, last_ids.second, 476 t, request->visit_source); 477 if (t & PageTransition::CHAIN_START) { 478 // Update the segment for this visit. 479 UpdateSegments(request->redirects[redirect_index], 480 from_visit_id, last_ids.second, t, last_recorded_time_); 481 } 482 483 // Subsequent transitions in the redirect list must all be sever 484 // redirects. 485 redirect_info = PageTransition::SERVER_REDIRECT; 486 } 487 488 // Last, save this redirect chain for later so we can set titles & favicons 489 // on the redirected pages properly. It is indexed by the destination page. 490 recent_redirects_.Put(request->url, request->redirects); 491 } 492 493 // TODO(brettw) bug 1140015: Add an "add page" notification so the history 494 // views can keep in sync. 495 496 // Add the last visit to the tracker so we can get outgoing transitions. 497 // TODO(evanm): Due to http://b/1194536 we lose the referrers of a subframe 498 // navigation anyway, so last_visit_id is always zero for them. But adding 499 // them here confuses main frame history, so we skip them for now. 500 if (transition != PageTransition::AUTO_SUBFRAME && 501 transition != PageTransition::MANUAL_SUBFRAME && !is_keyword_generated) { 502 tracker_.AddVisit(request->id_scope, request->page_id, request->url, 503 last_ids.second); 504 } 505 506 if (text_database_.get()) { 507 text_database_->AddPageURL(request->url, last_ids.first, last_ids.second, 508 last_recorded_time_); 509 } 510 511 ScheduleCommit(); 512 } 513 514 void HistoryBackend::InitImpl(const std::string& languages) { 515 DCHECK(!db_.get()) << "Initializing HistoryBackend twice"; 516 // In the rare case where the db fails to initialize a dialog may get shown 517 // the blocks the caller, yet allows other messages through. For this reason 518 // we only set db_ to the created database if creation is successful. That 519 // way other methods won't do anything as db_ is still NULL. 520 521 TimeTicks beginning_time = TimeTicks::Now(); 522 523 // Compute the file names. Note that the index file can be removed when the 524 // text db manager is finished being hooked up. 525 FilePath history_name = history_dir_.Append(chrome::kHistoryFilename); 526 FilePath thumbnail_name = GetThumbnailFileName(); 527 FilePath archived_name = GetArchivedFileName(); 528 FilePath tmp_bookmarks_file = history_dir_.Append( 529 chrome::kHistoryBookmarksFileName); 530 531 // History database. 532 db_.reset(new HistoryDatabase()); 533 sql::InitStatus status = db_->Init(history_name, tmp_bookmarks_file); 534 switch (status) { 535 case sql::INIT_OK: 536 break; 537 case sql::INIT_FAILURE: 538 // A NULL db_ will cause all calls on this object to notice this error 539 // and to not continue. 540 delegate_->NotifyProfileError(status); 541 db_.reset(); 542 return; 543 default: 544 NOTREACHED(); 545 } 546 547 // Fill the in-memory database and send it back to the history service on the 548 // main thread. 549 InMemoryHistoryBackend* mem_backend = new InMemoryHistoryBackend; 550 if (mem_backend->Init(history_name, history_dir_, db_.get(), languages)) 551 delegate_->SetInMemoryBackend(mem_backend); // Takes ownership of pointer. 552 else 553 delete mem_backend; // Error case, run without the in-memory DB. 554 db_->BeginExclusiveMode(); // Must be after the mem backend read the data. 555 556 // Create the history publisher which needs to be passed on to the text and 557 // thumbnail databases for publishing history. 558 history_publisher_.reset(new HistoryPublisher()); 559 if (!history_publisher_->Init()) { 560 // The init may fail when there are no indexers wanting our history. 561 // Hence no need to log the failure. 562 history_publisher_.reset(); 563 } 564 565 // Full-text database. This has to be first so we can pass it to the 566 // HistoryDatabase for migration. 567 text_database_.reset(new TextDatabaseManager(history_dir_, 568 db_.get(), db_.get())); 569 if (!text_database_->Init(history_publisher_.get())) { 570 LOG(WARNING) << "Text database initialization failed, running without it."; 571 text_database_.reset(); 572 } 573 if (db_->needs_version_17_migration()) { 574 // See needs_version_17_migration() decl for more. In this case, we want 575 // to erase all the text database files. This must be done after the text 576 // database manager has been initialized, since it knows about all the 577 // files it manages. 578 text_database_->DeleteAll(); 579 } 580 581 // Thumbnail database. 582 thumbnail_db_.reset(new ThumbnailDatabase()); 583 if (!db_->GetNeedsThumbnailMigration()) { 584 // No convertion needed - use new filename right away. 585 thumbnail_name = GetFaviconsFileName(); 586 } 587 if (thumbnail_db_->Init(thumbnail_name, 588 history_publisher_.get(), 589 db_.get()) != sql::INIT_OK) { 590 // Unlike the main database, we don't error out when the database is too 591 // new because this error is much less severe. Generally, this shouldn't 592 // happen since the thumbnail and main datbase versions should be in sync. 593 // We'll just continue without thumbnails & favicons in this case or any 594 // other error. 595 LOG(WARNING) << "Could not initialize the thumbnail database."; 596 thumbnail_db_.reset(); 597 } 598 599 if (db_->GetNeedsThumbnailMigration()) { 600 VLOG(1) << "Starting TopSites migration"; 601 delegate_->StartTopSitesMigration(); 602 } 603 604 // Archived database. 605 if (db_->needs_version_17_migration()) { 606 // See needs_version_17_migration() decl for more. In this case, we want 607 // to delete the archived database and need to do so before we try to 608 // open the file. We can ignore any error (maybe the file doesn't exist). 609 file_util::Delete(archived_name, false); 610 } 611 archived_db_.reset(new ArchivedDatabase()); 612 if (!archived_db_->Init(archived_name)) { 613 LOG(WARNING) << "Could not initialize the archived database."; 614 archived_db_.reset(); 615 } 616 617 // Tell the expiration module about all the nice databases we made. This must 618 // happen before db_->Init() is called since the callback ForceArchiveHistory 619 // may need to expire stuff. 620 // 621 // *sigh*, this can all be cleaned up when that migration code is removed. 622 // The main DB initialization should intuitively be first (not that it 623 // actually matters) and the expirer should be set last. 624 expirer_.SetDatabases(db_.get(), archived_db_.get(), 625 thumbnail_db_.get(), text_database_.get()); 626 627 // Open the long-running transaction. 628 db_->BeginTransaction(); 629 if (thumbnail_db_.get()) 630 thumbnail_db_->BeginTransaction(); 631 if (archived_db_.get()) 632 archived_db_->BeginTransaction(); 633 if (text_database_.get()) 634 text_database_->BeginTransaction(); 635 636 // Get the first item in our database. 637 db_->GetStartDate(&first_recorded_time_); 638 639 // Start expiring old stuff. 640 expirer_.StartArchivingOldStuff(TimeDelta::FromDays(kArchiveDaysThreshold)); 641 642 HISTOGRAM_TIMES("History.InitTime", 643 TimeTicks::Now() - beginning_time); 644 } 645 646 std::pair<URLID, VisitID> HistoryBackend::AddPageVisit( 647 const GURL& url, 648 Time time, 649 VisitID referring_visit, 650 PageTransition::Type transition, 651 VisitSource visit_source) { 652 // Top-level frame navigations are visible, everything else is hidden 653 bool new_hidden = !PageTransition::IsMainFrame(transition); 654 655 // NOTE: This code must stay in sync with 656 // ExpireHistoryBackend::ExpireURLsForVisits(). 657 // TODO(pkasting): http://b/1148304 We shouldn't be marking so many URLs as 658 // typed, which would eliminate the need for this code. 659 int typed_increment = 0; 660 PageTransition::Type transition_type = 661 PageTransition::StripQualifier(transition); 662 if ((transition_type == PageTransition::TYPED && 663 !PageTransition::IsRedirect(transition)) || 664 transition_type == PageTransition::KEYWORD_GENERATED) 665 typed_increment = 1; 666 667 // See if this URL is already in the DB. 668 URLRow url_info(url); 669 URLID url_id = db_->GetRowForURL(url, &url_info); 670 if (url_id) { 671 // Update of an existing row. 672 if (PageTransition::StripQualifier(transition) != PageTransition::RELOAD) 673 url_info.set_visit_count(url_info.visit_count() + 1); 674 if (typed_increment) 675 url_info.set_typed_count(url_info.typed_count() + typed_increment); 676 url_info.set_last_visit(time); 677 678 // Only allow un-hiding of pages, never hiding. 679 if (!new_hidden) 680 url_info.set_hidden(false); 681 682 db_->UpdateURLRow(url_id, url_info); 683 } else { 684 // Addition of a new row. 685 url_info.set_visit_count(1); 686 url_info.set_typed_count(typed_increment); 687 url_info.set_last_visit(time); 688 url_info.set_hidden(new_hidden); 689 690 url_id = db_->AddURL(url_info); 691 if (!url_id) { 692 NOTREACHED() << "Adding URL failed."; 693 return std::make_pair(0, 0); 694 } 695 url_info.id_ = url_id; 696 697 // We don't actually add the URL to the full text index at this point. It 698 // might be nice to do this so that even if we get no title or body, the 699 // user can search for URL components and get the page. 700 // 701 // However, in most cases, we'll get at least a title and usually contents, 702 // and this add will be redundant, slowing everything down. As a result, 703 // we ignore this edge case. 704 } 705 706 // Add the visit with the time to the database. 707 VisitRow visit_info(url_id, time, referring_visit, transition, 0); 708 VisitID visit_id = db_->AddVisit(&visit_info, visit_source); 709 710 if (visit_info.visit_time < first_recorded_time_) 711 first_recorded_time_ = visit_info.visit_time; 712 713 // Broadcast a notification of the visit. 714 if (visit_id) { 715 URLVisitedDetails* details = new URLVisitedDetails; 716 details->transition = transition; 717 details->row = url_info; 718 // TODO(meelapshah) Disabled due to potential PageCycler regression. 719 // Re-enable this. 720 // GetMostRecentRedirectsTo(url, &details->redirects); 721 BroadcastNotifications(NotificationType::HISTORY_URL_VISITED, details); 722 } else { 723 VLOG(0) << "Failed to build visit insert statement: " 724 << "url_id = " << url_id; 725 } 726 727 return std::make_pair(url_id, visit_id); 728 } 729 730 void HistoryBackend::AddPagesWithDetails(const std::vector<URLRow>& urls, 731 VisitSource visit_source) { 732 if (!db_.get()) 733 return; 734 735 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails); 736 for (std::vector<URLRow>::const_iterator i = urls.begin(); 737 i != urls.end(); ++i) { 738 DCHECK(!i->last_visit().is_null()); 739 740 // We will add to either the archived database or the main one depending on 741 // the date of the added visit. 742 URLDatabase* url_database; 743 VisitDatabase* visit_database; 744 if (i->last_visit() < expirer_.GetCurrentArchiveTime()) { 745 if (!archived_db_.get()) 746 return; // No archived database to save it to, just forget this. 747 url_database = archived_db_.get(); 748 visit_database = archived_db_.get(); 749 } else { 750 url_database = db_.get(); 751 visit_database = db_.get(); 752 } 753 754 URLRow existing_url; 755 URLID url_id = url_database->GetRowForURL(i->url(), &existing_url); 756 if (!url_id) { 757 // Add the page if it doesn't exist. 758 url_id = url_database->AddURL(*i); 759 if (!url_id) { 760 NOTREACHED() << "Could not add row to DB"; 761 return; 762 } 763 764 if (i->typed_count() > 0) 765 modified->changed_urls.push_back(*i); 766 } 767 768 // Add the page to the full text index. This function is also used for 769 // importing. Even though we don't have page contents, we can at least 770 // add the title and URL to the index so they can be searched. We don't 771 // bother to delete any already-existing FTS entries for the URL, since 772 // this is normally called on import. 773 // 774 // If you ever import *after* first run (selecting import from the menu), 775 // then these additional entries will "shadow" the originals when querying 776 // for the most recent match only, and the user won't get snippets. This is 777 // a very minor issue, and fixing it will make import slower, so we don't 778 // bother. 779 bool has_indexed = false; 780 if (text_database_.get()) { 781 // We do not have to make it update the visit database, below, we will 782 // create the visit entry with the indexed flag set. 783 has_indexed = text_database_->AddPageData(i->url(), url_id, 0, 784 i->last_visit(), 785 i->title(), string16()); 786 } 787 788 // Make up a visit to correspond to that page. 789 VisitRow visit_info(url_id, i->last_visit(), 0, 790 PageTransition::LINK | PageTransition::CHAIN_START | 791 PageTransition::CHAIN_END, 0); 792 visit_info.is_indexed = has_indexed; 793 if (!visit_database->AddVisit(&visit_info, visit_source)) { 794 NOTREACHED() << "Adding visit failed."; 795 return; 796 } 797 798 if (visit_info.visit_time < first_recorded_time_) 799 first_recorded_time_ = visit_info.visit_time; 800 } 801 802 // Broadcast a notification for typed URLs that have been modified. This 803 // will be picked up by the in-memory URL database on the main thread. 804 // 805 // TODO(brettw) bug 1140015: Add an "add page" notification so the history 806 // views can keep in sync. 807 BroadcastNotifications(NotificationType::HISTORY_TYPED_URLS_MODIFIED, 808 modified.release()); 809 810 ScheduleCommit(); 811 } 812 813 void HistoryBackend::SetPageTitle(const GURL& url, 814 const string16& title) { 815 if (!db_.get()) 816 return; 817 818 // Search for recent redirects which should get the same title. We make a 819 // dummy list containing the exact URL visited if there are no redirects so 820 // the processing below can be the same. 821 history::RedirectList dummy_list; 822 history::RedirectList* redirects; 823 RedirectCache::iterator iter = recent_redirects_.Get(url); 824 if (iter != recent_redirects_.end()) { 825 redirects = &iter->second; 826 827 // This redirect chain should have the destination URL as the last item. 828 DCHECK(!redirects->empty()); 829 DCHECK(redirects->back() == url); 830 } else { 831 // No redirect chain stored, make up one containing the URL we want so we 832 // can use the same logic below. 833 dummy_list.push_back(url); 834 redirects = &dummy_list; 835 } 836 837 bool typed_url_changed = false; 838 std::vector<URLRow> changed_urls; 839 for (size_t i = 0; i < redirects->size(); i++) { 840 URLRow row; 841 URLID row_id = db_->GetRowForURL(redirects->at(i), &row); 842 if (row_id && row.title() != title) { 843 row.set_title(title); 844 db_->UpdateURLRow(row_id, row); 845 changed_urls.push_back(row); 846 if (row.typed_count() > 0) 847 typed_url_changed = true; 848 } 849 } 850 851 // Broadcast notifications for typed URLs that have changed. This will 852 // update the in-memory database. 853 // 854 // TODO(brettw) bug 1140020: Broadcast for all changes (not just typed), 855 // in which case some logic can be removed. 856 if (typed_url_changed) { 857 URLsModifiedDetails* modified = 858 new URLsModifiedDetails; 859 for (size_t i = 0; i < changed_urls.size(); i++) { 860 if (changed_urls[i].typed_count() > 0) 861 modified->changed_urls.push_back(changed_urls[i]); 862 } 863 BroadcastNotifications(NotificationType::HISTORY_TYPED_URLS_MODIFIED, 864 modified); 865 } 866 867 // Update the full text index. 868 if (text_database_.get()) 869 text_database_->AddPageTitle(url, title); 870 871 // Only bother committing if things changed. 872 if (!changed_urls.empty()) 873 ScheduleCommit(); 874 } 875 876 void HistoryBackend::AddPageNoVisitForBookmark(const GURL& url) { 877 if (!db_.get()) 878 return; 879 880 URLRow url_info(url); 881 URLID url_id = db_->GetRowForURL(url, &url_info); 882 if (url_id) { 883 // URL is already known, nothing to do. 884 return; 885 } 886 url_info.set_last_visit(Time::Now()); 887 // Mark the page hidden. If the user types it in, it'll unhide. 888 url_info.set_hidden(true); 889 890 db_->AddURL(url_info); 891 } 892 893 void HistoryBackend::IterateURLs(HistoryService::URLEnumerator* iterator) { 894 if (db_.get()) { 895 HistoryDatabase::URLEnumerator e; 896 if (db_->InitURLEnumeratorForEverything(&e)) { 897 URLRow info; 898 while (e.GetNextURL(&info)) { 899 iterator->OnURL(info.url()); 900 } 901 iterator->OnComplete(true); // Success. 902 return; 903 } 904 } 905 iterator->OnComplete(false); // Failure. 906 } 907 908 bool HistoryBackend::GetAllTypedURLs(std::vector<history::URLRow>* urls) { 909 if (db_.get()) 910 return db_->GetAllTypedUrls(urls); 911 return false; 912 } 913 914 bool HistoryBackend::GetVisitsForURL(URLID id, VisitVector* visits) { 915 if (db_.get()) 916 return db_->GetVisitsForURL(id, visits); 917 return false; 918 } 919 920 bool HistoryBackend::UpdateURL(URLID id, const history::URLRow& url) { 921 if (db_.get()) 922 return db_->UpdateURLRow(id, url); 923 return false; 924 } 925 926 bool HistoryBackend::AddVisits(const GURL& url, 927 const std::vector<base::Time>& visits, 928 VisitSource visit_source) { 929 if (db_.get()) { 930 for (std::vector<base::Time>::const_iterator visit = visits.begin(); 931 visit != visits.end(); ++visit) { 932 if (!AddPageVisit(url, *visit, 0, 0, visit_source).first) { 933 return false; 934 } 935 } 936 ScheduleCommit(); 937 return true; 938 } 939 return false; 940 } 941 942 bool HistoryBackend::RemoveVisits(const VisitVector& visits) { 943 if (db_.get()) { 944 std::map<URLID, int> url_visits_removed; 945 for (VisitVector::const_iterator visit = visits.begin(); 946 visit != visits.end(); ++visit) { 947 db_->DeleteVisit(*visit); 948 std::map<URLID, int>::iterator visit_count = 949 url_visits_removed.find(visit->url_id); 950 if (visit_count == url_visits_removed.end()) { 951 url_visits_removed[visit->url_id] = 1; 952 } else { 953 ++visit_count->second; 954 } 955 } 956 for (std::map<URLID, int>::iterator count = url_visits_removed.begin(); 957 count != url_visits_removed.end(); ++count) { 958 history::URLRow url_row; 959 if (!db_->GetURLRow(count->first, &url_row)) { 960 return false; 961 } 962 DCHECK(count->second <= url_row.visit_count()); 963 url_row.set_visit_count(url_row.visit_count() - count->second); 964 if (!db_->UpdateURLRow(url_row.id(), url_row)) { 965 return false; 966 } 967 } 968 ScheduleCommit(); 969 return true; 970 } 971 return false; 972 } 973 974 bool HistoryBackend::GetURL(const GURL& url, history::URLRow* url_row) { 975 if (db_.get()) 976 return db_->GetRowForURL(url, url_row) != 0; 977 return false; 978 } 979 980 void HistoryBackend::QueryURL(scoped_refptr<QueryURLRequest> request, 981 const GURL& url, 982 bool want_visits) { 983 if (request->canceled()) 984 return; 985 986 bool success = false; 987 URLRow* row = &request->value.a; 988 VisitVector* visits = &request->value.b; 989 if (db_.get()) { 990 if (db_->GetRowForURL(url, row)) { 991 // Have a row. 992 success = true; 993 994 // Optionally query the visits. 995 if (want_visits) 996 db_->GetVisitsForURL(row->id(), visits); 997 } 998 } 999 request->ForwardResult(QueryURLRequest::TupleType(request->handle(), success, 1000 row, visits)); 1001 } 1002 1003 // Segment usage --------------------------------------------------------------- 1004 1005 void HistoryBackend::DeleteOldSegmentData() { 1006 if (db_.get()) 1007 db_->DeleteSegmentData(Time::Now() - 1008 TimeDelta::FromDays(kSegmentDataRetention)); 1009 } 1010 1011 void HistoryBackend::SetSegmentPresentationIndex(SegmentID segment_id, 1012 int index) { 1013 if (db_.get()) 1014 db_->SetSegmentPresentationIndex(segment_id, index); 1015 } 1016 1017 void HistoryBackend::QuerySegmentUsage( 1018 scoped_refptr<QuerySegmentUsageRequest> request, 1019 const Time from_time, 1020 int max_result_count) { 1021 if (request->canceled()) 1022 return; 1023 1024 if (db_.get()) { 1025 db_->QuerySegmentUsage(from_time, max_result_count, &request->value.get()); 1026 1027 // If this is the first time we query segments, invoke 1028 // DeleteOldSegmentData asynchronously. We do this to cleanup old 1029 // entries. 1030 if (!segment_queried_) { 1031 segment_queried_ = true; 1032 MessageLoop::current()->PostTask(FROM_HERE, 1033 NewRunnableMethod(this, &HistoryBackend::DeleteOldSegmentData)); 1034 } 1035 } 1036 request->ForwardResult( 1037 QuerySegmentUsageRequest::TupleType(request->handle(), 1038 &request->value.get())); 1039 } 1040 1041 // Keyword visits -------------------------------------------------------------- 1042 1043 void HistoryBackend::SetKeywordSearchTermsForURL(const GURL& url, 1044 TemplateURLID keyword_id, 1045 const string16& term) { 1046 if (!db_.get()) 1047 return; 1048 1049 // Get the ID for this URL. 1050 URLRow url_row; 1051 if (!db_->GetRowForURL(url, &url_row)) { 1052 // There is a small possibility the url was deleted before the keyword 1053 // was added. Ignore the request. 1054 return; 1055 } 1056 1057 db_->SetKeywordSearchTermsForURL(url_row.id(), keyword_id, term); 1058 1059 // details is deleted by BroadcastNotifications. 1060 KeywordSearchTermDetails* details = new KeywordSearchTermDetails; 1061 details->url = url; 1062 details->keyword_id = keyword_id; 1063 details->term = term; 1064 BroadcastNotifications(NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED, 1065 details); 1066 ScheduleCommit(); 1067 } 1068 1069 void HistoryBackend::DeleteAllSearchTermsForKeyword( 1070 TemplateURLID keyword_id) { 1071 if (!db_.get()) 1072 return; 1073 1074 db_->DeleteAllSearchTermsForKeyword(keyword_id); 1075 // TODO(sky): bug 1168470. Need to move from archive dbs too. 1076 ScheduleCommit(); 1077 } 1078 1079 void HistoryBackend::GetMostRecentKeywordSearchTerms( 1080 scoped_refptr<GetMostRecentKeywordSearchTermsRequest> request, 1081 TemplateURLID keyword_id, 1082 const string16& prefix, 1083 int max_count) { 1084 if (request->canceled()) 1085 return; 1086 1087 if (db_.get()) { 1088 db_->GetMostRecentKeywordSearchTerms(keyword_id, prefix, max_count, 1089 &(request->value)); 1090 } 1091 request->ForwardResult( 1092 GetMostRecentKeywordSearchTermsRequest::TupleType(request->handle(), 1093 &request->value)); 1094 } 1095 1096 // Downloads ------------------------------------------------------------------- 1097 1098 // Get all the download entries from the database. 1099 void HistoryBackend::QueryDownloads( 1100 scoped_refptr<DownloadQueryRequest> request) { 1101 if (request->canceled()) 1102 return; 1103 if (db_.get()) 1104 db_->QueryDownloads(&request->value); 1105 request->ForwardResult(DownloadQueryRequest::TupleType(&request->value)); 1106 } 1107 1108 // Clean up entries that has been corrupted (because of the crash, for example). 1109 void HistoryBackend::CleanUpInProgressEntries() { 1110 if (db_.get()) { 1111 // If some "in progress" entries were not updated when Chrome exited, they 1112 // need to be cleaned up. 1113 db_->CleanUpInProgressEntries(); 1114 } 1115 } 1116 1117 // Update a particular download entry. 1118 void HistoryBackend::UpdateDownload(int64 received_bytes, 1119 int32 state, 1120 int64 db_handle) { 1121 if (db_.get()) 1122 db_->UpdateDownload(received_bytes, state, db_handle); 1123 } 1124 1125 // Update the path of a particular download entry. 1126 void HistoryBackend::UpdateDownloadPath(const FilePath& path, 1127 int64 db_handle) { 1128 if (db_.get()) 1129 db_->UpdateDownloadPath(path, db_handle); 1130 } 1131 1132 // Create a new download entry and pass back the db_handle to it. 1133 void HistoryBackend::CreateDownload( 1134 scoped_refptr<DownloadCreateRequest> request, 1135 const DownloadCreateInfo& create_info) { 1136 int64 db_handle = 0; 1137 if (!request->canceled()) { 1138 if (db_.get()) 1139 db_handle = db_->CreateDownload(create_info); 1140 request->ForwardResult(DownloadCreateRequest::TupleType(create_info, 1141 db_handle)); 1142 } 1143 } 1144 1145 void HistoryBackend::RemoveDownload(int64 db_handle) { 1146 if (db_.get()) 1147 db_->RemoveDownload(db_handle); 1148 } 1149 1150 void HistoryBackend::RemoveDownloadsBetween(const Time remove_begin, 1151 const Time remove_end) { 1152 if (db_.get()) 1153 db_->RemoveDownloadsBetween(remove_begin, remove_end); 1154 } 1155 1156 void HistoryBackend::QueryHistory(scoped_refptr<QueryHistoryRequest> request, 1157 const string16& text_query, 1158 const QueryOptions& options) { 1159 if (request->canceled()) 1160 return; 1161 1162 TimeTicks beginning_time = TimeTicks::Now(); 1163 1164 if (db_.get()) { 1165 if (text_query.empty()) { 1166 // Basic history query for the main database. 1167 QueryHistoryBasic(db_.get(), db_.get(), options, &request->value); 1168 1169 // Now query the archived database. This is a bit tricky because we don't 1170 // want to query it if the queried time range isn't going to find anything 1171 // in it. 1172 // TODO(brettw) bug 1171036: do blimpie querying for the archived database 1173 // as well. 1174 // if (archived_db_.get() && 1175 // expirer_.GetCurrentArchiveTime() - TimeDelta::FromDays(7)) { 1176 } else { 1177 // Full text history query. 1178 QueryHistoryFTS(text_query, options, &request->value); 1179 } 1180 } 1181 1182 request->ForwardResult(QueryHistoryRequest::TupleType(request->handle(), 1183 &request->value)); 1184 1185 UMA_HISTOGRAM_TIMES("History.QueryHistory", 1186 TimeTicks::Now() - beginning_time); 1187 } 1188 1189 // Basic time-based querying of history. 1190 void HistoryBackend::QueryHistoryBasic(URLDatabase* url_db, 1191 VisitDatabase* visit_db, 1192 const QueryOptions& options, 1193 QueryResults* result) { 1194 // First get all visits. 1195 VisitVector visits; 1196 visit_db->GetVisibleVisitsInRange(options.begin_time, options.end_time, 1197 options.max_count, &visits); 1198 DCHECK(options.max_count == 0 || 1199 static_cast<int>(visits.size()) <= options.max_count); 1200 1201 // Now add them and the URL rows to the results. 1202 URLResult url_result; 1203 for (size_t i = 0; i < visits.size(); i++) { 1204 const VisitRow visit = visits[i]; 1205 1206 // Add a result row for this visit, get the URL info from the DB. 1207 if (!url_db->GetURLRow(visit.url_id, &url_result)) { 1208 VLOG(0) << "Failed to get id " << visit.url_id 1209 << " from history.urls."; 1210 continue; // DB out of sync and URL doesn't exist, try to recover. 1211 } 1212 1213 if (!url_result.url().is_valid()) { 1214 VLOG(0) << "Got invalid URL from history.urls with id " 1215 << visit.url_id << ": " 1216 << url_result.url().possibly_invalid_spec(); 1217 continue; // Don't report invalid URLs in case of corruption. 1218 } 1219 1220 // The archived database may be out of sync with respect to starring, 1221 // titles, last visit date, etc. Therefore, we query the main DB if the 1222 // current URL database is not the main one. 1223 if (url_db == db_.get()) { 1224 // Currently querying the archived DB, update with the main database to 1225 // catch any interesting stuff. This will update it if it exists in the 1226 // main DB, and do nothing otherwise. 1227 db_->GetRowForURL(url_result.url(), &url_result); 1228 } 1229 1230 url_result.set_visit_time(visit.visit_time); 1231 1232 // We don't set any of the query-specific parts of the URLResult, since 1233 // snippets and stuff don't apply to basic querying. 1234 result->AppendURLBySwapping(&url_result); 1235 } 1236 1237 if (options.begin_time <= first_recorded_time_) 1238 result->set_reached_beginning(true); 1239 } 1240 1241 void HistoryBackend::QueryHistoryFTS(const string16& text_query, 1242 const QueryOptions& options, 1243 QueryResults* result) { 1244 if (!text_database_.get()) 1245 return; 1246 1247 // Full text query, first get all the FTS results in the time range. 1248 std::vector<TextDatabase::Match> fts_matches; 1249 Time first_time_searched; 1250 text_database_->GetTextMatches(text_query, options, 1251 &fts_matches, &first_time_searched); 1252 1253 URLQuerier querier(db_.get(), archived_db_.get(), true); 1254 1255 // Now get the row and visit information for each one. 1256 URLResult url_result; // Declare outside loop to prevent re-construction. 1257 for (size_t i = 0; i < fts_matches.size(); i++) { 1258 if (options.max_count != 0 && 1259 static_cast<int>(result->size()) >= options.max_count) 1260 break; // Got too many items. 1261 1262 // Get the URL, querying the main and archived databases as necessary. If 1263 // this is not found, the history and full text search databases are out 1264 // of sync and we give up with this result. 1265 if (!querier.GetRowForURL(fts_matches[i].url, &url_result)) 1266 continue; 1267 1268 if (!url_result.url().is_valid()) 1269 continue; // Don't report invalid URLs in case of corruption. 1270 1271 // Copy over the FTS stuff that the URLDatabase doesn't know about. 1272 // We do this with swap() to avoid copying, since we know we don't 1273 // need the original any more. Note that we override the title with the 1274 // one from FTS, since that will match the title_match_positions (the 1275 // FTS title and the history DB title may differ). 1276 url_result.set_title(fts_matches[i].title); 1277 url_result.title_match_positions_.swap( 1278 fts_matches[i].title_match_positions); 1279 url_result.snippet_.Swap(&fts_matches[i].snippet); 1280 1281 // The visit time also comes from the full text search database. Since it 1282 // has the time, we can avoid an extra query of the visits table. 1283 url_result.set_visit_time(fts_matches[i].time); 1284 1285 // Add it to the vector, this will clear our |url_row| object as a 1286 // result of the swap. 1287 result->AppendURLBySwapping(&url_result); 1288 } 1289 1290 if (options.begin_time <= first_recorded_time_) 1291 result->set_reached_beginning(true); 1292 } 1293 1294 // Frontend to GetMostRecentRedirectsFrom from the history thread. 1295 void HistoryBackend::QueryRedirectsFrom( 1296 scoped_refptr<QueryRedirectsRequest> request, 1297 const GURL& url) { 1298 if (request->canceled()) 1299 return; 1300 bool success = GetMostRecentRedirectsFrom(url, &request->value); 1301 request->ForwardResult(QueryRedirectsRequest::TupleType( 1302 request->handle(), url, success, &request->value)); 1303 } 1304 1305 void HistoryBackend::QueryRedirectsTo( 1306 scoped_refptr<QueryRedirectsRequest> request, 1307 const GURL& url) { 1308 if (request->canceled()) 1309 return; 1310 bool success = GetMostRecentRedirectsTo(url, &request->value); 1311 request->ForwardResult(QueryRedirectsRequest::TupleType( 1312 request->handle(), url, success, &request->value)); 1313 } 1314 1315 void HistoryBackend::GetVisitCountToHost( 1316 scoped_refptr<GetVisitCountToHostRequest> request, 1317 const GURL& url) { 1318 if (request->canceled()) 1319 return; 1320 int count = 0; 1321 Time first_visit; 1322 const bool success = (db_.get() && db_->GetVisitCountToHost(url, &count, 1323 &first_visit)); 1324 request->ForwardResult(GetVisitCountToHostRequest::TupleType( 1325 request->handle(), success, count, first_visit)); 1326 } 1327 1328 void HistoryBackend::QueryTopURLsAndRedirects( 1329 scoped_refptr<QueryTopURLsAndRedirectsRequest> request, 1330 int result_count) { 1331 if (request->canceled()) 1332 return; 1333 1334 if (!db_.get()) { 1335 request->ForwardResult(QueryTopURLsAndRedirectsRequest::TupleType( 1336 request->handle(), false, NULL, NULL)); 1337 return; 1338 } 1339 1340 std::vector<GURL>* top_urls = &request->value.a; 1341 history::RedirectMap* redirects = &request->value.b; 1342 1343 ScopedVector<PageUsageData> data; 1344 db_->QuerySegmentUsage(base::Time::Now() - base::TimeDelta::FromDays(90), 1345 result_count, &data.get()); 1346 1347 for (size_t i = 0; i < data.size(); ++i) { 1348 top_urls->push_back(data[i]->GetURL()); 1349 RefCountedVector<GURL>* list = new RefCountedVector<GURL>; 1350 GetMostRecentRedirectsFrom(top_urls->back(), &list->data); 1351 (*redirects)[top_urls->back()] = list; 1352 } 1353 1354 request->ForwardResult(QueryTopURLsAndRedirectsRequest::TupleType( 1355 request->handle(), true, top_urls, redirects)); 1356 } 1357 1358 // Will replace QueryTopURLsAndRedirectsRequest. 1359 void HistoryBackend::QueryMostVisitedURLs( 1360 scoped_refptr<QueryMostVisitedURLsRequest> request, 1361 int result_count, 1362 int days_back) { 1363 if (request->canceled()) 1364 return; 1365 1366 if (!db_.get()) { 1367 // No History Database - return an empty list. 1368 request->ForwardResult(QueryMostVisitedURLsRequest::TupleType( 1369 request->handle(), MostVisitedURLList())); 1370 return; 1371 } 1372 1373 MostVisitedURLList* result = &request->value; 1374 QueryMostVisitedURLsImpl(result_count, days_back, result); 1375 request->ForwardResult(QueryMostVisitedURLsRequest::TupleType( 1376 request->handle(), *result)); 1377 } 1378 1379 void HistoryBackend::QueryMostVisitedURLsImpl(int result_count, 1380 int days_back, 1381 MostVisitedURLList* result) { 1382 if (!db_.get()) 1383 return; 1384 1385 ScopedVector<PageUsageData> data; 1386 db_->QuerySegmentUsage(base::Time::Now() - 1387 base::TimeDelta::FromDays(days_back), 1388 result_count, &data.get()); 1389 1390 for (size_t i = 0; i < data.size(); ++i) { 1391 PageUsageData* current_data = data[i]; 1392 RedirectList redirects; 1393 GetMostRecentRedirectsFrom(current_data->GetURL(), &redirects); 1394 MostVisitedURL url = MakeMostVisitedURL(*current_data, redirects); 1395 result->push_back(url); 1396 } 1397 } 1398 1399 void HistoryBackend::GetRedirectsFromSpecificVisit( 1400 VisitID cur_visit, history::RedirectList* redirects) { 1401 // Follow any redirects from the given visit and add them to the list. 1402 // It *should* be impossible to get a circular chain here, but we check 1403 // just in case to avoid infinite loops. 1404 GURL cur_url; 1405 std::set<VisitID> visit_set; 1406 visit_set.insert(cur_visit); 1407 while (db_->GetRedirectFromVisit(cur_visit, &cur_visit, &cur_url)) { 1408 if (visit_set.find(cur_visit) != visit_set.end()) { 1409 NOTREACHED() << "Loop in visit chain, giving up"; 1410 return; 1411 } 1412 visit_set.insert(cur_visit); 1413 redirects->push_back(cur_url); 1414 } 1415 } 1416 1417 void HistoryBackend::GetRedirectsToSpecificVisit( 1418 VisitID cur_visit, 1419 history::RedirectList* redirects) { 1420 // Follow redirects going to cur_visit. These are added to |redirects| in 1421 // the order they are found. If a redirect chain looks like A -> B -> C and 1422 // |cur_visit| = C, redirects will be {B, A} in that order. 1423 if (!db_.get()) 1424 return; 1425 1426 GURL cur_url; 1427 std::set<VisitID> visit_set; 1428 visit_set.insert(cur_visit); 1429 while (db_->GetRedirectToVisit(cur_visit, &cur_visit, &cur_url)) { 1430 if (visit_set.find(cur_visit) != visit_set.end()) { 1431 NOTREACHED() << "Loop in visit chain, giving up"; 1432 return; 1433 } 1434 visit_set.insert(cur_visit); 1435 redirects->push_back(cur_url); 1436 } 1437 } 1438 1439 bool HistoryBackend::GetMostRecentRedirectsFrom( 1440 const GURL& from_url, 1441 history::RedirectList* redirects) { 1442 redirects->clear(); 1443 if (!db_.get()) 1444 return false; 1445 1446 URLID from_url_id = db_->GetRowForURL(from_url, NULL); 1447 VisitID cur_visit = db_->GetMostRecentVisitForURL(from_url_id, NULL); 1448 if (!cur_visit) 1449 return false; // No visits for URL. 1450 1451 GetRedirectsFromSpecificVisit(cur_visit, redirects); 1452 return true; 1453 } 1454 1455 bool HistoryBackend::GetMostRecentRedirectsTo( 1456 const GURL& to_url, 1457 history::RedirectList* redirects) { 1458 redirects->clear(); 1459 if (!db_.get()) 1460 return false; 1461 1462 URLID to_url_id = db_->GetRowForURL(to_url, NULL); 1463 VisitID cur_visit = db_->GetMostRecentVisitForURL(to_url_id, NULL); 1464 if (!cur_visit) 1465 return false; // No visits for URL. 1466 1467 GetRedirectsToSpecificVisit(cur_visit, redirects); 1468 return true; 1469 } 1470 1471 void HistoryBackend::ScheduleAutocomplete(HistoryURLProvider* provider, 1472 HistoryURLProviderParams* params) { 1473 // ExecuteWithDB should handle the NULL database case. 1474 provider->ExecuteWithDB(this, db_.get(), params); 1475 } 1476 1477 void HistoryBackend::SetPageContents(const GURL& url, 1478 const string16& contents) { 1479 // This is histogrammed in the text database manager. 1480 if (!text_database_.get()) 1481 return; 1482 text_database_->AddPageContents(url, contents); 1483 } 1484 1485 void HistoryBackend::SetPageThumbnail( 1486 const GURL& url, 1487 const SkBitmap& thumbnail, 1488 const ThumbnailScore& score) { 1489 if (!db_.get() || !thumbnail_db_.get()) 1490 return; 1491 1492 URLRow url_row; 1493 URLID url_id = db_->GetRowForURL(url, &url_row); 1494 if (url_id) { 1495 thumbnail_db_->SetPageThumbnail(url, url_id, thumbnail, score, 1496 url_row.last_visit()); 1497 } 1498 1499 ScheduleCommit(); 1500 } 1501 1502 void HistoryBackend::GetPageThumbnail( 1503 scoped_refptr<GetPageThumbnailRequest> request, 1504 const GURL& page_url) { 1505 if (request->canceled()) 1506 return; 1507 1508 scoped_refptr<RefCountedBytes> data; 1509 GetPageThumbnailDirectly(page_url, &data); 1510 1511 request->ForwardResult(GetPageThumbnailRequest::TupleType( 1512 request->handle(), data)); 1513 } 1514 1515 void HistoryBackend::GetPageThumbnailDirectly( 1516 const GURL& page_url, 1517 scoped_refptr<RefCountedBytes>* data) { 1518 if (thumbnail_db_.get()) { 1519 *data = new RefCountedBytes; 1520 1521 // Time the result. 1522 TimeTicks beginning_time = TimeTicks::Now(); 1523 1524 history::RedirectList redirects; 1525 URLID url_id; 1526 bool success = false; 1527 1528 // If there are some redirects, try to get a thumbnail from the last 1529 // redirect destination. 1530 if (GetMostRecentRedirectsFrom(page_url, &redirects) && 1531 !redirects.empty()) { 1532 if ((url_id = db_->GetRowForURL(redirects.back(), NULL))) 1533 success = thumbnail_db_->GetPageThumbnail(url_id, &(*data)->data); 1534 } 1535 1536 // If we don't have a thumbnail from redirects, try the URL directly. 1537 if (!success) { 1538 if ((url_id = db_->GetRowForURL(page_url, NULL))) 1539 success = thumbnail_db_->GetPageThumbnail(url_id, &(*data)->data); 1540 } 1541 1542 // In this rare case, we start to mine the older redirect sessions 1543 // from the visit table to try to find a thumbnail. 1544 if (!success) { 1545 success = GetThumbnailFromOlderRedirect(page_url, &(*data)->data); 1546 } 1547 1548 if (!success) 1549 *data = NULL; // This will tell the callback there was an error. 1550 1551 UMA_HISTOGRAM_TIMES("History.GetPageThumbnail", 1552 TimeTicks::Now() - beginning_time); 1553 } 1554 } 1555 1556 void HistoryBackend::MigrateThumbnailsDatabase() { 1557 // If there is no History DB, we can't record that the migration was done. 1558 // It will be recorded on the next run. 1559 if (db_.get()) { 1560 // If there is no thumbnail DB, we can still record a successful migration. 1561 if (thumbnail_db_.get()) { 1562 thumbnail_db_->RenameAndDropThumbnails(GetThumbnailFileName(), 1563 GetFaviconsFileName()); 1564 } 1565 db_->ThumbnailMigrationDone(); 1566 } 1567 } 1568 1569 bool HistoryBackend::GetThumbnailFromOlderRedirect( 1570 const GURL& page_url, 1571 std::vector<unsigned char>* data) { 1572 // Look at a few previous visit sessions. 1573 VisitVector older_sessions; 1574 URLID page_url_id = db_->GetRowForURL(page_url, NULL); 1575 static const int kVisitsToSearchForThumbnail = 4; 1576 db_->GetMostRecentVisitsForURL( 1577 page_url_id, kVisitsToSearchForThumbnail, &older_sessions); 1578 1579 // Iterate across all those previous visits, and see if any of the 1580 // final destinations of those redirect chains have a good thumbnail 1581 // for us. 1582 bool success = false; 1583 for (VisitVector::const_iterator it = older_sessions.begin(); 1584 !success && it != older_sessions.end(); ++it) { 1585 history::RedirectList redirects; 1586 if (it->visit_id) { 1587 GetRedirectsFromSpecificVisit(it->visit_id, &redirects); 1588 1589 if (!redirects.empty()) { 1590 URLID url_id; 1591 if ((url_id = db_->GetRowForURL(redirects.back(), NULL))) 1592 success = thumbnail_db_->GetPageThumbnail(url_id, data); 1593 } 1594 } 1595 } 1596 1597 return success; 1598 } 1599 1600 void HistoryBackend::GetFavicon(scoped_refptr<GetFaviconRequest> request, 1601 const GURL& icon_url, 1602 int icon_types) { 1603 UpdateFaviconMappingAndFetchImpl(NULL, icon_url, request, icon_types); 1604 } 1605 1606 void HistoryBackend::UpdateFaviconMappingAndFetch( 1607 scoped_refptr<GetFaviconRequest> request, 1608 const GURL& page_url, 1609 const GURL& icon_url, 1610 IconType icon_type) { 1611 UpdateFaviconMappingAndFetchImpl(&page_url, icon_url, request, icon_type); 1612 } 1613 1614 void HistoryBackend::SetFaviconOutOfDateForPage(const GURL& page_url) { 1615 std::vector<IconMapping> icon_mappings; 1616 1617 if (!thumbnail_db_.get() || 1618 !thumbnail_db_->GetIconMappingsForPageURL(page_url, 1619 &icon_mappings)) 1620 return; 1621 1622 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 1623 m != icon_mappings.end(); ++m) { 1624 thumbnail_db_->SetFaviconLastUpdateTime(m->icon_id, Time()); 1625 } 1626 ScheduleCommit(); 1627 } 1628 1629 void HistoryBackend::SetImportedFavicons( 1630 const std::vector<ImportedFaviconUsage>& favicon_usage) { 1631 if (!db_.get() || !thumbnail_db_.get()) 1632 return; 1633 1634 Time now = Time::Now(); 1635 1636 // Track all URLs that had their favicons set or updated. 1637 std::set<GURL> favicons_changed; 1638 1639 for (size_t i = 0; i < favicon_usage.size(); i++) { 1640 FaviconID favicon_id = thumbnail_db_->GetFaviconIDForFaviconURL( 1641 favicon_usage[i].favicon_url, history::FAVICON, NULL); 1642 if (!favicon_id) { 1643 // This favicon doesn't exist yet, so we create it using the given data. 1644 favicon_id = thumbnail_db_->AddFavicon(favicon_usage[i].favicon_url, 1645 history::FAVICON); 1646 if (!favicon_id) 1647 continue; // Unable to add the favicon. 1648 thumbnail_db_->SetFavicon(favicon_id, 1649 new RefCountedBytes(favicon_usage[i].png_data), now); 1650 } 1651 1652 // Save the mapping from all the URLs to the favicon. 1653 BookmarkService* bookmark_service = GetBookmarkService(); 1654 for (std::set<GURL>::const_iterator url = favicon_usage[i].urls.begin(); 1655 url != favicon_usage[i].urls.end(); ++url) { 1656 URLRow url_row; 1657 if (!db_->GetRowForURL(*url, &url_row)) { 1658 // If the URL is present as a bookmark, add the url in history to 1659 // save the favicon mapping. This will match with what history db does 1660 // for regular bookmarked URLs with favicons - when history db is 1661 // cleaned, we keep an entry in the db with 0 visits as long as that 1662 // url is bookmarked. 1663 if (bookmark_service && bookmark_service_->IsBookmarked(*url)) { 1664 URLRow url_info(*url); 1665 url_info.set_visit_count(0); 1666 url_info.set_typed_count(0); 1667 url_info.set_last_visit(base::Time()); 1668 url_info.set_hidden(false); 1669 db_->AddURL(url_info); 1670 thumbnail_db_->AddIconMapping(*url, favicon_id); 1671 favicons_changed.insert(*url); 1672 } 1673 } else { 1674 if (!thumbnail_db_->GetIconMappingForPageURL(*url, FAVICON, NULL)) { 1675 // URL is present in history, update the favicon *only* if it is not 1676 // set already. 1677 thumbnail_db_->AddIconMapping(*url, favicon_id); 1678 favicons_changed.insert(*url); 1679 } 1680 } 1681 } 1682 } 1683 1684 if (!favicons_changed.empty()) { 1685 // Send the notification about the changed favicon URLs. 1686 FaviconChangeDetails* changed_details = new FaviconChangeDetails; 1687 changed_details->urls.swap(favicons_changed); 1688 BroadcastNotifications(NotificationType::FAVICON_CHANGED, changed_details); 1689 } 1690 } 1691 1692 void HistoryBackend::UpdateFaviconMappingAndFetchImpl( 1693 const GURL* page_url, 1694 const GURL& icon_url, 1695 scoped_refptr<GetFaviconRequest> request, 1696 int icon_types) { 1697 // Check only a single type was given when the page_url was specified. 1698 DCHECK(!page_url || (page_url && (icon_types == FAVICON || 1699 icon_types == TOUCH_ICON || icon_types == TOUCH_PRECOMPOSED_ICON))); 1700 1701 if (request->canceled()) 1702 return; 1703 1704 FaviconData favicon; 1705 1706 if (thumbnail_db_.get()) { 1707 const FaviconID favicon_id = 1708 thumbnail_db_->GetFaviconIDForFaviconURL( 1709 icon_url, icon_types, &favicon.icon_type); 1710 if (favicon_id) { 1711 scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); 1712 favicon.known_icon = true; 1713 Time last_updated; 1714 if (thumbnail_db_->GetFavicon(favicon_id, &last_updated, &data->data, 1715 NULL)) { 1716 favicon.expired = (Time::Now() - last_updated) > 1717 TimeDelta::FromDays(kFaviconRefetchDays); 1718 favicon.image_data = data; 1719 } 1720 1721 if (page_url) 1722 SetFaviconMapping(*page_url, favicon_id, favicon.icon_type); 1723 } 1724 // else case, haven't cached entry yet. Caller is responsible for 1725 // downloading the favicon and invoking SetFavicon. 1726 } 1727 request->ForwardResult(GetFaviconRequest::TupleType( 1728 request->handle(), favicon)); 1729 } 1730 1731 void HistoryBackend::GetFaviconForURL( 1732 scoped_refptr<GetFaviconRequest> request, 1733 const GURL& page_url, 1734 int icon_types) { 1735 if (request->canceled()) 1736 return; 1737 1738 FaviconData favicon; 1739 1740 if (db_.get() && thumbnail_db_.get()) { 1741 // Time the query. 1742 TimeTicks beginning_time = TimeTicks::Now(); 1743 1744 std::vector<IconMapping> icon_mappings; 1745 Time last_updated; 1746 scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); 1747 if (thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings) && 1748 (icon_mappings.front().icon_type & icon_types) && 1749 thumbnail_db_->GetFavicon(icon_mappings.front().icon_id, &last_updated, 1750 &data->data, &favicon.icon_url)) { 1751 favicon.known_icon = true; 1752 favicon.expired = (Time::Now() - last_updated) > 1753 TimeDelta::FromDays(kFaviconRefetchDays); 1754 favicon.icon_type = icon_mappings.front().icon_type; 1755 favicon.image_data = data; 1756 } 1757 1758 UMA_HISTOGRAM_TIMES("History.GetFavIconForURL", // historical name 1759 TimeTicks::Now() - beginning_time); 1760 } 1761 1762 request->ForwardResult( 1763 GetFaviconRequest::TupleType(request->handle(), favicon)); 1764 } 1765 1766 void HistoryBackend::SetFavicon( 1767 const GURL& page_url, 1768 const GURL& icon_url, 1769 scoped_refptr<RefCountedMemory> data, 1770 IconType icon_type) { 1771 DCHECK(data.get()); 1772 if (!thumbnail_db_.get() || !db_.get()) 1773 return; 1774 1775 FaviconID id = thumbnail_db_->GetFaviconIDForFaviconURL( 1776 icon_url, icon_type, NULL); 1777 if (!id) 1778 id = thumbnail_db_->AddFavicon(icon_url, icon_type); 1779 1780 // Set the image data. 1781 thumbnail_db_->SetFavicon(id, data, Time::Now()); 1782 1783 SetFaviconMapping(page_url, id, icon_type); 1784 } 1785 1786 void HistoryBackend::SetFaviconMapping(const GURL& page_url, 1787 FaviconID id, 1788 IconType icon_type) { 1789 if (!thumbnail_db_.get()) 1790 return; 1791 1792 // Find all the pages whose favicons we should set, we want to set it for 1793 // all the pages in the redirect chain if it redirected. 1794 history::RedirectList dummy_list; 1795 history::RedirectList* redirects; 1796 RedirectCache::iterator iter = recent_redirects_.Get(page_url); 1797 if (iter != recent_redirects_.end()) { 1798 redirects = &iter->second; 1799 1800 // This redirect chain should have the destination URL as the last item. 1801 DCHECK(!redirects->empty()); 1802 DCHECK(redirects->back() == page_url); 1803 } else { 1804 // No redirect chain stored, make up one containing the URL we want to we 1805 // can use the same logic below. 1806 dummy_list.push_back(page_url); 1807 redirects = &dummy_list; 1808 } 1809 1810 std::set<GURL> favicons_changed; 1811 1812 // Save page <-> favicon association. 1813 for (history::RedirectList::const_iterator i(redirects->begin()); 1814 i != redirects->end(); ++i) { 1815 FaviconID replaced_id; 1816 if (AddOrUpdateIconMapping(*i, id, icon_type, &replaced_id)) { 1817 // The page's favicon ID changed. This means that the one we just 1818 // changed from could have been orphaned, and we need to re-check it. 1819 // This is not super fast, but this case will get triggered rarely, 1820 // since normally a page will always map to the same favicon ID. It 1821 // will mostly happen for favicons we import. 1822 if (replaced_id && !thumbnail_db_->HasMappingFor(replaced_id)) 1823 thumbnail_db_->DeleteFavicon(replaced_id); 1824 1825 favicons_changed.insert(*i); 1826 } 1827 } 1828 1829 // Send the notification about the changed favicons. 1830 FaviconChangeDetails* changed_details = new FaviconChangeDetails; 1831 changed_details->urls.swap(favicons_changed); 1832 BroadcastNotifications(NotificationType::FAVICON_CHANGED, changed_details); 1833 1834 ScheduleCommit(); 1835 } 1836 1837 bool HistoryBackend::AddOrUpdateIconMapping(const GURL& page_url, 1838 FaviconID id, 1839 IconType icon_type, 1840 FaviconID* replaced_icon) { 1841 *replaced_icon = 0; 1842 std::vector<IconMapping> icon_mappings; 1843 if (!thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings)) { 1844 // There is no mapping add it directly. 1845 thumbnail_db_->AddIconMapping(page_url, id); 1846 return true; 1847 } 1848 // Iterate all matched icon mappings, 1849 // a. If the given icon id and matched icon id are same, return. 1850 // b. If the given icon type and matched icon type are same, but icon id 1851 // are not, update the IconMapping. 1852 // c. If the given icon_type and matched icon type are not same, but 1853 // either of them is ICON_TOUCH or ICON_PRECOMPOSED_TOUCH, update the 1854 // IconMapping. 1855 // d. Otherwise add a icon mapping. 1856 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 1857 m != icon_mappings.end(); ++m) { 1858 if (m->icon_id == id) 1859 // The mapping is already there. 1860 return false; 1861 1862 if ((icon_type == TOUCH_ICON && m->icon_type == TOUCH_PRECOMPOSED_ICON) || 1863 (icon_type == TOUCH_PRECOMPOSED_ICON && m->icon_type == TOUCH_ICON) || 1864 (icon_type == m->icon_type)) { 1865 thumbnail_db_->UpdateIconMapping(m->mapping_id, id); 1866 *replaced_icon = m->icon_id; 1867 return true; 1868 } 1869 } 1870 thumbnail_db_->AddIconMapping(page_url, id); 1871 return true; 1872 } 1873 1874 void HistoryBackend::Commit() { 1875 if (!db_.get()) 1876 return; 1877 1878 // Note that a commit may not actually have been scheduled if a caller 1879 // explicitly calls this instead of using ScheduleCommit. Likewise, we 1880 // may reset the flag written by a pending commit. But this is OK! It 1881 // will merely cause extra commits (which is kind of the idea). We 1882 // could optimize more for this case (we may get two extra commits in 1883 // some cases) but it hasn't been important yet. 1884 CancelScheduledCommit(); 1885 1886 db_->CommitTransaction(); 1887 DCHECK(db_->transaction_nesting() == 0) << "Somebody left a transaction open"; 1888 db_->BeginTransaction(); 1889 1890 if (thumbnail_db_.get()) { 1891 thumbnail_db_->CommitTransaction(); 1892 DCHECK(thumbnail_db_->transaction_nesting() == 0) << 1893 "Somebody left a transaction open"; 1894 thumbnail_db_->BeginTransaction(); 1895 } 1896 1897 if (archived_db_.get()) { 1898 archived_db_->CommitTransaction(); 1899 archived_db_->BeginTransaction(); 1900 } 1901 1902 if (text_database_.get()) { 1903 text_database_->CommitTransaction(); 1904 text_database_->BeginTransaction(); 1905 } 1906 } 1907 1908 void HistoryBackend::ScheduleCommit() { 1909 if (scheduled_commit_.get()) 1910 return; 1911 scheduled_commit_ = new CommitLaterTask(this); 1912 MessageLoop::current()->PostDelayedTask(FROM_HERE, 1913 NewRunnableMethod(scheduled_commit_.get(), 1914 &CommitLaterTask::RunCommit), 1915 kCommitIntervalMs); 1916 } 1917 1918 void HistoryBackend::CancelScheduledCommit() { 1919 if (scheduled_commit_) { 1920 scheduled_commit_->Cancel(); 1921 scheduled_commit_ = NULL; 1922 } 1923 } 1924 1925 void HistoryBackend::ProcessDBTaskImpl() { 1926 if (!db_.get()) { 1927 // db went away, release all the refs. 1928 ReleaseDBTasks(); 1929 return; 1930 } 1931 1932 // Remove any canceled tasks. 1933 while (!db_task_requests_.empty() && db_task_requests_.front()->canceled()) { 1934 db_task_requests_.front()->Release(); 1935 db_task_requests_.pop_front(); 1936 } 1937 if (db_task_requests_.empty()) 1938 return; 1939 1940 // Run the first task. 1941 HistoryDBTaskRequest* request = db_task_requests_.front(); 1942 db_task_requests_.pop_front(); 1943 if (request->value->RunOnDBThread(this, db_.get())) { 1944 // The task is done. Notify the callback. 1945 request->ForwardResult(HistoryDBTaskRequest::TupleType()); 1946 // We AddRef'd the request before adding, need to release it now. 1947 request->Release(); 1948 } else { 1949 // Tasks wants to run some more. Schedule it at the end of current tasks. 1950 db_task_requests_.push_back(request); 1951 // And process it after an invoke later. 1952 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 1953 this, &HistoryBackend::ProcessDBTaskImpl)); 1954 } 1955 } 1956 1957 void HistoryBackend::ReleaseDBTasks() { 1958 for (std::list<HistoryDBTaskRequest*>::iterator i = 1959 db_task_requests_.begin(); i != db_task_requests_.end(); ++i) { 1960 (*i)->Release(); 1961 } 1962 db_task_requests_.clear(); 1963 } 1964 1965 //////////////////////////////////////////////////////////////////////////////// 1966 // 1967 // Generic operations 1968 // 1969 //////////////////////////////////////////////////////////////////////////////// 1970 1971 void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) { 1972 for (std::vector<GURL>::const_iterator url = urls.begin(); url != urls.end(); 1973 ++url) { 1974 expirer_.DeleteURL(*url); 1975 } 1976 1977 db_->GetStartDate(&first_recorded_time_); 1978 // Force a commit, if the user is deleting something for privacy reasons, we 1979 // want to get it on disk ASAP. 1980 Commit(); 1981 } 1982 1983 void HistoryBackend::DeleteURL(const GURL& url) { 1984 expirer_.DeleteURL(url); 1985 1986 db_->GetStartDate(&first_recorded_time_); 1987 // Force a commit, if the user is deleting something for privacy reasons, we 1988 // want to get it on disk ASAP. 1989 Commit(); 1990 } 1991 1992 void HistoryBackend::ExpireHistoryBetween( 1993 scoped_refptr<ExpireHistoryRequest> request, 1994 const std::set<GURL>& restrict_urls, 1995 Time begin_time, 1996 Time end_time) { 1997 if (request->canceled()) 1998 return; 1999 2000 if (db_.get()) { 2001 if (begin_time.is_null() && end_time.is_null() && restrict_urls.empty()) { 2002 // Special case deleting all history so it can be faster and to reduce the 2003 // possibility of an information leak. 2004 DeleteAllHistory(); 2005 } else { 2006 // Clearing parts of history, have the expirer do the depend 2007 expirer_.ExpireHistoryBetween(restrict_urls, begin_time, end_time); 2008 2009 // Force a commit, if the user is deleting something for privacy reasons, 2010 // we want to get it on disk ASAP. 2011 Commit(); 2012 } 2013 } 2014 2015 if (begin_time <= first_recorded_time_) 2016 db_->GetStartDate(&first_recorded_time_); 2017 2018 request->ForwardResult(ExpireHistoryRequest::TupleType()); 2019 2020 if (history_publisher_.get() && restrict_urls.empty()) 2021 history_publisher_->DeleteUserHistoryBetween(begin_time, end_time); 2022 } 2023 2024 void HistoryBackend::URLsNoLongerBookmarked(const std::set<GURL>& urls) { 2025 if (!db_.get()) 2026 return; 2027 2028 for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { 2029 URLRow url_row; 2030 if (!db_->GetRowForURL(*i, &url_row)) 2031 continue; // The URL isn't in the db; nothing to do. 2032 2033 VisitVector visits; 2034 db_->GetVisitsForURL(url_row.id(), &visits); 2035 2036 if (visits.empty()) 2037 expirer_.DeleteURL(*i); // There are no more visits; nuke the URL. 2038 } 2039 } 2040 2041 void HistoryBackend::ProcessDBTask( 2042 scoped_refptr<HistoryDBTaskRequest> request) { 2043 DCHECK(request.get()); 2044 if (request->canceled()) 2045 return; 2046 2047 bool task_scheduled = !db_task_requests_.empty(); 2048 // Make sure we up the refcount of the request. ProcessDBTaskImpl will 2049 // release when done with the task. 2050 request->AddRef(); 2051 db_task_requests_.push_back(request.get()); 2052 if (!task_scheduled) { 2053 // No other tasks are scheduled. Process request now. 2054 ProcessDBTaskImpl(); 2055 } 2056 } 2057 2058 void HistoryBackend::BroadcastNotifications( 2059 NotificationType type, 2060 HistoryDetails* details_deleted) { 2061 DCHECK(delegate_.get()); 2062 delegate_->BroadcastNotifications(type, details_deleted); 2063 } 2064 2065 // Deleting -------------------------------------------------------------------- 2066 2067 void HistoryBackend::DeleteAllHistory() { 2068 // Our approach to deleting all history is: 2069 // 1. Copy the bookmarks and their dependencies to new tables with temporary 2070 // names. 2071 // 2. Delete the original tables. Since tables can not share pages, we know 2072 // that any data we don't want to keep is now in an unused page. 2073 // 3. Renaming the temporary tables to match the original. 2074 // 4. Vacuuming the database to delete the unused pages. 2075 // 2076 // Since we are likely to have very few bookmarks and their dependencies 2077 // compared to all history, this is also much faster than just deleting from 2078 // the original tables directly. 2079 2080 // Get the bookmarked URLs. 2081 std::vector<GURL> starred_urls; 2082 BookmarkService* bookmark_service = GetBookmarkService(); 2083 if (bookmark_service) 2084 bookmark_service_->GetBookmarks(&starred_urls); 2085 2086 std::vector<URLRow> kept_urls; 2087 for (size_t i = 0; i < starred_urls.size(); i++) { 2088 URLRow row; 2089 if (!db_->GetRowForURL(starred_urls[i], &row)) 2090 continue; 2091 2092 // Clear the last visit time so when we write these rows they are "clean." 2093 row.set_last_visit(Time()); 2094 row.set_visit_count(0); 2095 row.set_typed_count(0); 2096 kept_urls.push_back(row); 2097 } 2098 2099 // Clear thumbnail and favicon history. The favicons for the given URLs will 2100 // be kept. 2101 if (!ClearAllThumbnailHistory(&kept_urls)) { 2102 LOG(ERROR) << "Thumbnail history could not be cleared"; 2103 // We continue in this error case. If the user wants to delete their 2104 // history, we should delete as much as we can. 2105 } 2106 2107 // ClearAllMainHistory will change the IDs of the URLs in kept_urls. Therfore, 2108 // we clear the list afterwards to make sure nobody uses this invalid data. 2109 if (!ClearAllMainHistory(kept_urls)) 2110 LOG(ERROR) << "Main history could not be cleared"; 2111 kept_urls.clear(); 2112 2113 // Delete FTS files & archived history. 2114 if (text_database_.get()) { 2115 // We assume that the text database has one transaction on them that we need 2116 // to close & restart (the long-running history transaction). 2117 text_database_->CommitTransaction(); 2118 text_database_->DeleteAll(); 2119 text_database_->BeginTransaction(); 2120 } 2121 2122 if (archived_db_.get()) { 2123 // Close the database and delete the file. 2124 archived_db_.reset(); 2125 FilePath archived_file_name = GetArchivedFileName(); 2126 file_util::Delete(archived_file_name, false); 2127 2128 // Now re-initialize the database (which may fail). 2129 archived_db_.reset(new ArchivedDatabase()); 2130 if (!archived_db_->Init(archived_file_name)) { 2131 LOG(WARNING) << "Could not initialize the archived database."; 2132 archived_db_.reset(); 2133 } else { 2134 // Open our long-running transaction on this database. 2135 archived_db_->BeginTransaction(); 2136 } 2137 } 2138 2139 db_->GetStartDate(&first_recorded_time_); 2140 2141 // Send out the notfication that history is cleared. The in-memory datdabase 2142 // will pick this up and clear itself. 2143 URLsDeletedDetails* details = new URLsDeletedDetails; 2144 details->all_history = true; 2145 BroadcastNotifications(NotificationType::HISTORY_URLS_DELETED, details); 2146 } 2147 2148 bool HistoryBackend::ClearAllThumbnailHistory( 2149 std::vector<URLRow>* kept_urls) { 2150 if (!thumbnail_db_.get()) { 2151 // When we have no reference to the thumbnail database, maybe there was an 2152 // error opening it. In this case, we just try to blow it away to try to 2153 // fix the error if it exists. This may fail, in which case either the 2154 // file doesn't exist or there's no more we can do. 2155 file_util::Delete(GetThumbnailFileName(), false); 2156 return true; 2157 } 2158 2159 // Create the duplicate favicon table, this is where the favicons we want 2160 // to keep will be stored. 2161 if (!thumbnail_db_->InitTemporaryFaviconsTable()) 2162 return false; 2163 2164 if (!thumbnail_db_->InitTemporaryIconMappingTable()) 2165 return false; 2166 2167 // This maps existing favicon IDs to the ones in the temporary table. 2168 typedef std::map<FaviconID, FaviconID> FaviconMap; 2169 FaviconMap copied_favicons; 2170 2171 // Copy all unique favicons to the temporary table, and update all the 2172 // URLs to have the new IDs. 2173 for (std::vector<URLRow>::iterator i = kept_urls->begin(); 2174 i != kept_urls->end(); ++i) { 2175 std::vector<IconMapping> icon_mappings; 2176 if (!thumbnail_db_->GetIconMappingsForPageURL(i->url(), &icon_mappings)) 2177 continue; 2178 2179 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 2180 m != icon_mappings.end(); ++m) { 2181 FaviconID old_id = m->icon_id; 2182 FaviconID new_id; 2183 FaviconMap::const_iterator found = copied_favicons.find(old_id); 2184 if (found == copied_favicons.end()) { 2185 new_id = thumbnail_db_->CopyToTemporaryFaviconTable(old_id); 2186 copied_favicons[old_id] = new_id; 2187 } else { 2188 // We already encountered a URL that used this favicon, use the ID we 2189 // previously got. 2190 new_id = found->second; 2191 } 2192 // Add Icon mapping, and we don't care wheteher it suceeded or not. 2193 thumbnail_db_->AddToTemporaryIconMappingTable(i->url(), new_id); 2194 } 2195 } 2196 2197 // Rename the duplicate favicon and icon_mapping back table and recreate the 2198 // other tables. This will make the database consistent again. 2199 thumbnail_db_->CommitTemporaryFaviconTable(); 2200 thumbnail_db_->CommitTemporaryIconMappingTable(); 2201 2202 thumbnail_db_->RecreateThumbnailTable(); 2203 2204 // Vacuum to remove all the pages associated with the dropped tables. There 2205 // must be no transaction open on the table when we do this. We assume that 2206 // our long-running transaction is open, so we complete it and start it again. 2207 DCHECK(thumbnail_db_->transaction_nesting() == 1); 2208 thumbnail_db_->CommitTransaction(); 2209 thumbnail_db_->Vacuum(); 2210 thumbnail_db_->BeginTransaction(); 2211 return true; 2212 } 2213 2214 bool HistoryBackend::ClearAllMainHistory( 2215 const std::vector<URLRow>& kept_urls) { 2216 // Create the duplicate URL table. We will copy the kept URLs into this. 2217 if (!db_->CreateTemporaryURLTable()) 2218 return false; 2219 2220 // Insert the URLs into the temporary table, we need to keep a map of changed 2221 // IDs since the ID will be different in the new table. 2222 typedef std::map<URLID, URLID> URLIDMap; 2223 URLIDMap old_to_new; // Maps original ID to new one. 2224 for (std::vector<URLRow>::const_iterator i = kept_urls.begin(); 2225 i != kept_urls.end(); 2226 ++i) { 2227 URLID new_id = db_->AddTemporaryURL(*i); 2228 old_to_new[i->id()] = new_id; 2229 } 2230 2231 // Replace the original URL table with the temporary one. 2232 if (!db_->CommitTemporaryURLTable()) 2233 return false; 2234 2235 // Delete the old tables and recreate them empty. 2236 db_->RecreateAllTablesButURL(); 2237 2238 // Vacuum to reclaim the space from the dropped tables. This must be done 2239 // when there is no transaction open, and we assume that our long-running 2240 // transaction is currently open. 2241 db_->CommitTransaction(); 2242 db_->Vacuum(); 2243 db_->BeginTransaction(); 2244 db_->GetStartDate(&first_recorded_time_); 2245 2246 return true; 2247 } 2248 2249 BookmarkService* HistoryBackend::GetBookmarkService() { 2250 if (bookmark_service_) 2251 bookmark_service_->BlockTillLoaded(); 2252 return bookmark_service_; 2253 } 2254 2255 } // namespace history 2256