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