Home | History | Annotate | Download | only in glue
      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/sync/glue/typed_url_change_processor.h"
      6 
      7 #include "base/string_util.h"
      8 #include "base/utf_string_conversions.h"
      9 #include "chrome/browser/history/history_backend.h"
     10 #include "chrome/browser/history/history_notifications.h"
     11 #include "chrome/browser/profiles/profile.h"
     12 #include "chrome/browser/sync/glue/typed_url_model_associator.h"
     13 #include "chrome/browser/sync/profile_sync_service.h"
     14 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
     15 #include "content/common/notification_service.h"
     16 #include "content/common/notification_type.h"
     17 
     18 namespace browser_sync {
     19 
     20 TypedUrlChangeProcessor::TypedUrlChangeProcessor(
     21     TypedUrlModelAssociator* model_associator,
     22     history::HistoryBackend* history_backend,
     23     UnrecoverableErrorHandler* error_handler)
     24     : ChangeProcessor(error_handler),
     25       model_associator_(model_associator),
     26       history_backend_(history_backend),
     27       observing_(false),
     28       expected_loop_(MessageLoop::current()) {
     29   DCHECK(model_associator);
     30   DCHECK(history_backend);
     31   DCHECK(error_handler);
     32   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
     33   // When running in unit tests, there is already a NotificationService object.
     34   // Since only one can exist at a time per thread, check first.
     35   if (!NotificationService::current())
     36     notification_service_.reset(new NotificationService);
     37   StartObserving();
     38 }
     39 
     40 TypedUrlChangeProcessor::~TypedUrlChangeProcessor() {
     41   DCHECK(expected_loop_ == MessageLoop::current());
     42 }
     43 
     44 void TypedUrlChangeProcessor::Observe(NotificationType type,
     45                                       const NotificationSource& source,
     46                                       const NotificationDetails& details) {
     47   DCHECK(expected_loop_ == MessageLoop::current());
     48   if (!observing_)
     49     return;
     50 
     51   VLOG(1) << "Observed typed_url change.";
     52   DCHECK(running());
     53   DCHECK(NotificationType::HISTORY_TYPED_URLS_MODIFIED == type ||
     54          NotificationType::HISTORY_URLS_DELETED == type ||
     55          NotificationType::HISTORY_URL_VISITED == type);
     56   if (type == NotificationType::HISTORY_TYPED_URLS_MODIFIED) {
     57     HandleURLsModified(Details<history::URLsModifiedDetails>(details).ptr());
     58   } else if (type == NotificationType::HISTORY_URLS_DELETED) {
     59     HandleURLsDeleted(Details<history::URLsDeletedDetails>(details).ptr());
     60   } else if (type == NotificationType::HISTORY_URL_VISITED) {
     61     HandleURLsVisited(Details<history::URLVisitedDetails>(details).ptr());
     62   }
     63 }
     64 
     65 void TypedUrlChangeProcessor::HandleURLsModified(
     66     history::URLsModifiedDetails* details) {
     67   // Get all the visits.
     68   std::map<history::URLID, history::VisitVector> visit_vectors;
     69   for (std::vector<history::URLRow>::iterator url =
     70        details->changed_urls.begin(); url != details->changed_urls.end();
     71        ++url) {
     72     if (!history_backend_->GetVisitsForURL(url->id(),
     73                                            &(visit_vectors[url->id()]))) {
     74       error_handler()->OnUnrecoverableError(FROM_HERE,
     75           "Could not get the url's visits.");
     76       return;
     77     }
     78     DCHECK(!visit_vectors[url->id()].empty());
     79   }
     80 
     81   sync_api::WriteTransaction trans(share_handle());
     82 
     83   sync_api::ReadNode typed_url_root(&trans);
     84   if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
     85     error_handler()->OnUnrecoverableError(FROM_HERE,
     86         "Server did not create the top-level typed_url node. We "
     87          "might be running against an out-of-date server.");
     88     return;
     89   }
     90 
     91   for (std::vector<history::URLRow>::iterator url =
     92        details->changed_urls.begin(); url != details->changed_urls.end();
     93        ++url) {
     94     std::string tag = url->url().spec();
     95 
     96     history::VisitVector& visits = visit_vectors[url->id()];
     97 
     98     DCHECK(!visits.empty());
     99 
    100     DCHECK(static_cast<size_t>(url->visit_count()) == visits.size());
    101     if (static_cast<size_t>(url->visit_count()) != visits.size()) {
    102       error_handler()->OnUnrecoverableError(FROM_HERE,
    103           "Visit count does not match.");
    104       return;
    105     }
    106 
    107     sync_api::WriteNode update_node(&trans);
    108     if (update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
    109       model_associator_->WriteToSyncNode(*url, visits, &update_node);
    110     } else {
    111       sync_api::WriteNode create_node(&trans);
    112       if (!create_node.InitUniqueByCreation(syncable::TYPED_URLS,
    113                                             typed_url_root, tag)) {
    114         error_handler()->OnUnrecoverableError(FROM_HERE,
    115             "Failed to create typed_url sync node.");
    116         return;
    117       }
    118 
    119       create_node.SetTitle(UTF8ToWide(tag));
    120       model_associator_->WriteToSyncNode(*url, visits, &create_node);
    121 
    122       model_associator_->Associate(&tag, create_node.GetId());
    123     }
    124   }
    125 }
    126 
    127 void TypedUrlChangeProcessor::HandleURLsDeleted(
    128     history::URLsDeletedDetails* details) {
    129   sync_api::WriteTransaction trans(share_handle());
    130 
    131   if (details->all_history) {
    132     if (!model_associator_->DeleteAllNodes(&trans)) {
    133       error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    134       return;
    135     }
    136   } else {
    137     for (std::set<GURL>::iterator url = details->urls.begin();
    138          url != details->urls.end(); ++url) {
    139       sync_api::WriteNode sync_node(&trans);
    140       int64 sync_id =
    141       model_associator_->GetSyncIdFromChromeId(url->spec());
    142       if (sync_api::kInvalidId != sync_id) {
    143         if (!sync_node.InitByIdLookup(sync_id)) {
    144           error_handler()->OnUnrecoverableError(FROM_HERE,
    145               "Typed url node lookup failed.");
    146           return;
    147         }
    148         model_associator_->Disassociate(sync_node.GetId());
    149         sync_node.Remove();
    150       }
    151     }
    152   }
    153 }
    154 
    155 void TypedUrlChangeProcessor::HandleURLsVisited(
    156     history::URLVisitedDetails* details) {
    157   if (!details->row.typed_count()) {
    158     // We only care about typed urls.
    159     return;
    160   }
    161   history::VisitVector visits;
    162   if (!history_backend_->GetVisitsForURL(details->row.id(), &visits) ||
    163       visits.empty()) {
    164     error_handler()->OnUnrecoverableError(FROM_HERE,
    165         "Could not get the url's visits.");
    166     return;
    167   }
    168 
    169   DCHECK(static_cast<size_t>(details->row.visit_count()) == visits.size());
    170 
    171   sync_api::WriteTransaction trans(share_handle());
    172   std::string tag = details->row.url().spec();
    173   sync_api::WriteNode update_node(&trans);
    174   if (!update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
    175     // If we don't know about it yet, it will be added later.
    176     return;
    177   }
    178   sync_pb::TypedUrlSpecifics typed_url(update_node.GetTypedUrlSpecifics());
    179   typed_url.add_visit(visits.back().visit_time.ToInternalValue());
    180   update_node.SetTypedUrlSpecifics(typed_url);
    181 }
    182 
    183 void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
    184     const sync_api::BaseTransaction* trans,
    185     const sync_api::SyncManager::ChangeRecord* changes,
    186     int change_count) {
    187   DCHECK(expected_loop_ == MessageLoop::current());
    188   if (!running())
    189     return;
    190   StopObserving();
    191 
    192   sync_api::ReadNode typed_url_root(trans);
    193   if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
    194     error_handler()->OnUnrecoverableError(FROM_HERE,
    195         "TypedUrl root node lookup failed.");
    196     return;
    197   }
    198 
    199   TypedUrlModelAssociator::TypedUrlTitleVector titles;
    200   TypedUrlModelAssociator::TypedUrlVector new_urls;
    201   TypedUrlModelAssociator::TypedUrlVisitVector new_visits;
    202   history::VisitVector deleted_visits;
    203   TypedUrlModelAssociator::TypedUrlUpdateVector updated_urls;
    204 
    205   for (int i = 0; i < change_count; ++i) {
    206     if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
    207         changes[i].action) {
    208       DCHECK(changes[i].specifics.HasExtension(sync_pb::typed_url)) <<
    209           "Typed URL delete change does not have necessary specifics.";
    210       GURL url(changes[i].specifics.GetExtension(sync_pb::typed_url).url());
    211       history_backend_->DeleteURL(url);
    212       continue;
    213     }
    214 
    215     sync_api::ReadNode sync_node(trans);
    216     if (!sync_node.InitByIdLookup(changes[i].id)) {
    217       error_handler()->OnUnrecoverableError(FROM_HERE,
    218           "TypedUrl node lookup failed.");
    219       return;
    220     }
    221 
    222     // Check that the changed node is a child of the typed_urls folder.
    223     DCHECK(typed_url_root.GetId() == sync_node.GetParentId());
    224     DCHECK(syncable::TYPED_URLS == sync_node.GetModelType());
    225 
    226     const sync_pb::TypedUrlSpecifics& typed_url(
    227         sync_node.GetTypedUrlSpecifics());
    228     GURL url(typed_url.url());
    229 
    230     if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) {
    231       DCHECK(typed_url.visit_size());
    232       if (!typed_url.visit_size()) {
    233         continue;
    234       }
    235 
    236       history::URLRow new_url(url);
    237       new_url.set_title(UTF8ToUTF16(typed_url.title()));
    238 
    239       // When we add a new url, the last visit is always added, thus we set
    240       // the initial visit count to one.  This value will be automatically
    241       // incremented as visits are added.
    242       new_url.set_visit_count(1);
    243       new_url.set_typed_count(typed_url.typed_count());
    244       new_url.set_hidden(typed_url.hidden());
    245 
    246       new_url.set_last_visit(base::Time::FromInternalValue(
    247           typed_url.visit(typed_url.visit_size() - 1)));
    248 
    249       new_urls.push_back(new_url);
    250 
    251       // The latest visit gets added automatically, so skip it.
    252       std::vector<base::Time> added_visits;
    253       for (int c = 0; c < typed_url.visit_size() - 1; ++c) {
    254         DCHECK(typed_url.visit(c) < typed_url.visit(c + 1));
    255         added_visits.push_back(
    256             base::Time::FromInternalValue(typed_url.visit(c)));
    257       }
    258 
    259       new_visits.push_back(
    260           std::pair<GURL, std::vector<base::Time> >(url, added_visits));
    261     } else {
    262       history::URLRow old_url;
    263       if (!history_backend_->GetURL(url, &old_url)) {
    264         error_handler()->OnUnrecoverableError(FROM_HERE,
    265             "TypedUrl db lookup failed.");
    266         return;
    267       }
    268 
    269       history::VisitVector visits;
    270       if (!history_backend_->GetVisitsForURL(old_url.id(), &visits)) {
    271         error_handler()->OnUnrecoverableError(FROM_HERE,
    272             "Could not get the url's visits.");
    273         return;
    274       }
    275 
    276       history::URLRow new_url(url);
    277       new_url.set_title(UTF8ToUTF16(typed_url.title()));
    278       new_url.set_visit_count(old_url.visit_count());
    279       new_url.set_typed_count(typed_url.typed_count());
    280       new_url.set_last_visit(old_url.last_visit());
    281       new_url.set_hidden(typed_url.hidden());
    282 
    283       updated_urls.push_back(
    284         std::pair<history::URLID, history::URLRow>(old_url.id(), new_url));
    285 
    286       if (old_url.title().compare(new_url.title()) != 0) {
    287         titles.push_back(std::pair<GURL, string16>(new_url.url(),
    288                                                    new_url.title()));
    289       }
    290 
    291       std::vector<base::Time> added_visits;
    292       history::VisitVector removed_visits;
    293       TypedUrlModelAssociator::DiffVisits(visits, typed_url,
    294                                           &added_visits, &removed_visits);
    295       if (added_visits.size()) {
    296         new_visits.push_back(
    297           std::pair<GURL, std::vector<base::Time> >(url, added_visits));
    298       }
    299       if (removed_visits.size()) {
    300         deleted_visits.insert(deleted_visits.end(), removed_visits.begin(),
    301                               removed_visits.end());
    302       }
    303     }
    304   }
    305   if (!model_associator_->WriteToHistoryBackend(&titles, &new_urls,
    306                                                 &updated_urls,
    307                                                 &new_visits, &deleted_visits)) {
    308     error_handler()->OnUnrecoverableError(FROM_HERE,
    309         "Could not write to the history backend.");
    310     return;
    311   }
    312 
    313   StartObserving();
    314 }
    315 
    316 void TypedUrlChangeProcessor::StartImpl(Profile* profile) {
    317   DCHECK(expected_loop_ == MessageLoop::current());
    318   observing_ = true;
    319 }
    320 
    321 void TypedUrlChangeProcessor::StopImpl() {
    322   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    323   observing_ = false;
    324 }
    325 
    326 
    327 void TypedUrlChangeProcessor::StartObserving() {
    328   DCHECK(expected_loop_ == MessageLoop::current());
    329   notification_registrar_.Add(this,
    330                               NotificationType::HISTORY_TYPED_URLS_MODIFIED,
    331                               NotificationService::AllSources());
    332   notification_registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
    333                               NotificationService::AllSources());
    334   notification_registrar_.Add(this, NotificationType::HISTORY_URL_VISITED,
    335                               NotificationService::AllSources());
    336 }
    337 
    338 void TypedUrlChangeProcessor::StopObserving() {
    339   DCHECK(expected_loop_ == MessageLoop::current());
    340   notification_registrar_.Remove(this,
    341                                  NotificationType::HISTORY_TYPED_URLS_MODIFIED,
    342                                  NotificationService::AllSources());
    343   notification_registrar_.Remove(this,
    344                                  NotificationType::HISTORY_URLS_DELETED,
    345                                  NotificationService::AllSources());
    346   notification_registrar_.Remove(this,
    347                                  NotificationType::HISTORY_URL_VISITED,
    348                                  NotificationService::AllSources());
    349 }
    350 
    351 }  // namespace browser_sync
    352