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
22using base::Time;
23using base::TimeDelta;
24
25namespace history {
26
27namespace {
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.
31const 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.
35class 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.
55class 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.
88bool 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.
113const 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."
119const 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.
125const int kExpirationEmptyDelayMin = 5;
126
127// The number of minutes that we wait for before scheduling a task to
128// delete old history index files.
129const 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.
133const int kStoreHistoryIndexesForMonths = 12;
134
135}  // namespace
136
137struct 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
163ExpireHistoryBackend::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
175ExpireHistoryBackend::~ExpireHistoryBackend() {
176}
177
178void 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
188void 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
224void 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
269void 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
279void 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
286const ExpiringVisitsReader* ExpireHistoryBackend::GetAllVisitsReader() {
287  if (!all_visits_reader_.get())
288    all_visits_reader_.reset(new AllVisitsReader());
289  return all_visits_reader_.get();
290}
291
292const 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
299void 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
318void 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
330void 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
365void 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
394void 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
430URLID 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
451namespace {
452
453struct ChangedURL {
454  ChangedURL() : visit_count(0), typed_count(0) {}
455  int visit_count;
456  int typed_count;
457};
458
459}  // namespace
460
461void 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
519void 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
574void 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
589void 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
605bool 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
665void ExpireHistoryBackend::ParanoidExpireHistory() {
666  // TODO(brettw): Bug 1067331: write this to clean up any errors.
667}
668
669void 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
683void 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
704BookmarkService* 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