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