profile_writer.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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/importer/profile_writer.h"
6
7#include "base/string_util.h"
8#include "base/threading/thread.h"
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/bookmarks/bookmark_model.h"
11#include "chrome/browser/importer/importer.h"
12#include "chrome/browser/password_manager/password_store.h"
13#include "chrome/browser/prefs/pref_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/search_engines/template_url.h"
16#include "chrome/browser/search_engines/template_url_model.h"
17#include "chrome/common/notification_service.h"
18#include "chrome/common/pref_names.h"
19
20using webkit_glue::PasswordForm;
21
22ProfileWriter::BookmarkEntry::BookmarkEntry() : in_toolbar(false),
23    is_folder(false) {}
24
25ProfileWriter::BookmarkEntry::~BookmarkEntry() {}
26
27ProfileWriter::ProfileWriter(Profile* profile) : profile_(profile) {}
28
29bool ProfileWriter::BookmarkModelIsLoaded() const {
30  return profile_->GetBookmarkModel()->IsLoaded();
31}
32
33bool ProfileWriter::TemplateURLModelIsLoaded() const {
34  return profile_->GetTemplateURLModel()->loaded();
35}
36
37void ProfileWriter::AddPasswordForm(const PasswordForm& form) {
38  profile_->GetPasswordStore(Profile::EXPLICIT_ACCESS)->AddLogin(form);
39}
40
41#if defined(OS_WIN)
42void ProfileWriter::AddIE7PasswordInfo(const IE7PasswordInfo& info) {
43  profile_->GetWebDataService(Profile::EXPLICIT_ACCESS)->AddIE7Login(info);
44}
45#endif
46
47void ProfileWriter::AddHistoryPage(const std::vector<history::URLRow>& page,
48                                   history::VisitSource visit_source) {
49  profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->
50      AddPagesWithDetails(page, visit_source);
51}
52
53void ProfileWriter::AddHomepage(const GURL& home_page) {
54  DCHECK(profile_);
55
56  PrefService* prefs = profile_->GetPrefs();
57  // NOTE: We set the kHomePage value, but keep the NewTab page as the homepage.
58  const PrefService::Preference* pref = prefs->FindPreference(prefs::kHomePage);
59  if (pref && !pref->IsManaged()) {
60    prefs->SetString(prefs::kHomePage, home_page.spec());
61    prefs->ScheduleSavePersistentPrefs();
62  }
63}
64
65void ProfileWriter::AddBookmarkEntry(
66    const std::vector<BookmarkEntry>& bookmark,
67    const std::wstring& first_folder_name,
68    int options) {
69  BookmarkModel* model = profile_->GetBookmarkModel();
70  DCHECK(model->IsLoaded());
71
72  bool import_to_bookmark_bar = ((options & IMPORT_TO_BOOKMARK_BAR) != 0);
73  std::wstring real_first_folder = import_to_bookmark_bar ? first_folder_name :
74      GenerateUniqueFolderName(model, first_folder_name);
75
76  bool show_bookmark_toolbar = false;
77  std::set<const BookmarkNode*> groups_added_to;
78  bool import_mode = false;
79  if (bookmark.size() > 1) {
80    model->BeginImportMode();
81    import_mode = true;
82  }
83  for (std::vector<BookmarkEntry>::const_iterator it = bookmark.begin();
84       it != bookmark.end(); ++it) {
85    // Don't insert this url if it isn't valid.
86    if (!it->is_folder && !it->url.is_valid())
87      continue;
88
89    // We suppose that bookmarks are unique by Title, URL, and Folder.  Since
90    // checking for uniqueness may not be always the user's intention we have
91    // this as an option.
92    if (options & ADD_IF_UNIQUE && DoesBookmarkExist(model, *it,
93        real_first_folder, import_to_bookmark_bar))
94      continue;
95
96    // Set up groups in BookmarkModel in such a way that path[i] is
97    // the subgroup of path[i-1]. Finally they construct a path in the
98    // model:
99    //   path[0] \ path[1] \ ... \ path[size() - 1]
100    const BookmarkNode* parent =
101        (it->in_toolbar ? model->GetBookmarkBarNode() : model->other_node());
102    for (std::vector<std::wstring>::const_iterator i = it->path.begin();
103         i != it->path.end(); ++i) {
104      const BookmarkNode* child = NULL;
105      const std::wstring& folder_name = (!import_to_bookmark_bar &&
106          !it->in_toolbar && (i == it->path.begin())) ? real_first_folder : *i;
107
108      for (int index = 0; index < parent->GetChildCount(); ++index) {
109        const BookmarkNode* node = parent->GetChild(index);
110        if ((node->type() == BookmarkNode::BOOKMARK_BAR ||
111             node->type() == BookmarkNode::FOLDER) &&
112            node->GetTitle() == WideToUTF16Hack(folder_name)) {
113          child = node;
114          break;
115        }
116      }
117      if (child == NULL)
118        child = model->AddGroup(parent, parent->GetChildCount(),
119                                WideToUTF16Hack(folder_name));
120      parent = child;
121    }
122    groups_added_to.insert(parent);
123    if (it->is_folder) {
124      model->AddGroup(parent, parent->GetChildCount(),
125                      WideToUTF16Hack(it->title));
126    } else {
127      model->AddURLWithCreationTime(parent, parent->GetChildCount(),
128          WideToUTF16Hack(it->title), it->url, it->creation_time);
129    }
130
131    // If some items are put into toolbar, it looks like the user was using
132    // it in their last browser. We turn on the bookmarks toolbar.
133    if (it->in_toolbar)
134      show_bookmark_toolbar = true;
135  }
136
137  // Reset the date modified time of the groups we added to. We do this to
138  // make sure the 'recently added to' combobox in the bubble doesn't get random
139  // groups.
140  for (std::set<const BookmarkNode*>::const_iterator i =
141          groups_added_to.begin();
142       i != groups_added_to.end(); ++i) {
143    model->ResetDateGroupModified(*i);
144  }
145
146  if (import_mode) {
147    model->EndImportMode();
148  }
149
150  if (show_bookmark_toolbar && !(options & BOOKMARK_BAR_DISABLED))
151    ShowBookmarkBar();
152}
153
154void ProfileWriter::AddFavicons(
155    const std::vector<history::ImportedFavIconUsage>& favicons) {
156  profile_->GetFaviconService(Profile::EXPLICIT_ACCESS)->
157      SetImportedFavicons(favicons);
158}
159
160typedef std::map<std::string, const TemplateURL*> HostPathMap;
161
162// Returns the key for the map built by BuildHostPathMap. If url_string is not
163// a valid URL, an empty string is returned, otherwise host+path is returned.
164static std::string HostPathKeyForURL(const GURL& url) {
165  return url.is_valid() ? url.host() + url.path() : std::string();
166}
167
168// Builds the key to use in HostPathMap for the specified TemplateURL. Returns
169// an empty string if a host+path can't be generated for the TemplateURL.
170// If an empty string is returned, the TemplateURL should not be added to
171// HostPathMap.
172//
173// If |try_url_if_invalid| is true, and |t_url| isn't valid, a string is built
174// from the raw TemplateURL string. Use a value of true for |try_url_if_invalid|
175// when checking imported URLs as the imported URL may not be valid yet may
176// match the host+path of one of the default URLs. This is used to catch the
177// case of IE using an invalid OSDD URL for Live Search, yet the host+path
178// matches our prepopulate data. IE's URL for Live Search is something like
179// 'http://...{Language}...'. As {Language} is not a valid OSDD parameter value
180// the TemplateURL is invalid.
181static std::string BuildHostPathKey(const TemplateURL* t_url,
182                                    bool try_url_if_invalid) {
183  if (t_url->url()) {
184    if (try_url_if_invalid && !t_url->url()->IsValid())
185      return HostPathKeyForURL(GURL(t_url->url()->url()));
186
187    if (t_url->url()->SupportsReplacement()) {
188      return HostPathKeyForURL(GURL(
189          t_url->url()->ReplaceSearchTerms(
190          *t_url, ASCIIToUTF16("random string"),
191          TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())));
192    }
193  }
194  return std::string();
195}
196
197// Builds a set that contains an entry of the host+path for each TemplateURL in
198// the TemplateURLModel that has a valid search url.
199static void BuildHostPathMap(const TemplateURLModel& model,
200                             HostPathMap* host_path_map) {
201  std::vector<const TemplateURL*> template_urls = model.GetTemplateURLs();
202  for (size_t i = 0; i < template_urls.size(); ++i) {
203    const std::string host_path = BuildHostPathKey(template_urls[i], false);
204    if (!host_path.empty()) {
205      const TemplateURL* existing_turl = (*host_path_map)[host_path];
206      if (!existing_turl ||
207          (template_urls[i]->show_in_default_list() &&
208           !existing_turl->show_in_default_list())) {
209        // If there are multiple TemplateURLs with the same host+path, favor
210        // those shown in the default list.  If there are multiple potential
211        // defaults, favor the first one, which should be the more commonly used
212        // one.
213        (*host_path_map)[host_path] = template_urls[i];
214      }
215    }  // else case, TemplateURL doesn't have a search url, doesn't support
216       // replacement, or doesn't have valid GURL. Ignore it.
217  }
218}
219
220void ProfileWriter::AddKeywords(const std::vector<TemplateURL*>& template_urls,
221                                int default_keyword_index,
222                                bool unique_on_host_and_path) {
223  TemplateURLModel* model = profile_->GetTemplateURLModel();
224  HostPathMap host_path_map;
225  if (unique_on_host_and_path)
226    BuildHostPathMap(*model, &host_path_map);
227
228  for (std::vector<TemplateURL*>::const_iterator i = template_urls.begin();
229       i != template_urls.end(); ++i) {
230    TemplateURL* t_url = *i;
231    bool default_keyword =
232        default_keyword_index >= 0 &&
233        (i - template_urls.begin() == default_keyword_index);
234
235    // TemplateURLModel requires keywords to be unique. If there is already a
236    // TemplateURL with this keyword, don't import it again.
237    const TemplateURL* turl_with_keyword =
238        model->GetTemplateURLForKeyword(t_url->keyword());
239    if (turl_with_keyword != NULL) {
240      if (default_keyword)
241        model->SetDefaultSearchProvider(turl_with_keyword);
242      delete t_url;
243      continue;
244    }
245
246    // For search engines if there is already a keyword with the same
247    // host+path, we don't import it. This is done to avoid both duplicate
248    // search providers (such as two Googles, or two Yahoos) as well as making
249    // sure the search engines we provide aren't replaced by those from the
250    // imported browser.
251    if (unique_on_host_and_path &&
252        host_path_map.find(
253            BuildHostPathKey(t_url, true)) != host_path_map.end()) {
254      if (default_keyword) {
255        const TemplateURL* turl_with_host_path =
256            host_path_map[BuildHostPathKey(t_url, true)];
257        if (turl_with_host_path)
258          model->SetDefaultSearchProvider(turl_with_host_path);
259        else
260          NOTREACHED();  // BuildHostPathMap should only insert non-null values.
261      }
262      delete t_url;
263      continue;
264    }
265    if (t_url->url() && t_url->url()->IsValid()) {
266      model->Add(t_url);
267      if (default_keyword && TemplateURL::SupportsReplacement(t_url))
268        model->SetDefaultSearchProvider(t_url);
269    } else {
270      // Don't add invalid TemplateURLs to the model.
271      delete t_url;
272    }
273  }
274}
275
276void ProfileWriter::ShowBookmarkBar() {
277  DCHECK(profile_);
278
279  PrefService* prefs = profile_->GetPrefs();
280  // Check whether the bookmark bar is shown in current pref.
281  if (!prefs->GetBoolean(prefs::kShowBookmarkBar)) {
282    // Set the pref and notify the notification service.
283    prefs->SetBoolean(prefs::kShowBookmarkBar, true);
284    prefs->ScheduleSavePersistentPrefs();
285    Source<Profile> source(profile_);
286    NotificationService::current()->Notify(
287        NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source,
288        NotificationService::NoDetails());
289  }
290}
291
292ProfileWriter::~ProfileWriter() {}
293
294std::wstring ProfileWriter::GenerateUniqueFolderName(
295    BookmarkModel* model,
296    const std::wstring& folder_name) {
297  // Build a set containing the folder names of the other folder.
298  std::set<std::wstring> other_folder_names;
299  const BookmarkNode* other = model->other_node();
300
301  for (int i = 0, child_count = other->GetChildCount(); i < child_count; ++i) {
302    const BookmarkNode* node = other->GetChild(i);
303    if (node->is_folder())
304      other_folder_names.insert(UTF16ToWideHack(node->GetTitle()));
305  }
306
307  if (other_folder_names.find(folder_name) == other_folder_names.end())
308    return folder_name;  // Name is unique, use it.
309
310  // Otherwise iterate until we find a unique name.
311  for (int i = 1; i < 100; ++i) {
312    std::wstring name = folder_name + StringPrintf(L" (%d)", i);
313    if (other_folder_names.find(name) == other_folder_names.end())
314      return name;
315  }
316
317  return folder_name;
318}
319
320bool ProfileWriter::DoesBookmarkExist(
321    BookmarkModel* model,
322    const BookmarkEntry& entry,
323    const std::wstring& first_folder_name,
324    bool import_to_bookmark_bar) {
325  std::vector<const BookmarkNode*> nodes_with_same_url;
326  model->GetNodesByURL(entry.url, &nodes_with_same_url);
327  if (nodes_with_same_url.empty())
328    return false;
329
330  for (size_t i = 0; i < nodes_with_same_url.size(); ++i) {
331    const BookmarkNode* node = nodes_with_same_url[i];
332    if (WideToUTF16Hack(entry.title) != node->GetTitle())
333      continue;
334
335    // Does the path match?
336    bool found_match = true;
337    const BookmarkNode* parent = node->GetParent();
338    for (std::vector<std::wstring>::const_reverse_iterator path_it =
339             entry.path.rbegin();
340         (path_it != entry.path.rend()) && found_match; ++path_it) {
341      const std::wstring& folder_name =
342          (!import_to_bookmark_bar && path_it + 1 == entry.path.rend()) ?
343          first_folder_name : *path_it;
344      if (NULL == parent || *path_it != folder_name)
345        found_match = false;
346      else
347        parent = parent->GetParent();
348    }
349
350    // We need a post test to differentiate checks such as
351    // /home/hello and /hello. The parent should either by the other folder
352    // node, or the bookmarks bar, depending upon import_to_bookmark_bar and
353    // entry.in_toolbar.
354    if (found_match &&
355        ((import_to_bookmark_bar && entry.in_toolbar && parent !=
356          model->GetBookmarkBarNode()) ||
357         ((!import_to_bookmark_bar || !entry.in_toolbar) &&
358           parent != model->other_node()))) {
359      found_match = false;
360    }
361
362    if (found_match)
363      return true;  // Found a match with the same url path and title.
364  }
365  return false;
366}
367