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/bookmarks/bookmark_node_data.h"
6
7#include <string>
8
9#include "base/basictypes.h"
10#include "base/pickle.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
13#include "chrome/browser/bookmarks/bookmark_model.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/common/url_constants.h"
16#include "net/base/escape.h"
17#include "ui/base/clipboard/scoped_clipboard_writer.h"
18
19#if defined(OS_MACOSX)
20#include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h"
21#else
22#include "chrome/browser/browser_process.h"
23#endif
24
25const char* BookmarkNodeData::kClipboardFormatString =
26    "chromium/x-bookmark-entries";
27
28BookmarkNodeData::Element::Element() : is_url(false), id_(0) {
29}
30
31BookmarkNodeData::Element::Element(const BookmarkNode* node)
32    : is_url(node->is_url()),
33      url(node->GetURL()),
34      title(node->GetTitle()),
35      id_(node->id()) {
36  for (int i = 0; i < node->child_count(); ++i)
37    children.push_back(Element(node->GetChild(i)));
38}
39
40BookmarkNodeData::Element::~Element() {
41}
42
43void BookmarkNodeData::Element::WriteToPickle(Pickle* pickle) const {
44  pickle->WriteBool(is_url);
45  pickle->WriteString(url.spec());
46  pickle->WriteString16(title);
47  pickle->WriteInt64(id_);
48  if (!is_url) {
49    pickle->WriteSize(children.size());
50    for (std::vector<Element>::const_iterator i = children.begin();
51         i != children.end(); ++i) {
52      i->WriteToPickle(pickle);
53    }
54  }
55}
56
57bool BookmarkNodeData::Element::ReadFromPickle(Pickle* pickle,
58                                               void** iterator) {
59  std::string url_spec;
60  if (!pickle->ReadBool(iterator, &is_url) ||
61      !pickle->ReadString(iterator, &url_spec) ||
62      !pickle->ReadString16(iterator, &title) ||
63      !pickle->ReadInt64(iterator, &id_)) {
64    return false;
65  }
66  url = GURL(url_spec);
67  children.clear();
68  if (!is_url) {
69    size_t children_count;
70    if (!pickle->ReadSize(iterator, &children_count))
71      return false;
72    children.resize(children_count);
73    for (std::vector<Element>::iterator i = children.begin();
74         i != children.end(); ++i) {
75      if (!i->ReadFromPickle(pickle, iterator))
76        return false;
77    }
78  }
79  return true;
80}
81
82#if defined(TOOLKIT_VIEWS)
83// static
84ui::OSExchangeData::CustomFormat BookmarkNodeData::GetBookmarkCustomFormat() {
85  static ui::OSExchangeData::CustomFormat format;
86  static bool format_valid = false;
87
88  if (!format_valid) {
89    format_valid = true;
90    format = ui::OSExchangeData::RegisterCustomFormat(
91        BookmarkNodeData::kClipboardFormatString);
92  }
93  return format;
94}
95#endif
96
97BookmarkNodeData::BookmarkNodeData() {
98}
99
100BookmarkNodeData::BookmarkNodeData(const BookmarkNode* node) {
101  elements.push_back(Element(node));
102}
103
104BookmarkNodeData::BookmarkNodeData(
105    const std::vector<const BookmarkNode*>& nodes) {
106  ReadFromVector(nodes);
107}
108
109BookmarkNodeData::~BookmarkNodeData() {
110}
111
112bool BookmarkNodeData::ReadFromVector(
113    const std::vector<const BookmarkNode*>& nodes) {
114  Clear();
115
116  if (nodes.empty())
117    return false;
118
119  for (size_t i = 0; i < nodes.size(); ++i)
120    elements.push_back(Element(nodes[i]));
121
122  return true;
123}
124
125bool BookmarkNodeData::ReadFromTuple(const GURL& url, const string16& title) {
126  Clear();
127
128  if (!url.is_valid())
129    return false;
130
131  Element element;
132  element.title = title;
133  element.url = url;
134  element.is_url = true;
135
136  elements.push_back(element);
137
138  return true;
139}
140
141#if !defined(OS_MACOSX)
142void BookmarkNodeData::WriteToClipboard(Profile* profile) const {
143  ui::ScopedClipboardWriter scw(g_browser_process->clipboard());
144
145  // If there is only one element and it is a URL, write the URL to the
146  // clipboard.
147  if (elements.size() == 1 && elements[0].is_url) {
148    const string16& title = elements[0].title;
149    const std::string url = elements[0].url.spec();
150
151    scw.WriteBookmark(title, url);
152    scw.WriteHyperlink(EscapeForHTML(title), url);
153
154    // Also write the URL to the clipboard as text so that it can be pasted
155    // into text fields. We use WriteText instead of WriteURL because we don't
156    // want to clobber the X clipboard when the user copies out of the omnibox
157    // on Linux (on Windows and Mac, there is no difference between these
158    // functions).
159    scw.WriteText(UTF8ToUTF16(url));
160  }
161
162  Pickle pickle;
163  WriteToPickle(profile, &pickle);
164  scw.WritePickledData(pickle, kClipboardFormatString);
165}
166
167bool BookmarkNodeData::ReadFromClipboard() {
168  std::string data;
169  ui::Clipboard* clipboard = g_browser_process->clipboard();
170  clipboard->ReadData(kClipboardFormatString, &data);
171
172  if (!data.empty()) {
173    Pickle pickle(data.data(), data.size());
174    if (ReadFromPickle(&pickle))
175      return true;
176  }
177
178  string16 title;
179  std::string url;
180  clipboard->ReadBookmark(&title, &url);
181  if (!url.empty()) {
182    Element element;
183    element.is_url = true;
184    element.url = GURL(url);
185    element.title = title;
186
187    elements.clear();
188    elements.push_back(element);
189    return true;
190  }
191
192  return false;
193}
194
195bool BookmarkNodeData::ClipboardContainsBookmarks() {
196  return g_browser_process->clipboard()->IsFormatAvailableByString(
197      BookmarkNodeData::kClipboardFormatString, ui::Clipboard::BUFFER_STANDARD);
198}
199#else
200void BookmarkNodeData::WriteToClipboard(Profile* profile) const {
201  bookmark_pasteboard_helper_mac::WriteToClipboard(elements, profile_path_);
202}
203
204bool BookmarkNodeData::ReadFromClipboard() {
205  return bookmark_pasteboard_helper_mac::ReadFromClipboard(elements,
206                                                           &profile_path_);
207}
208
209bool BookmarkNodeData::ReadFromDragClipboard() {
210  return bookmark_pasteboard_helper_mac::ReadFromDragClipboard(elements,
211                                                               &profile_path_);
212}
213
214bool BookmarkNodeData::ClipboardContainsBookmarks() {
215  return bookmark_pasteboard_helper_mac::ClipboardContainsBookmarks();
216}
217#endif  // !defined(OS_MACOSX)
218
219#if defined(TOOLKIT_VIEWS)
220void BookmarkNodeData::Write(Profile* profile, ui::OSExchangeData* data) const {
221  DCHECK(data);
222
223  // If there is only one element and it is a URL, write the URL to the
224  // clipboard.
225  if (elements.size() == 1 && elements[0].is_url) {
226    if (elements[0].url.SchemeIs(chrome::kJavaScriptScheme)) {
227      data->SetString(UTF8ToUTF16(elements[0].url.spec()));
228    } else {
229      data->SetURL(elements[0].url, elements[0].title);
230    }
231  }
232
233  Pickle data_pickle;
234  WriteToPickle(profile, &data_pickle);
235
236  data->SetPickledData(GetBookmarkCustomFormat(), data_pickle);
237}
238
239bool BookmarkNodeData::Read(const ui::OSExchangeData& data) {
240  elements.clear();
241
242  profile_path_.clear();
243
244  if (data.HasCustomFormat(GetBookmarkCustomFormat())) {
245    Pickle drag_data_pickle;
246    if (data.GetPickledData(GetBookmarkCustomFormat(), &drag_data_pickle)) {
247      if (!ReadFromPickle(&drag_data_pickle))
248        return false;
249    }
250  } else {
251    // See if there is a URL on the clipboard.
252    Element element;
253    GURL url;
254    string16 title;
255    if (data.GetURLAndTitle(&url, &title))
256      ReadFromTuple(url, title);
257  }
258
259  return is_valid();
260}
261#endif
262
263void BookmarkNodeData::WriteToPickle(Profile* profile, Pickle* pickle) const {
264  FilePath path = profile ? profile->GetPath() : FilePath();
265  FilePath::WriteStringTypeToPickle(pickle, path.value());
266  pickle->WriteSize(elements.size());
267
268  for (size_t i = 0; i < elements.size(); ++i)
269    elements[i].WriteToPickle(pickle);
270}
271
272bool BookmarkNodeData::ReadFromPickle(Pickle* pickle) {
273  void* data_iterator = NULL;
274  size_t element_count;
275  if (FilePath::ReadStringTypeFromPickle(pickle, &data_iterator,
276                                         &profile_path_) &&
277      pickle->ReadSize(&data_iterator, &element_count)) {
278    std::vector<Element> tmp_elements;
279    tmp_elements.resize(element_count);
280    for (size_t i = 0; i < element_count; ++i) {
281      if (!tmp_elements[i].ReadFromPickle(pickle, &data_iterator)) {
282        return false;
283      }
284    }
285    elements.swap(tmp_elements);
286  }
287
288  return true;
289}
290
291std::vector<const BookmarkNode*> BookmarkNodeData::GetNodes(
292    Profile* profile) const {
293  std::vector<const BookmarkNode*> nodes;
294
295  if (!IsFromProfile(profile))
296    return nodes;
297
298  for (size_t i = 0; i < elements.size(); ++i) {
299    const BookmarkNode* node =
300        profile->GetBookmarkModel()->GetNodeByID(elements[i].id_);
301    if (!node) {
302      nodes.clear();
303      return nodes;
304    }
305    nodes.push_back(node);
306  }
307  return nodes;
308}
309
310const BookmarkNode* BookmarkNodeData::GetFirstNode(Profile* profile) const {
311  std::vector<const BookmarkNode*> nodes = GetNodes(profile);
312  return nodes.size() == 1 ? nodes[0] : NULL;
313}
314
315void BookmarkNodeData::Clear() {
316  profile_path_.clear();
317  elements.clear();
318}
319
320void BookmarkNodeData::SetOriginatingProfile(Profile* profile) {
321  DCHECK(profile_path_.empty());
322
323  if (profile)
324    profile_path_ = profile->GetPath().value();
325}
326
327bool BookmarkNodeData::IsFromProfile(Profile* profile) const {
328  // An empty path means the data is not associated with any profile.
329  return !profile_path_.empty() && profile_path_ == profile->GetPath().value();
330}
331