bookmark_utils.cc revision d57369da7c6519fef57db42085f7b42d4c8845c1
1// Copyright (c) 2012 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/bookmarks/bookmark_utils.h"
6
7#include <utility>
8
9#include "base/basictypes.h"
10#include "base/files/file_path.h"
11#include "base/i18n/case_conversion.h"
12#include "base/i18n/string_search.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/string16.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/time/time.h"
17#include "chrome/browser/bookmarks/bookmark_model.h"
18#include "chrome/browser/history/query_parser.h"
19#include "chrome/common/pref_names.h"
20#include "components/user_prefs/pref_registry_syncable.h"
21#include "content/public/browser/user_metrics.h"
22#include "net/base/net_util.h"
23#include "ui/base/models/tree_node_iterator.h"
24
25using base::Time;
26
27namespace {
28
29void CloneBookmarkNodeImpl(BookmarkModel* model,
30                           const BookmarkNodeData::Element& element,
31                           const BookmarkNode* parent,
32                           int index_to_add_at,
33                           bool reset_node_times) {
34  const BookmarkNode* cloned_node = NULL;
35  if (element.is_url) {
36    if (reset_node_times) {
37      cloned_node = model->AddURL(parent, index_to_add_at, element.title,
38                                  element.url);
39    } else {
40      DCHECK(!element.date_added.is_null());
41      cloned_node = model->AddURLWithCreationTime(parent, index_to_add_at,
42                                                  element.title, element.url,
43                                                  element.date_added);
44    }
45  } else {
46    cloned_node = model->AddFolder(parent, index_to_add_at, element.title);
47    if (!reset_node_times) {
48      DCHECK(!element.date_folder_modified.is_null());
49      model->SetDateFolderModified(cloned_node, element.date_folder_modified);
50    }
51    for (int i = 0; i < static_cast<int>(element.children.size()); ++i)
52      CloneBookmarkNodeImpl(model, element.children[i], cloned_node, i,
53                            reset_node_times);
54  }
55  model->SetNodeMetaInfoMap(cloned_node, element.meta_info_map);
56}
57
58// Comparison function that compares based on date modified of the two nodes.
59bool MoreRecentlyModified(const BookmarkNode* n1, const BookmarkNode* n2) {
60  return n1->date_folder_modified() > n2->date_folder_modified();
61}
62
63// Returns true if |text| contains each string in |words|. This is used when
64// searching for bookmarks.
65bool DoesBookmarkTextContainWords(const base::string16& text,
66                                  const std::vector<base::string16>& words) {
67  for (size_t i = 0; i < words.size(); ++i) {
68    if (!base::i18n::StringSearchIgnoringCaseAndAccents(
69            words[i], text, NULL, NULL)) {
70      return false;
71    }
72  }
73  return true;
74}
75
76// Returns true if |node|s title or url contains the strings in |words|.
77// |languages| argument is user's accept-language setting to decode IDN.
78bool DoesBookmarkContainWords(const BookmarkNode* node,
79                              const std::vector<base::string16>& words,
80                              const std::string& languages) {
81  return
82      DoesBookmarkTextContainWords(node->GetTitle(), words) ||
83      DoesBookmarkTextContainWords(UTF8ToUTF16(node->url().spec()), words) ||
84      DoesBookmarkTextContainWords(net::FormatUrl(
85          node->url(), languages, net::kFormatUrlOmitNothing,
86          net::UnescapeRule::NORMAL, NULL, NULL, NULL), words);
87}
88
89// This is used with a tree iterator to skip subtrees which are not visible.
90bool PruneInvisibleFolders(const BookmarkNode* node) {
91  return !node->IsVisible();
92}
93
94}  // namespace
95
96namespace bookmark_utils {
97
98void CloneBookmarkNode(BookmarkModel* model,
99                       const std::vector<BookmarkNodeData::Element>& elements,
100                       const BookmarkNode* parent,
101                       int index_to_add_at,
102                       bool reset_node_times) {
103  if (!parent->is_folder() || !model) {
104    NOTREACHED();
105    return;
106  }
107  for (size_t i = 0; i < elements.size(); ++i) {
108    CloneBookmarkNodeImpl(model, elements[i], parent, index_to_add_at + i,
109                          reset_node_times);
110  }
111}
112
113void CopyToClipboard(BookmarkModel* model,
114                     const std::vector<const BookmarkNode*>& nodes,
115                     bool remove_nodes) {
116  if (nodes.empty())
117    return;
118
119  BookmarkNodeData(nodes).WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
120
121  if (remove_nodes) {
122    for (size_t i = 0; i < nodes.size(); ++i) {
123      int index = nodes[i]->parent()->GetIndexOf(nodes[i]);
124      if (index > -1)
125        model->Remove(nodes[i]->parent(), index);
126    }
127  }
128}
129
130void PasteFromClipboard(BookmarkModel* model,
131                        const BookmarkNode* parent,
132                        int index) {
133  if (!parent)
134    return;
135
136  BookmarkNodeData bookmark_data;
137  if (!bookmark_data.ReadFromClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE))
138    return;
139
140  if (index == -1)
141    index = parent->child_count();
142  CloneBookmarkNode(model, bookmark_data.elements, parent, index, true);
143}
144
145bool CanPasteFromClipboard(const BookmarkNode* node) {
146  if (!node)
147    return false;
148  return BookmarkNodeData::ClipboardContainsBookmarks();
149}
150
151std::vector<const BookmarkNode*> GetMostRecentlyModifiedFolders(
152    BookmarkModel* model,
153    size_t max_count) {
154  std::vector<const BookmarkNode*> nodes;
155  ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node(),
156                                                    PruneInvisibleFolders);
157
158  while (iterator.has_next()) {
159    const BookmarkNode* parent = iterator.Next();
160    if (parent->is_folder() && parent->date_folder_modified() > base::Time()) {
161      if (max_count == 0) {
162        nodes.push_back(parent);
163      } else {
164        std::vector<const BookmarkNode*>::iterator i =
165            std::upper_bound(nodes.begin(), nodes.end(), parent,
166                             &MoreRecentlyModified);
167        if (nodes.size() < max_count || i != nodes.end()) {
168          nodes.insert(i, parent);
169          while (nodes.size() > max_count)
170            nodes.pop_back();
171        }
172      }
173    }  // else case, the root node, which we don't care about or imported nodes
174       // (which have a time of 0).
175  }
176
177  if (nodes.size() < max_count) {
178    // Add the permanent nodes if there is space. The permanent nodes are the
179    // only children of the root_node.
180    const BookmarkNode* root_node = model->root_node();
181
182    for (int i = 0; i < root_node->child_count(); ++i) {
183      const BookmarkNode* node = root_node->GetChild(i);
184      if (node->IsVisible() &&
185          std::find(nodes.begin(), nodes.end(), node) == nodes.end()) {
186        nodes.push_back(node);
187
188        if (nodes.size() == max_count)
189          break;
190      }
191    }
192  }
193  return nodes;
194}
195
196void GetMostRecentlyAddedEntries(BookmarkModel* model,
197                                 size_t count,
198                                 std::vector<const BookmarkNode*>* nodes) {
199  ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
200  while (iterator.has_next()) {
201    const BookmarkNode* node = iterator.Next();
202    if (node->is_url()) {
203      std::vector<const BookmarkNode*>::iterator insert_position =
204          std::upper_bound(nodes->begin(), nodes->end(), node,
205                           &MoreRecentlyAdded);
206      if (nodes->size() < count || insert_position != nodes->end()) {
207        nodes->insert(insert_position, node);
208        while (nodes->size() > count)
209          nodes->pop_back();
210      }
211    }
212  }
213}
214
215bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) {
216  return n1->date_added() > n2->date_added();
217}
218
219void GetBookmarksContainingText(BookmarkModel* model,
220                                const base::string16& text,
221                                size_t max_count,
222                                const std::string& languages,
223                                std::vector<const BookmarkNode*>* nodes) {
224  std::vector<base::string16> words;
225  QueryParser parser;
226  parser.ParseQueryWords(base::i18n::ToLower(text), &words);
227  if (words.empty())
228    return;
229
230  ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
231  while (iterator.has_next()) {
232    const BookmarkNode* node = iterator.Next();
233    if (DoesBookmarkContainWords(node, words, languages)) {
234      nodes->push_back(node);
235      if (nodes->size() == max_count)
236        return;
237    }
238  }
239}
240
241void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
242  registry->RegisterBooleanPref(
243      prefs::kShowBookmarkBar,
244      false,
245      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
246  registry->RegisterBooleanPref(
247      prefs::kEditBookmarksEnabled,
248      true,
249      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
250  registry->RegisterBooleanPref(
251      prefs::kShowAppsShortcutInBookmarkBar,
252      true,
253      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
254}
255
256const BookmarkNode* GetParentForNewNodes(
257    const BookmarkNode* parent,
258    const std::vector<const BookmarkNode*>& selection,
259    int* index) {
260  const BookmarkNode* real_parent = parent;
261
262  if (selection.size() == 1 && selection[0]->is_folder())
263    real_parent = selection[0];
264
265  if (index) {
266    if (selection.size() == 1 && selection[0]->is_url()) {
267      *index = real_parent->GetIndexOf(selection[0]) + 1;
268      if (*index == 0) {
269        // Node doesn't exist in parent, add to end.
270        NOTREACHED();
271        *index = real_parent->child_count();
272      }
273    } else {
274      *index = real_parent->child_count();
275    }
276  }
277
278  return real_parent;
279}
280
281void DeleteBookmarkFolders(BookmarkModel* model,
282                           const std::vector<int64>& ids) {
283  // Remove the folders that were removed. This has to be done after all the
284  // other changes have been committed.
285  for (std::vector<int64>::const_iterator iter = ids.begin();
286       iter != ids.end();
287       ++iter) {
288    const BookmarkNode* node = model->GetNodeByID(*iter);
289    if (!node)
290      continue;
291    const BookmarkNode* parent = node->parent();
292    model->Remove(parent, parent->GetIndexOf(node));
293  }
294}
295
296void AddIfNotBookmarked(BookmarkModel* model,
297                        const GURL& url,
298                        const base::string16& title) {
299  std::vector<const BookmarkNode*> bookmarks;
300  model->GetNodesByURL(url, &bookmarks);
301  if (!bookmarks.empty())
302    return;  // Nothing to do, a bookmark with that url already exists.
303
304  content::RecordAction(content::UserMetricsAction("BookmarkAdded"));
305  const BookmarkNode* parent = model->GetParentForNewNodes();
306  model->AddURL(parent, parent->child_count(), title, url);
307}
308
309void RemoveAllBookmarks(BookmarkModel* model, const GURL& url) {
310  std::vector<const BookmarkNode*> bookmarks;
311  model->GetNodesByURL(url, &bookmarks);
312
313  // Remove all the bookmarks.
314  for (size_t i = 0; i < bookmarks.size(); ++i) {
315    const BookmarkNode* node = bookmarks[i];
316    int index = node->parent()->GetIndexOf(node);
317    if (index > -1)
318      model->Remove(node->parent(), index);
319  }
320}
321
322}  // namespace bookmark_utils
323