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
15namespace browser_sync {
16
17const char kTypedUrlTag[] = "google_chrome_typed_urls";
18
19TypedUrlModelAssociator::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
31TypedUrlModelAssociator::~TypedUrlModelAssociator() {}
32
33bool 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
185bool 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
203bool TypedUrlModelAssociator::DisassociateModels() {
204  id_map_.clear();
205  id_map_inverse_.clear();
206  return true;
207}
208
209bool 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
233void TypedUrlModelAssociator::AbortAssociation() {
234  // TODO(zork): Implement this.
235}
236
237const std::string* TypedUrlModelAssociator::GetChromeNodeFromSyncId(
238    int64 sync_id) {
239  return NULL;
240}
241
242bool TypedUrlModelAssociator::InitSyncNodeFromChromeId(
243    const std::string& node_id,
244    sync_api::BaseNode* sync_node) {
245  return false;
246}
247
248int64 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
254void 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
264void 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
273bool 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
283bool 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
327int 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
435void 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
458void 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
490bool 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