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_model_associator.h"
      6 
      7 #include <set>
      8 
      9 #include "base/utf_string_conversions.h"
     10 #include "chrome/browser/history/history_backend.h"
     11 #include "chrome/browser/sync/engine/syncapi.h"
     12 #include "chrome/browser/sync/profile_sync_service.h"
     13 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
     14 
     15 namespace browser_sync {
     16 
     17 const char kTypedUrlTag[] = "google_chrome_typed_urls";
     18 
     19 TypedUrlModelAssociator::TypedUrlModelAssociator(
     20     ProfileSyncService* sync_service,
     21     history::HistoryBackend* history_backend)
     22     : sync_service_(sync_service),
     23       history_backend_(history_backend),
     24       typed_url_node_id_(sync_api::kInvalidId),
     25       expected_loop_(MessageLoop::current()) {
     26   DCHECK(sync_service_);
     27   DCHECK(history_backend_);
     28   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
     29 }
     30 
     31 TypedUrlModelAssociator::~TypedUrlModelAssociator() {}
     32 
     33 bool TypedUrlModelAssociator::AssociateModels() {
     34   VLOG(1) << "Associating TypedUrl Models";
     35   DCHECK(expected_loop_ == MessageLoop::current());
     36 
     37   std::vector<history::URLRow> typed_urls;
     38   if (!history_backend_->GetAllTypedURLs(&typed_urls)) {
     39     LOG(ERROR) << "Could not get the typed_url entries.";
     40     return false;
     41   }
     42 
     43   // Get all the visits.
     44   std::map<history::URLID, history::VisitVector> visit_vectors;
     45   for (std::vector<history::URLRow>::iterator ix = typed_urls.begin();
     46        ix != typed_urls.end(); ++ix) {
     47     if (!history_backend_->GetVisitsForURL(ix->id(),
     48                                            &(visit_vectors[ix->id()]))) {
     49       LOG(ERROR) << "Could not get the url's visits.";
     50       return false;
     51     }
     52     DCHECK(!visit_vectors[ix->id()].empty());
     53   }
     54 
     55   TypedUrlTitleVector titles;
     56   TypedUrlVector new_urls;
     57   TypedUrlVisitVector new_visits;
     58   TypedUrlUpdateVector updated_urls;
     59 
     60   {
     61     sync_api::WriteTransaction trans(sync_service_->GetUserShare());
     62     sync_api::ReadNode typed_url_root(&trans);
     63     if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
     64       LOG(ERROR) << "Server did not create the top-level typed_url node. We "
     65                  << "might be running against an out-of-date server.";
     66       return false;
     67     }
     68 
     69     std::set<std::string> current_urls;
     70     for (std::vector<history::URLRow>::iterator ix = typed_urls.begin();
     71          ix != typed_urls.end(); ++ix) {
     72       std::string tag = ix->url().spec();
     73 
     74       history::VisitVector& visits = visit_vectors[ix->id()];
     75       DCHECK(visits.size() == static_cast<size_t>(ix->visit_count()));
     76       if (visits.size() != static_cast<size_t>(ix->visit_count())) {
     77         LOG(ERROR) << "Visit count does not match.";
     78         return false;
     79       }
     80 
     81       sync_api::ReadNode node(&trans);
     82       if (node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
     83         const sync_pb::TypedUrlSpecifics& typed_url(
     84             node.GetTypedUrlSpecifics());
     85         DCHECK_EQ(tag, typed_url.url());
     86 
     87         history::URLRow new_url(ix->url());
     88 
     89         std::vector<base::Time> added_visits;
     90         int difference = MergeUrls(typed_url, *ix, &visits, &new_url,
     91                                    &added_visits);
     92         if (difference & DIFF_NODE_CHANGED) {
     93           sync_api::WriteNode write_node(&trans);
     94           if (!write_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
     95             LOG(ERROR) << "Failed to edit typed_url sync node.";
     96             return false;
     97           }
     98           WriteToSyncNode(new_url, visits, &write_node);
     99         }
    100         if (difference & DIFF_TITLE_CHANGED) {
    101           titles.push_back(std::pair<GURL, string16>(new_url.url(),
    102                                                      new_url.title()));
    103         }
    104         if (difference & DIFF_ROW_CHANGED) {
    105           updated_urls.push_back(
    106               std::pair<history::URLID, history::URLRow>(ix->id(), new_url));
    107         }
    108         if (difference & DIFF_VISITS_ADDED) {
    109           new_visits.push_back(
    110               std::pair<GURL, std::vector<base::Time> >(ix->url(),
    111                                                         added_visits));
    112         }
    113 
    114         Associate(&tag, node.GetId());
    115       } else {
    116         sync_api::WriteNode node(&trans);
    117         if (!node.InitUniqueByCreation(syncable::TYPED_URLS,
    118                                        typed_url_root, tag)) {
    119           LOG(ERROR) << "Failed to create typed_url sync node.";
    120           return false;
    121         }
    122 
    123         node.SetTitle(UTF8ToWide(tag));
    124         WriteToSyncNode(*ix, visits, &node);
    125 
    126         Associate(&tag, node.GetId());
    127       }
    128 
    129       current_urls.insert(tag);
    130     }
    131 
    132     int64 sync_child_id = typed_url_root.GetFirstChildId();
    133     while (sync_child_id != sync_api::kInvalidId) {
    134       sync_api::ReadNode sync_child_node(&trans);
    135       if (!sync_child_node.InitByIdLookup(sync_child_id)) {
    136         LOG(ERROR) << "Failed to fetch child node.";
    137         return false;
    138       }
    139       const sync_pb::TypedUrlSpecifics& typed_url(
    140         sync_child_node.GetTypedUrlSpecifics());
    141 
    142       if (current_urls.find(typed_url.url()) == current_urls.end()) {
    143         new_visits.push_back(
    144             std::pair<GURL, std::vector<base::Time> >(
    145                 GURL(typed_url.url()),
    146                 std::vector<base::Time>()));
    147         std::vector<base::Time>& visits = new_visits.back().second;
    148         history::URLRow new_url(GURL(typed_url.url()));
    149 
    150         new_url.set_title(UTF8ToUTF16(typed_url.title()));
    151 
    152         // When we add a new url, the last visit is always added, thus we set
    153         // the initial visit count to one.  This value will be automatically
    154         // incremented as visits are added.
    155         new_url.set_visit_count(1);
    156         new_url.set_typed_count(typed_url.typed_count());
    157         new_url.set_hidden(typed_url.hidden());
    158 
    159         // The latest visit gets added automatically, so skip it.
    160         for (int c = 0; c < typed_url.visit_size() - 1; ++c) {
    161           DCHECK(typed_url.visit(c) < typed_url.visit(c + 1));
    162           visits.push_back(base::Time::FromInternalValue(typed_url.visit(c)));
    163         }
    164 
    165         new_url.set_last_visit(base::Time::FromInternalValue(
    166             typed_url.visit(typed_url.visit_size() - 1)));
    167 
    168         Associate(&typed_url.url(), sync_child_node.GetId());
    169         new_urls.push_back(new_url);
    170       }
    171 
    172       sync_child_id = sync_child_node.GetSuccessorId();
    173     }
    174   }
    175 
    176   // Since we're on the history thread, we don't have to worry about updating
    177   // the history database after closing the write transaction, since
    178   // this is the only thread that writes to the database.  We also don't have
    179   // to worry about the sync model getting out of sync, because changes are
    180   // propagated to the ChangeProcessor on this thread.
    181   return WriteToHistoryBackend(&titles, &new_urls, &updated_urls,
    182                                &new_visits, NULL);
    183 }
    184 
    185 bool TypedUrlModelAssociator::DeleteAllNodes(
    186     sync_api::WriteTransaction* trans) {
    187   DCHECK(expected_loop_ == MessageLoop::current());
    188   for (TypedUrlToSyncIdMap::iterator node_id = id_map_.begin();
    189        node_id != id_map_.end(); ++node_id) {
    190     sync_api::WriteNode sync_node(trans);
    191     if (!sync_node.InitByIdLookup(node_id->second)) {
    192       LOG(ERROR) << "Typed url node lookup failed.";
    193       return false;
    194     }
    195     sync_node.Remove();
    196   }
    197 
    198   id_map_.clear();
    199   id_map_inverse_.clear();
    200   return true;
    201 }
    202 
    203 bool TypedUrlModelAssociator::DisassociateModels() {
    204   id_map_.clear();
    205   id_map_inverse_.clear();
    206   return true;
    207 }
    208 
    209 bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
    210   DCHECK(has_nodes);
    211   *has_nodes = false;
    212   int64 typed_url_sync_id;
    213   if (!GetSyncIdForTaggedNode(kTypedUrlTag, &typed_url_sync_id)) {
    214     LOG(ERROR) << "Server did not create the top-level typed_url node. We "
    215                << "might be running against an out-of-date server.";
    216     return false;
    217   }
    218   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    219 
    220   sync_api::ReadNode typed_url_node(&trans);
    221   if (!typed_url_node.InitByIdLookup(typed_url_sync_id)) {
    222     LOG(ERROR) << "Server did not create the top-level typed_url node. We "
    223                << "might be running against an out-of-date server.";
    224     return false;
    225   }
    226 
    227   // The sync model has user created nodes if the typed_url folder has any
    228   // children.
    229   *has_nodes = sync_api::kInvalidId != typed_url_node.GetFirstChildId();
    230   return true;
    231 }
    232 
    233 void TypedUrlModelAssociator::AbortAssociation() {
    234   // TODO(zork): Implement this.
    235 }
    236 
    237 const std::string* TypedUrlModelAssociator::GetChromeNodeFromSyncId(
    238     int64 sync_id) {
    239   return NULL;
    240 }
    241 
    242 bool TypedUrlModelAssociator::InitSyncNodeFromChromeId(
    243     const std::string& node_id,
    244     sync_api::BaseNode* sync_node) {
    245   return false;
    246 }
    247 
    248 int64 TypedUrlModelAssociator::GetSyncIdFromChromeId(
    249     const std::string& typed_url) {
    250   TypedUrlToSyncIdMap::const_iterator iter = id_map_.find(typed_url);
    251   return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
    252 }
    253 
    254 void TypedUrlModelAssociator::Associate(
    255     const std::string* typed_url, int64 sync_id) {
    256   DCHECK(expected_loop_ == MessageLoop::current());
    257   DCHECK_NE(sync_api::kInvalidId, sync_id);
    258   DCHECK(id_map_.find(*typed_url) == id_map_.end());
    259   DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
    260   id_map_[*typed_url] = sync_id;
    261   id_map_inverse_[sync_id] = *typed_url;
    262 }
    263 
    264 void TypedUrlModelAssociator::Disassociate(int64 sync_id) {
    265   DCHECK(expected_loop_ == MessageLoop::current());
    266   SyncIdToTypedUrlMap::iterator iter = id_map_inverse_.find(sync_id);
    267   if (iter == id_map_inverse_.end())
    268     return;
    269   CHECK(id_map_.erase(iter->second));
    270   id_map_inverse_.erase(iter);
    271 }
    272 
    273 bool TypedUrlModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
    274                                                      int64* sync_id) {
    275   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    276   sync_api::ReadNode sync_node(&trans);
    277   if (!sync_node.InitByTagLookup(tag.c_str()))
    278     return false;
    279   *sync_id = sync_node.GetId();
    280   return true;
    281 }
    282 
    283 bool TypedUrlModelAssociator::WriteToHistoryBackend(
    284     const TypedUrlTitleVector* titles,
    285     const TypedUrlVector* new_urls,
    286     const TypedUrlUpdateVector* updated_urls,
    287     const TypedUrlVisitVector* new_visits,
    288     const history::VisitVector* deleted_visits) {
    289   if (titles) {
    290     for (TypedUrlTitleVector::const_iterator title = titles->begin();
    291          title != titles->end(); ++title) {
    292       history_backend_->SetPageTitle(title->first, title->second);
    293     }
    294   }
    295   if (new_urls) {
    296     history_backend_->AddPagesWithDetails(*new_urls, history::SOURCE_SYNCED);
    297   }
    298   if (updated_urls) {
    299     for (TypedUrlUpdateVector::const_iterator url = updated_urls->begin();
    300          url != updated_urls->end(); ++url) {
    301       if (!history_backend_->UpdateURL(url->first, url->second)) {
    302         LOG(ERROR) << "Could not update page: " << url->second.url().spec();
    303         return false;
    304       }
    305     }
    306   }
    307   if (new_visits) {
    308     for (TypedUrlVisitVector::const_iterator visits = new_visits->begin();
    309          visits != new_visits->end(); ++visits) {
    310            if (!history_backend_->AddVisits(visits->first, visits->second,
    311                                             history::SOURCE_SYNCED)) {
    312         LOG(ERROR) << "Could not add visits.";
    313         return false;
    314       }
    315     }
    316   }
    317   if (deleted_visits) {
    318     if (!history_backend_->RemoveVisits(*deleted_visits)) {
    319       LOG(ERROR) << "Could not remove visits.";
    320       return false;
    321     }
    322   }
    323   return true;
    324 }
    325 
    326 // static
    327 int TypedUrlModelAssociator::MergeUrls(
    328     const sync_pb::TypedUrlSpecifics& typed_url,
    329     const history::URLRow& url,
    330     history::VisitVector* visits,
    331     history::URLRow* new_url,
    332     std::vector<base::Time>* new_visits) {
    333   DCHECK(new_url);
    334   DCHECK(!typed_url.url().compare(url.url().spec()));
    335   DCHECK(!typed_url.url().compare(new_url->url().spec()));
    336   DCHECK(visits->size());
    337 
    338   new_url->set_visit_count(visits->size());
    339 
    340   // Convert these values only once.
    341   string16 typed_title(UTF8ToUTF16(typed_url.title()));
    342   base::Time typed_visit =
    343       base::Time::FromInternalValue(
    344           typed_url.visit(typed_url.visit_size() - 1));
    345 
    346   // This is a bitfield represting what we'll need to update with the output
    347   // value.
    348   int different = DIFF_NONE;
    349 
    350   // Check if the non-incremented values changed.
    351   if ((typed_title.compare(url.title()) != 0) ||
    352       (typed_url.hidden() != url.hidden())) {
    353     // Use the values from the most recent visit.
    354     if (typed_visit >= url.last_visit()) {
    355       new_url->set_title(typed_title);
    356       new_url->set_hidden(typed_url.hidden());
    357       different |= DIFF_ROW_CHANGED;
    358 
    359       // If we're changing the local title, note this.
    360       if (new_url->title().compare(url.title()) != 0) {
    361         different |= DIFF_TITLE_CHANGED;
    362       }
    363     } else {
    364       new_url->set_title(url.title());
    365       new_url->set_hidden(url.hidden());
    366       different |= DIFF_NODE_CHANGED;
    367     }
    368   } else {
    369     // No difference.
    370     new_url->set_title(url.title());
    371     new_url->set_hidden(url.hidden());
    372   }
    373 
    374   // For typed count, we just select the maximum value.
    375   if (typed_url.typed_count() > url.typed_count()) {
    376     new_url->set_typed_count(typed_url.typed_count());
    377     different |= DIFF_ROW_CHANGED;
    378   } else if (typed_url.typed_count() < url.typed_count()) {
    379     new_url->set_typed_count(url.typed_count());
    380     different |= DIFF_NODE_CHANGED;
    381   } else {
    382     // No difference.
    383     new_url->set_typed_count(typed_url.typed_count());
    384   }
    385 
    386   size_t left_visit_count = typed_url.visit_size();
    387   size_t right_visit_count = visits->size();
    388   size_t left = 0;
    389   size_t right = 0;
    390   while (left < left_visit_count && right < right_visit_count) {
    391     base::Time left_time = base::Time::FromInternalValue(typed_url.visit(left));
    392     if (left_time < (*visits)[right].visit_time) {
    393       different |= DIFF_VISITS_ADDED;
    394       new_visits->push_back(left_time);
    395       // This visit is added to visits below.
    396       ++left;
    397     } else if (left_time > (*visits)[right].visit_time) {
    398       different |= DIFF_NODE_CHANGED;
    399       ++right;
    400     } else {
    401       ++left;
    402       ++right;
    403     }
    404   }
    405 
    406   for ( ; left < left_visit_count; ++left) {
    407     different |= DIFF_VISITS_ADDED;
    408     base::Time left_time = base::Time::FromInternalValue(typed_url.visit(left));
    409     new_visits->push_back(left_time);
    410     // This visit is added to visits below.
    411   }
    412   if (different & DIFF_VISITS_ADDED) {
    413     history::VisitVector::iterator visit_ix = visits->begin();
    414     for (std::vector<base::Time>::iterator new_visit = new_visits->begin();
    415          new_visit != new_visits->end(); ++new_visit) {
    416       while (visit_ix != visits->end() && *new_visit > visit_ix->visit_time) {
    417         ++visit_ix;
    418       }
    419       visit_ix = visits->insert(visit_ix,
    420                                 history::VisitRow(url.id(), *new_visit,
    421                                                   0, 0, 0));
    422       ++visit_ix;
    423     }
    424   }
    425 
    426   new_url->set_last_visit(visits->back().visit_time);
    427 
    428   DCHECK(static_cast<size_t>(new_url->visit_count()) ==
    429          (visits->size() - new_visits->size()));
    430 
    431   return different;
    432 }
    433 
    434 // static
    435 void TypedUrlModelAssociator::WriteToSyncNode(
    436     const history::URLRow& url,
    437     const history::VisitVector& visits,
    438     sync_api::WriteNode* node) {
    439   DCHECK(!url.last_visit().is_null());
    440   DCHECK(!visits.empty());
    441   DCHECK(url.last_visit() == visits.back().visit_time);
    442 
    443   sync_pb::TypedUrlSpecifics typed_url;
    444   typed_url.set_url(url.url().spec());
    445   typed_url.set_title(UTF16ToUTF8(url.title()));
    446   typed_url.set_typed_count(url.typed_count());
    447   typed_url.set_hidden(url.hidden());
    448 
    449   for (history::VisitVector::const_iterator visit = visits.begin();
    450        visit != visits.end(); ++visit) {
    451     typed_url.add_visit(visit->visit_time.ToInternalValue());
    452   }
    453 
    454   node->SetTypedUrlSpecifics(typed_url);
    455 }
    456 
    457 // static
    458 void TypedUrlModelAssociator::DiffVisits(
    459     const history::VisitVector& old_visits,
    460     const sync_pb::TypedUrlSpecifics& new_url,
    461     std::vector<base::Time>* new_visits,
    462     history::VisitVector* removed_visits) {
    463   size_t left_visit_count = old_visits.size();
    464   size_t right_visit_count = new_url.visit_size();
    465   size_t left = 0;
    466   size_t right = 0;
    467   while (left < left_visit_count && right < right_visit_count) {
    468     base::Time right_time = base::Time::FromInternalValue(new_url.visit(right));
    469     if (old_visits[left].visit_time < right_time) {
    470       removed_visits->push_back(old_visits[left]);
    471       ++left;
    472     } else if (old_visits[left].visit_time > right_time) {
    473       new_visits->push_back(right_time);
    474       ++right;
    475     } else {
    476       ++left;
    477       ++right;
    478     }
    479   }
    480 
    481   for ( ; left < left_visit_count; ++left) {
    482     removed_visits->push_back(old_visits[left]);
    483   }
    484 
    485   for ( ; right < right_visit_count; ++right) {
    486     new_visits->push_back(base::Time::FromInternalValue(new_url.visit(right)));
    487   }
    488 }
    489 
    490 bool TypedUrlModelAssociator::CryptoReadyIfNecessary() {
    491   // We only access the cryptographer while holding a transaction.
    492   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    493   syncable::ModelTypeSet encrypted_types;
    494   sync_service_->GetEncryptedDataTypes(&encrypted_types);
    495   return encrypted_types.count(syncable::TYPED_URLS) == 0 ||
    496          sync_service_->IsCryptographerReady(&trans);
    497 }
    498 
    499 }  // namespace browser_sync
    500