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/expire_history_backend.h" 6 7 #include <algorithm> 8 #include <limits> 9 10 #include "base/compiler_specific.h" 11 #include "base/file_util.h" 12 #include "base/message_loop.h" 13 #include "chrome/browser/bookmarks/bookmark_service.h" 14 #include "chrome/browser/history/archived_database.h" 15 #include "chrome/browser/history/history_database.h" 16 #include "chrome/browser/history/history_notifications.h" 17 #include "chrome/browser/history/text_database.h" 18 #include "chrome/browser/history/text_database_manager.h" 19 #include "chrome/browser/history/thumbnail_database.h" 20 #include "content/common/notification_type.h" 21 22 using base::Time; 23 using base::TimeDelta; 24 25 namespace history { 26 27 namespace { 28 29 // The number of days by which the expiration threshold is advanced for items 30 // that we want to expire early, such as those of AUTO_SUBFRAME transition type. 31 const int kEarlyExpirationAdvanceDays = 30; 32 33 // Reads all types of visits starting from beginning of time to the given end 34 // time. This is the most general reader. 35 class AllVisitsReader : public ExpiringVisitsReader { 36 public: 37 virtual bool Read(Time end_time, HistoryDatabase* db, 38 VisitVector* visits, int max_visits) const { 39 DCHECK(db) << "must have a database to operate upon"; 40 DCHECK(visits) << "visit vector has to exist in order to populate it"; 41 42 db->GetAllVisitsInRange(Time(), end_time, max_visits, visits); 43 // When we got the maximum number of visits we asked for, we say there could 44 // be additional things to expire now. 45 return static_cast<int>(visits->size()) == max_visits; 46 } 47 }; 48 49 // Reads only AUTO_SUBFRAME visits, within a computed range. The range is 50 // computed as follows: 51 // * |begin_time| is read from the meta table. This value is updated whenever 52 // there are no more additional visits to expire by this reader. 53 // * |end_time| is advanced forward by a constant (kEarlyExpirationAdvanceDay), 54 // but not past the current time. 55 class AutoSubframeVisitsReader : public ExpiringVisitsReader { 56 public: 57 virtual bool Read(Time end_time, HistoryDatabase* db, 58 VisitVector* visits, int max_visits) const { 59 DCHECK(db) << "must have a database to operate upon"; 60 DCHECK(visits) << "visit vector has to exist in order to populate it"; 61 62 Time begin_time = db->GetEarlyExpirationThreshold(); 63 // Advance |end_time| to expire early. 64 Time early_end_time = end_time + 65 TimeDelta::FromDays(kEarlyExpirationAdvanceDays); 66 67 // We don't want to set the early expiration threshold to a time in the 68 // future. 69 Time now = Time::Now(); 70 if (early_end_time > now) 71 early_end_time = now; 72 73 db->GetVisitsInRangeForTransition(begin_time, early_end_time, 74 max_visits, 75 PageTransition::AUTO_SUBFRAME, 76 visits); 77 bool more = static_cast<int>(visits->size()) == max_visits; 78 if (!more) 79 db->UpdateEarlyExpirationThreshold(early_end_time); 80 81 return more; 82 } 83 }; 84 85 // Returns true if this visit is worth archiving. Otherwise, this visit is not 86 // worth saving (for example, subframe navigations and redirects) and we can 87 // just delete it when it gets old. 88 bool ShouldArchiveVisit(const VisitRow& visit) { 89 int no_qualifier = PageTransition::StripQualifier(visit.transition); 90 91 // These types of transitions are always "important" and the user will want 92 // to see them. 93 if (no_qualifier == PageTransition::TYPED || 94 no_qualifier == PageTransition::AUTO_BOOKMARK || 95 no_qualifier == PageTransition::START_PAGE) 96 return true; 97 98 // Only archive these "less important" transitions when they were the final 99 // navigation and not part of a redirect chain. 100 if ((no_qualifier == PageTransition::LINK || 101 no_qualifier == PageTransition::FORM_SUBMIT || 102 no_qualifier == PageTransition::KEYWORD || 103 no_qualifier == PageTransition::GENERATED) && 104 visit.transition & PageTransition::CHAIN_END) 105 return true; 106 107 // The transition types we ignore are AUTO_SUBFRAME and MANUAL_SUBFRAME. 108 return false; 109 } 110 111 // The number of visits we will expire very time we check for old items. This 112 // Prevents us from doing too much work any given time. 113 const int kNumExpirePerIteration = 10; 114 115 // The number of seconds between checking for items that should be expired when 116 // we think there might be more items to expire. This timeout is used when the 117 // last expiration found at least kNumExpirePerIteration and we want to check 118 // again "soon." 119 const int kExpirationDelaySec = 30; 120 121 // The number of minutes between checking, as with kExpirationDelaySec, but 122 // when we didn't find enough things to expire last time. If there was no 123 // history to expire last iteration, it's likely there is nothing next 124 // iteration, so we want to wait longer before checking to avoid wasting CPU. 125 const int kExpirationEmptyDelayMin = 5; 126 127 // The number of minutes that we wait for before scheduling a task to 128 // delete old history index files. 129 const int kIndexExpirationDelayMin = 2; 130 131 // The number of the most recent months for which we do not want to delete 132 // the history index files. 133 const int kStoreHistoryIndexesForMonths = 12; 134 135 } // namespace 136 137 struct ExpireHistoryBackend::DeleteDependencies { 138 // The time range affected. These can be is_null() to be unbounded in one 139 // or both directions. 140 base::Time begin_time, end_time; 141 142 // ----- Filled by DeleteVisitRelatedInfo or manually if a function doesn't 143 // call that function. ----- 144 145 // The unique URL rows affected by this delete. 146 std::map<URLID, URLRow> affected_urls; 147 148 // ----- Filled by DeleteOneURL ----- 149 150 // The URLs deleted during this operation. 151 std::vector<URLRow> deleted_urls; 152 153 // The list of all favicon IDs that the affected URLs had. Favicons will be 154 // shared between all URLs with the same favicon, so this is the set of IDs 155 // that we will need to check when the delete operations are complete. 156 std::set<FaviconID> affected_favicons; 157 158 // Tracks the set of databases that have changed so we can optimize when 159 // when we're done. 160 TextDatabaseManager::ChangeSet text_db_changes; 161 }; 162 163 ExpireHistoryBackend::ExpireHistoryBackend( 164 BroadcastNotificationDelegate* delegate, 165 BookmarkService* bookmark_service) 166 : delegate_(delegate), 167 main_db_(NULL), 168 archived_db_(NULL), 169 thumb_db_(NULL), 170 text_db_(NULL), 171 ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)), 172 bookmark_service_(bookmark_service) { 173 } 174 175 ExpireHistoryBackend::~ExpireHistoryBackend() { 176 } 177 178 void ExpireHistoryBackend::SetDatabases(HistoryDatabase* main_db, 179 ArchivedDatabase* archived_db, 180 ThumbnailDatabase* thumb_db, 181 TextDatabaseManager* text_db) { 182 main_db_ = main_db; 183 archived_db_ = archived_db; 184 thumb_db_ = thumb_db; 185 text_db_ = text_db; 186 } 187 188 void ExpireHistoryBackend::DeleteURL(const GURL& url) { 189 if (!main_db_) 190 return; 191 192 URLRow url_row; 193 if (!main_db_->GetRowForURL(url, &url_row)) 194 return; // Nothing to delete. 195 196 // Collect all the visits and delete them. Note that we don't give up if 197 // there are no visits, since the URL could still have an entry that we should 198 // delete. 199 // TODO(brettw): bug 1171148: We should also delete from the archived DB. 200 VisitVector visits; 201 main_db_->GetVisitsForURL(url_row.id(), &visits); 202 203 DeleteDependencies dependencies; 204 DeleteVisitRelatedInfo(visits, &dependencies); 205 206 // We skip ExpireURLsForVisits (since we are deleting from the URL, and not 207 // starting with visits in a given time range). We therefore need to call the 208 // deletion and favicon update functions manually. 209 210 BookmarkService* bookmark_service = GetBookmarkService(); 211 bool is_bookmarked = 212 (bookmark_service && bookmark_service->IsBookmarked(url)); 213 214 DeleteOneURL(url_row, is_bookmarked, &dependencies); 215 if (!is_bookmarked) 216 DeleteFaviconsIfPossible(dependencies.affected_favicons); 217 218 if (text_db_) 219 text_db_->OptimizeChangedDatabases(dependencies.text_db_changes); 220 221 BroadcastDeleteNotifications(&dependencies); 222 } 223 224 void ExpireHistoryBackend::ExpireHistoryBetween( 225 const std::set<GURL>& restrict_urls, Time begin_time, Time end_time) { 226 if (!main_db_) 227 return; 228 229 // There may be stuff in the text database manager's temporary cache. 230 if (text_db_) 231 text_db_->DeleteFromUncommitted(restrict_urls, begin_time, end_time); 232 233 // Find the affected visits and delete them. 234 // TODO(brettw): bug 1171164: We should query the archived database here, too. 235 VisitVector visits; 236 main_db_->GetAllVisitsInRange(begin_time, end_time, 0, &visits); 237 if (!restrict_urls.empty()) { 238 std::set<URLID> url_ids; 239 for (std::set<GURL>::const_iterator url = restrict_urls.begin(); 240 url != restrict_urls.end(); ++url) 241 url_ids.insert(main_db_->GetRowForURL(*url, NULL)); 242 VisitVector all_visits; 243 all_visits.swap(visits); 244 for (VisitVector::iterator visit = all_visits.begin(); 245 visit != all_visits.end(); ++visit) { 246 if (url_ids.find(visit->url_id) != url_ids.end()) 247 visits.push_back(*visit); 248 } 249 } 250 if (visits.empty()) 251 return; 252 253 DeleteDependencies dependencies; 254 DeleteVisitRelatedInfo(visits, &dependencies); 255 256 // Delete or update the URLs affected. We want to update the visit counts 257 // since this is called by the user who wants to delete their recent history, 258 // and we don't want to leave any evidence. 259 ExpireURLsForVisits(visits, &dependencies); 260 DeleteFaviconsIfPossible(dependencies.affected_favicons); 261 262 // An is_null begin time means that all history should be deleted. 263 BroadcastDeleteNotifications(&dependencies); 264 265 // Pick up any bits possibly left over. 266 ParanoidExpireHistory(); 267 } 268 269 void ExpireHistoryBackend::ArchiveHistoryBefore(Time end_time) { 270 if (!main_db_) 271 return; 272 273 // Archive as much history as possible before the given date. 274 ArchiveSomeOldHistory(end_time, GetAllVisitsReader(), 275 std::numeric_limits<size_t>::max()); 276 ParanoidExpireHistory(); 277 } 278 279 void ExpireHistoryBackend::InitWorkQueue() { 280 DCHECK(work_queue_.empty()) << "queue has to be empty prior to init"; 281 282 for (size_t i = 0; i < readers_.size(); i++) 283 work_queue_.push(readers_[i]); 284 } 285 286 const ExpiringVisitsReader* ExpireHistoryBackend::GetAllVisitsReader() { 287 if (!all_visits_reader_.get()) 288 all_visits_reader_.reset(new AllVisitsReader()); 289 return all_visits_reader_.get(); 290 } 291 292 const ExpiringVisitsReader* 293 ExpireHistoryBackend::GetAutoSubframeVisitsReader() { 294 if (!auto_subframe_visits_reader_.get()) 295 auto_subframe_visits_reader_.reset(new AutoSubframeVisitsReader()); 296 return auto_subframe_visits_reader_.get(); 297 } 298 299 void ExpireHistoryBackend::StartArchivingOldStuff( 300 TimeDelta expiration_threshold) { 301 expiration_threshold_ = expiration_threshold; 302 303 // Remove all readers, just in case this was method was called before. 304 readers_.clear(); 305 // For now, we explicitly add all known readers. If we come up with more 306 // reader types (in case we want to expire different types of visits in 307 // different ways), we can make it be populated by creator/owner of 308 // ExpireHistoryBackend. 309 readers_.push_back(GetAllVisitsReader()); 310 readers_.push_back(GetAutoSubframeVisitsReader()); 311 312 // Initialize the queue with all tasks for the first set of iterations. 313 InitWorkQueue(); 314 ScheduleArchive(); 315 ScheduleExpireHistoryIndexFiles(); 316 } 317 318 void ExpireHistoryBackend::DeleteFaviconsIfPossible( 319 const std::set<FaviconID>& favicon_set) { 320 if (!thumb_db_) 321 return; 322 323 for (std::set<FaviconID>::const_iterator i = favicon_set.begin(); 324 i != favicon_set.end(); ++i) { 325 if (!thumb_db_->HasMappingFor(*i)) 326 thumb_db_->DeleteFavicon(*i); 327 } 328 } 329 330 void ExpireHistoryBackend::BroadcastDeleteNotifications( 331 DeleteDependencies* dependencies) { 332 if (!dependencies->deleted_urls.empty()) { 333 // Broadcast the URL deleted notification. Note that we also broadcast when 334 // we were requested to delete everything even if that was a NOP, since 335 // some components care to know when history is deleted (it's up to them to 336 // determine if they care whether anything was deleted). 337 URLsDeletedDetails* deleted_details = new URLsDeletedDetails; 338 deleted_details->all_history = false; 339 std::vector<URLRow> typed_urls_changed; // Collect this for later. 340 for (size_t i = 0; i < dependencies->deleted_urls.size(); i++) { 341 deleted_details->urls.insert(dependencies->deleted_urls[i].url()); 342 if (dependencies->deleted_urls[i].typed_count() > 0) 343 typed_urls_changed.push_back(dependencies->deleted_urls[i]); 344 } 345 delegate_->BroadcastNotifications(NotificationType::HISTORY_URLS_DELETED, 346 deleted_details); 347 348 // Broadcast the typed URL changed modification (this updates the inline 349 // autocomplete database). 350 // 351 // Note: if we ever need to broadcast changes to more than just typed URLs, 352 // this notification should be changed rather than a new "non-typed" 353 // notification added. The in-memory database can always do the filtering 354 // itself in that case. 355 if (!typed_urls_changed.empty()) { 356 URLsModifiedDetails* modified_details = new URLsModifiedDetails; 357 modified_details->changed_urls.swap(typed_urls_changed); 358 delegate_->BroadcastNotifications( 359 NotificationType::HISTORY_TYPED_URLS_MODIFIED, 360 modified_details); 361 } 362 } 363 } 364 365 void ExpireHistoryBackend::DeleteVisitRelatedInfo( 366 const VisitVector& visits, 367 DeleteDependencies* dependencies) { 368 for (size_t i = 0; i < visits.size(); i++) { 369 // Delete the visit itself. 370 main_db_->DeleteVisit(visits[i]); 371 372 // Add the URL row to the affected URL list. 373 std::map<URLID, URLRow>::const_iterator found = 374 dependencies->affected_urls.find(visits[i].url_id); 375 const URLRow* cur_row = NULL; 376 if (found == dependencies->affected_urls.end()) { 377 URLRow row; 378 if (!main_db_->GetURLRow(visits[i].url_id, &row)) 379 continue; 380 dependencies->affected_urls[visits[i].url_id] = row; 381 cur_row = &dependencies->affected_urls[visits[i].url_id]; 382 } else { 383 cur_row = &found->second; 384 } 385 386 // Delete any associated full-text indexed data. 387 if (visits[i].is_indexed && text_db_) { 388 text_db_->DeletePageData(visits[i].visit_time, cur_row->url(), 389 &dependencies->text_db_changes); 390 } 391 } 392 } 393 394 void ExpireHistoryBackend::DeleteOneURL( 395 const URLRow& url_row, 396 bool is_bookmarked, 397 DeleteDependencies* dependencies) { 398 main_db_->DeleteSegmentForURL(url_row.id()); 399 400 // The URL may be in the text database manager's temporary cache. 401 if (text_db_) { 402 std::set<GURL> restrict_urls; 403 restrict_urls.insert(url_row.url()); 404 text_db_->DeleteFromUncommitted(restrict_urls, base::Time(), base::Time()); 405 } 406 407 if (!is_bookmarked) { 408 dependencies->deleted_urls.push_back(url_row); 409 410 // Delete stuff that references this URL. 411 if (thumb_db_) { 412 thumb_db_->DeleteThumbnail(url_row.id()); 413 414 // Collect shared information. 415 std::vector<IconMapping> icon_mappings; 416 if (thumb_db_->GetIconMappingsForPageURL(url_row.url(), &icon_mappings)) { 417 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 418 m != icon_mappings.end(); ++m) { 419 dependencies->affected_favicons.insert(m->icon_id); 420 } 421 // Delete the mapping entries for the url. 422 thumb_db_->DeleteIconMappings(url_row.url()); 423 } 424 } 425 // Last, delete the URL entry. 426 main_db_->DeleteURLRow(url_row.id()); 427 } 428 } 429 430 URLID ExpireHistoryBackend::ArchiveOneURL(const URLRow& url_row) { 431 if (!archived_db_) 432 return 0; 433 434 // See if this URL is present in the archived database already. Note that 435 // we must look up by ID since the URL ID will be different. 436 URLRow archived_row; 437 if (archived_db_->GetRowForURL(url_row.url(), &archived_row)) { 438 // TODO(sky): bug 1168470, need to archive past search terms. 439 // TODO(brettw): should be copy the visit counts over? This will mean that 440 // the main DB's visit counts are only for the last 3 months rather than 441 // accumulative. 442 archived_row.set_last_visit(url_row.last_visit()); 443 archived_db_->UpdateURLRow(archived_row.id(), archived_row); 444 return archived_row.id(); 445 } 446 447 // This row is not in the archived DB, add it. 448 return archived_db_->AddURL(url_row); 449 } 450 451 namespace { 452 453 struct ChangedURL { 454 ChangedURL() : visit_count(0), typed_count(0) {} 455 int visit_count; 456 int typed_count; 457 }; 458 459 } // namespace 460 461 void ExpireHistoryBackend::ExpireURLsForVisits( 462 const VisitVector& visits, 463 DeleteDependencies* dependencies) { 464 // First find all unique URLs and the number of visits we're deleting for 465 // each one. 466 std::map<URLID, ChangedURL> changed_urls; 467 for (size_t i = 0; i < visits.size(); i++) { 468 ChangedURL& cur = changed_urls[visits[i].url_id]; 469 cur.visit_count++; 470 // NOTE: This code must stay in sync with HistoryBackend::AddPageVisit(). 471 // TODO(pkasting): http://b/1148304 We shouldn't be marking so many URLs as 472 // typed, which would eliminate the need for this code. 473 PageTransition::Type transition = 474 PageTransition::StripQualifier(visits[i].transition); 475 if ((transition == PageTransition::TYPED && 476 !PageTransition::IsRedirect(visits[i].transition)) || 477 transition == PageTransition::KEYWORD_GENERATED) 478 cur.typed_count++; 479 } 480 481 // Check each unique URL with deleted visits. 482 BookmarkService* bookmark_service = GetBookmarkService(); 483 for (std::map<URLID, ChangedURL>::const_iterator i = changed_urls.begin(); 484 i != changed_urls.end(); ++i) { 485 // The unique URL rows should already be filled into the dependencies. 486 URLRow& url_row = dependencies->affected_urls[i->first]; 487 if (!url_row.id()) 488 continue; // URL row doesn't exist in the database. 489 490 // Check if there are any other visits for this URL and update the time 491 // (the time change may not actually be synced to disk below when we're 492 // archiving). 493 VisitRow last_visit; 494 if (main_db_->GetMostRecentVisitForURL(url_row.id(), &last_visit)) 495 url_row.set_last_visit(last_visit.visit_time); 496 else 497 url_row.set_last_visit(Time()); 498 499 // Don't delete URLs with visits still in the DB, or bookmarked. 500 bool is_bookmarked = 501 (bookmark_service && bookmark_service->IsBookmarked(url_row.url())); 502 if (!is_bookmarked && url_row.last_visit().is_null()) { 503 // Not bookmarked and no more visits. Nuke the url. 504 DeleteOneURL(url_row, is_bookmarked, dependencies); 505 } else { 506 // NOTE: The calls to std::max() below are a backstop, but they should 507 // never actually be needed unless the database is corrupt (I think). 508 url_row.set_visit_count( 509 std::max(0, url_row.visit_count() - i->second.visit_count)); 510 url_row.set_typed_count( 511 std::max(0, url_row.typed_count() - i->second.typed_count)); 512 513 // Update the db with the new details. 514 main_db_->UpdateURLRow(url_row.id(), url_row); 515 } 516 } 517 } 518 519 void ExpireHistoryBackend::ArchiveURLsAndVisits( 520 const VisitVector& visits, 521 DeleteDependencies* dependencies) { 522 if (!archived_db_ || !main_db_) 523 return; 524 525 // Make sure all unique URL rows are added to the dependency list and the 526 // archived database. We will also keep the mapping between the main DB URLID 527 // and the archived one. 528 std::map<URLID, URLID> main_id_to_archived_id; 529 for (size_t i = 0; i < visits.size(); i++) { 530 std::map<URLID, URLRow>::const_iterator found = 531 dependencies->affected_urls.find(visits[i].url_id); 532 if (found == dependencies->affected_urls.end()) { 533 // Unique URL encountered, archive it. 534 URLRow row; // Row in the main DB. 535 URLID archived_id; // ID in the archived DB. 536 if (!main_db_->GetURLRow(visits[i].url_id, &row) || 537 !(archived_id = ArchiveOneURL(row))) { 538 // Failure archiving, skip this one. 539 continue; 540 } 541 542 // Only add URL to the dependency list once we know we successfully 543 // archived it. 544 main_id_to_archived_id[row.id()] = archived_id; 545 dependencies->affected_urls[row.id()] = row; 546 } 547 } 548 549 // Retrieve the sources for all the archived visits before archiving. 550 // The returned visit_sources vector should contain the source for each visit 551 // from visits at the same index. 552 VisitSourceMap visit_sources; 553 main_db_->GetVisitsSource(visits, &visit_sources); 554 555 // Now archive the visits since we know the URL ID to make them reference. 556 // The source visit list should still reference the visits in the main DB, but 557 // we will update it to reflect only the visits that were successfully 558 // archived. 559 for (size_t i = 0; i < visits.size(); i++) { 560 // Construct the visit that we will add to the archived database. We do 561 // not store referring visits since we delete many of the visits when 562 // archiving. 563 VisitRow cur_visit(visits[i]); 564 cur_visit.url_id = main_id_to_archived_id[cur_visit.url_id]; 565 cur_visit.referring_visit = 0; 566 VisitSourceMap::iterator iter = visit_sources.find(visits[i].visit_id); 567 archived_db_->AddVisit( 568 &cur_visit, 569 iter == visit_sources.end() ? SOURCE_BROWSED : iter->second); 570 // Ignore failures, we will delete it from the main DB no matter what. 571 } 572 } 573 574 void ExpireHistoryBackend::ScheduleArchive() { 575 TimeDelta delay; 576 if (work_queue_.empty()) { 577 // If work queue is empty, reset the work queue to contain all tasks and 578 // schedule next iteration after a longer delay. 579 InitWorkQueue(); 580 delay = TimeDelta::FromMinutes(kExpirationEmptyDelayMin); 581 } else { 582 delay = TimeDelta::FromSeconds(kExpirationDelaySec); 583 } 584 585 MessageLoop::current()->PostDelayedTask(FROM_HERE, factory_.NewRunnableMethod( 586 &ExpireHistoryBackend::DoArchiveIteration), delay.InMilliseconds()); 587 } 588 589 void ExpireHistoryBackend::DoArchiveIteration() { 590 DCHECK(!work_queue_.empty()) << "queue has to be non-empty"; 591 592 const ExpiringVisitsReader* reader = work_queue_.front(); 593 bool more_to_expire = ArchiveSomeOldHistory(GetCurrentArchiveTime(), reader, 594 kNumExpirePerIteration); 595 596 work_queue_.pop(); 597 // If there are more items to expire, add the reader back to the queue, thus 598 // creating a new task for future iterations. 599 if (more_to_expire) 600 work_queue_.push(reader); 601 602 ScheduleArchive(); 603 } 604 605 bool ExpireHistoryBackend::ArchiveSomeOldHistory( 606 base::Time end_time, 607 const ExpiringVisitsReader* reader, 608 int max_visits) { 609 if (!main_db_) 610 return false; 611 612 // Add an extra time unit to given end time, because 613 // GetAllVisitsInRange, et al. queries' end value is non-inclusive. 614 Time effective_end_time = 615 Time::FromInternalValue(end_time.ToInternalValue() + 1); 616 617 VisitVector affected_visits; 618 bool more_to_expire = reader->Read(effective_end_time, main_db_, 619 &affected_visits, max_visits); 620 621 // Some visits we'll delete while others we'll archive. 622 VisitVector deleted_visits, archived_visits; 623 for (size_t i = 0; i < affected_visits.size(); i++) { 624 if (ShouldArchiveVisit(affected_visits[i])) 625 archived_visits.push_back(affected_visits[i]); 626 else 627 deleted_visits.push_back(affected_visits[i]); 628 } 629 630 // Do the actual archiving. 631 DeleteDependencies archived_dependencies; 632 ArchiveURLsAndVisits(archived_visits, &archived_dependencies); 633 DeleteVisitRelatedInfo(archived_visits, &archived_dependencies); 634 635 DeleteDependencies deleted_dependencies; 636 DeleteVisitRelatedInfo(deleted_visits, &deleted_dependencies); 637 638 // This will remove or archive all the affected URLs. Must do the deleting 639 // cleanup before archiving so the delete dependencies structure references 640 // only those URLs that were actually deleted instead of having some visits 641 // archived and then the rest deleted. 642 ExpireURLsForVisits(deleted_visits, &deleted_dependencies); 643 ExpireURLsForVisits(archived_visits, &archived_dependencies); 644 645 // Create a union of all affected favicons (we don't store favicons for 646 // archived URLs) and delete them. 647 std::set<FaviconID> affected_favicons( 648 archived_dependencies.affected_favicons); 649 for (std::set<FaviconID>::const_iterator i = 650 deleted_dependencies.affected_favicons.begin(); 651 i != deleted_dependencies.affected_favicons.end(); ++i) { 652 affected_favicons.insert(*i); 653 } 654 DeleteFaviconsIfPossible(affected_favicons); 655 656 // Send notifications for the stuff that was deleted. These won't normally be 657 // in history views since they were subframes, but they will be in the visited 658 // link system, which needs to be updated now. This function is smart enough 659 // to not do anything if nothing was deleted. 660 BroadcastDeleteNotifications(&deleted_dependencies); 661 662 return more_to_expire; 663 } 664 665 void ExpireHistoryBackend::ParanoidExpireHistory() { 666 // TODO(brettw): Bug 1067331: write this to clean up any errors. 667 } 668 669 void ExpireHistoryBackend::ScheduleExpireHistoryIndexFiles() { 670 if (!text_db_) { 671 // Can't expire old history index files because we 672 // don't know where they're located. 673 return; 674 } 675 676 TimeDelta delay = TimeDelta::FromMinutes(kIndexExpirationDelayMin); 677 MessageLoop::current()->PostDelayedTask( 678 FROM_HERE, factory_.NewRunnableMethod( 679 &ExpireHistoryBackend::DoExpireHistoryIndexFiles), 680 delay.InMilliseconds()); 681 } 682 683 void ExpireHistoryBackend::DoExpireHistoryIndexFiles() { 684 Time::Exploded exploded; 685 Time::Now().LocalExplode(&exploded); 686 int cutoff_month = 687 exploded.year * 12 + exploded.month - kStoreHistoryIndexesForMonths; 688 TextDatabase::DBIdent cutoff_id = 689 (cutoff_month / 12) * 100 + (cutoff_month % 12); 690 691 FilePath::StringType history_index_files_pattern = TextDatabase::file_base(); 692 history_index_files_pattern.append(FILE_PATH_LITERAL("*")); 693 file_util::FileEnumerator file_enumerator( 694 text_db_->GetDir(), false, file_util::FileEnumerator::FILES, 695 history_index_files_pattern); 696 for (FilePath file = file_enumerator.Next(); !file.empty(); 697 file = file_enumerator.Next()) { 698 TextDatabase::DBIdent file_id = TextDatabase::FileNameToID(file); 699 if (file_id < cutoff_id) 700 file_util::Delete(file, false); 701 } 702 } 703 704 BookmarkService* ExpireHistoryBackend::GetBookmarkService() { 705 // We use the bookmark service to determine if a URL is bookmarked. The 706 // bookmark service is loaded on a separate thread and may not be done by the 707 // time we get here. We therefor block until the bookmarks have finished 708 // loading. 709 if (bookmark_service_) 710 bookmark_service_->BlockTillLoaded(); 711 return bookmark_service_; 712 } 713 714 } // namespace history 715