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/history/starred_url_database.h"
6
7 #include "app/sql/statement.h"
8 #include "base/file_util.h"
9 #include "base/json/json_writer.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_vector.h"
12 #include "base/stl_util-inl.h"
13 #include "base/string_util.h"
14 #include "base/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/bookmarks/bookmark_codec.h"
17 #include "chrome/browser/bookmarks/bookmark_model.h"
18 #include "chrome/browser/history/history.h"
19 #include "chrome/browser/history/query_parser.h"
20
21 // The following table is used to store star (aka bookmark) information. This
22 // class derives from URLDatabase, which has its own schema.
23 //
24 // starred
25 // id Unique identifier (primary key) for the entry.
26 // type Type of entry, if 0 this corresponds to a URL, 1 for
27 // a system folder, 2 for a user created folder, 3 for
28 // other.
29 // url_id ID of the url, only valid if type == 0
30 // group_id ID of the folder, only valid if type != 0. This id comes
31 // from the UI and is NOT the same as id.
32 // title User assigned title.
33 // date_added Creation date.
34 // visual_order Visual order within parent.
35 // parent_id Folder ID of the parent this entry is contained in, if 0
36 // entry is not in a folder.
37 // date_modified Time the folder was last modified. See comments in
38 // StarredEntry::date_folder_modified
39 // NOTE: group_id and parent_id come from the UI, id is assigned by the
40 // db.
41
42 namespace history {
43
44 namespace {
45
46 // Fields used by FillInStarredEntry.
47 #define STAR_FIELDS \
48 " starred.id, starred.type, starred.title, starred.date_added, " \
49 "starred.visual_order, starred.parent_id, urls.url, urls.id, " \
50 "starred.group_id, starred.date_modified "
51 const char kHistoryStarFields[] = STAR_FIELDS;
52
FillInStarredEntry(const sql::Statement & s,StarredEntry * entry)53 void FillInStarredEntry(const sql::Statement& s, StarredEntry* entry) {
54 DCHECK(entry);
55 entry->id = s.ColumnInt64(0);
56 switch (s.ColumnInt(1)) {
57 case 0:
58 entry->type = history::StarredEntry::URL;
59 entry->url = GURL(s.ColumnString(6));
60 break;
61 case 1:
62 entry->type = history::StarredEntry::BOOKMARK_BAR;
63 break;
64 case 2:
65 entry->type = history::StarredEntry::USER_FOLDER;
66 break;
67 case 3:
68 entry->type = history::StarredEntry::OTHER;
69 break;
70 default:
71 NOTREACHED();
72 break;
73 }
74 entry->title = s.ColumnString16(2);
75 entry->date_added = base::Time::FromInternalValue(s.ColumnInt64(3));
76 entry->visual_order = s.ColumnInt(4);
77 entry->parent_folder_id = s.ColumnInt64(5);
78 entry->url_id = s.ColumnInt64(7);
79 entry->folder_id = s.ColumnInt64(8);
80 entry->date_folder_modified = base::Time::FromInternalValue(s.ColumnInt64(9));
81 }
82
83 } // namespace
84
StarredURLDatabase()85 StarredURLDatabase::StarredURLDatabase() {
86 }
87
~StarredURLDatabase()88 StarredURLDatabase::~StarredURLDatabase() {
89 }
90
MigrateBookmarksToFile(const FilePath & path)91 bool StarredURLDatabase::MigrateBookmarksToFile(const FilePath& path) {
92 if (!GetDB().DoesTableExist("starred"))
93 return true;
94
95 if (EnsureStarredIntegrity() && !MigrateBookmarksToFileImpl(path)) {
96 NOTREACHED() << " Bookmarks migration failed";
97 return false;
98 }
99
100 if (!GetDB().Execute("DROP TABLE starred")) {
101 NOTREACHED() << "Unable to drop starred table";
102 return false;
103 }
104 return true;
105 }
106
GetAllStarredEntries(std::vector<StarredEntry> * entries)107 bool StarredURLDatabase::GetAllStarredEntries(
108 std::vector<StarredEntry>* entries) {
109 DCHECK(entries);
110 std::string sql = "SELECT ";
111 sql.append(kHistoryStarFields);
112 sql.append("FROM starred LEFT JOIN urls ON starred.url_id = urls.id ");
113 sql += "ORDER BY parent_id, visual_order";
114
115 sql::Statement s(GetDB().GetUniqueStatement(sql.c_str()));
116 if (!s) {
117 NOTREACHED() << "Statement prepare failed";
118 return false;
119 }
120
121 history::StarredEntry entry;
122 while (s.Step()) {
123 FillInStarredEntry(s, &entry);
124 // Reset the url for non-url types. This is needed as we're reusing the
125 // same entry for the loop.
126 if (entry.type != history::StarredEntry::URL)
127 entry.url = GURL();
128 entries->push_back(entry);
129 }
130 return true;
131 }
132
EnsureStarredIntegrity()133 bool StarredURLDatabase::EnsureStarredIntegrity() {
134 std::set<StarredNode*> roots;
135 std::set<StarID> folders_with_duplicate_ids;
136 std::set<StarredNode*> unparented_urls;
137 std::set<StarID> empty_url_ids;
138
139 if (!BuildStarNodes(&roots, &folders_with_duplicate_ids, &unparented_urls,
140 &empty_url_ids)) {
141 return false;
142 }
143
144 bool valid = EnsureStarredIntegrityImpl(&roots, folders_with_duplicate_ids,
145 &unparented_urls, empty_url_ids);
146
147 STLDeleteElements(&roots);
148 STLDeleteElements(&unparented_urls);
149 return valid;
150 }
151
UpdateStarredEntryRow(StarID star_id,const string16 & title,UIStarID parent_folder_id,int visual_order,base::Time date_modified)152 bool StarredURLDatabase::UpdateStarredEntryRow(StarID star_id,
153 const string16& title,
154 UIStarID parent_folder_id,
155 int visual_order,
156 base::Time date_modified) {
157 DCHECK(star_id && visual_order >= 0);
158 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
159 "UPDATE starred SET title=?, parent_id=?, visual_order=?, "
160 "date_modified=? WHERE id=?"));
161 if (!statement)
162 return 0;
163
164 statement.BindString16(0, title);
165 statement.BindInt64(1, parent_folder_id);
166 statement.BindInt(2, visual_order);
167 statement.BindInt64(3, date_modified.ToInternalValue());
168 statement.BindInt64(4, star_id);
169 return statement.Run();
170 }
171
AdjustStarredVisualOrder(UIStarID parent_folder_id,int start_visual_order,int delta)172 bool StarredURLDatabase::AdjustStarredVisualOrder(UIStarID parent_folder_id,
173 int start_visual_order,
174 int delta) {
175 DCHECK(parent_folder_id && start_visual_order >= 0);
176 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
177 "UPDATE starred SET visual_order=visual_order+? "
178 "WHERE parent_id=? AND visual_order >= ?"));
179 if (!statement)
180 return false;
181
182 statement.BindInt(0, delta);
183 statement.BindInt64(1, parent_folder_id);
184 statement.BindInt(2, start_visual_order);
185 return statement.Run();
186 }
187
CreateStarredEntryRow(URLID url_id,UIStarID folder_id,UIStarID parent_folder_id,const string16 & title,const base::Time & date_added,int visual_order,StarredEntry::Type type)188 StarID StarredURLDatabase::CreateStarredEntryRow(URLID url_id,
189 UIStarID folder_id,
190 UIStarID parent_folder_id,
191 const string16& title,
192 const base::Time& date_added,
193 int visual_order,
194 StarredEntry::Type type) {
195 DCHECK(visual_order >= 0 &&
196 (type != history::StarredEntry::URL || url_id));
197 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
198 "INSERT INTO starred "
199 "(type, url_id, group_id, title, date_added, visual_order, parent_id, "
200 "date_modified) VALUES (?,?,?,?,?,?,?,?)"));
201 if (!statement)
202 return 0;
203
204 switch (type) {
205 case history::StarredEntry::URL:
206 statement.BindInt(0, 0);
207 break;
208 case history::StarredEntry::BOOKMARK_BAR:
209 statement.BindInt(0, 1);
210 break;
211 case history::StarredEntry::USER_FOLDER:
212 statement.BindInt(0, 2);
213 break;
214 case history::StarredEntry::OTHER:
215 statement.BindInt(0, 3);
216 break;
217 default:
218 NOTREACHED();
219 }
220 statement.BindInt64(1, url_id);
221 statement.BindInt64(2, folder_id);
222 statement.BindString16(3, title);
223 statement.BindInt64(4, date_added.ToInternalValue());
224 statement.BindInt(5, visual_order);
225 statement.BindInt64(6, parent_folder_id);
226 statement.BindInt64(7, base::Time().ToInternalValue());
227 if (statement.Run())
228 return GetDB().GetLastInsertRowId();
229 return 0;
230 }
231
DeleteStarredEntryRow(StarID star_id)232 bool StarredURLDatabase::DeleteStarredEntryRow(StarID star_id) {
233 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
234 "DELETE FROM starred WHERE id=?"));
235 if (!statement)
236 return false;
237
238 statement.BindInt64(0, star_id);
239 return statement.Run();
240 }
241
GetStarredEntry(StarID star_id,StarredEntry * entry)242 bool StarredURLDatabase::GetStarredEntry(StarID star_id, StarredEntry* entry) {
243 DCHECK(entry && star_id);
244 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
245 "SELECT" STAR_FIELDS "FROM starred LEFT JOIN urls ON "
246 "starred.url_id = urls.id WHERE starred.id=?"));
247 if (!statement)
248 return false;
249
250 statement.BindInt64(0, star_id);
251
252 if (statement.Step()) {
253 FillInStarredEntry(statement, entry);
254 return true;
255 }
256 return false;
257 }
258
CreateStarredEntry(StarredEntry * entry)259 StarID StarredURLDatabase::CreateStarredEntry(StarredEntry* entry) {
260 entry->id = 0; // Ensure 0 for failure case.
261
262 // Adjust the visual order when we are inserting it somewhere.
263 if (entry->parent_folder_id)
264 AdjustStarredVisualOrder(entry->parent_folder_id, entry->visual_order, 1);
265
266 // Insert the new entry.
267 switch (entry->type) {
268 case StarredEntry::USER_FOLDER:
269 entry->id = CreateStarredEntryRow(0, entry->folder_id,
270 entry->parent_folder_id, entry->title, entry->date_added,
271 entry->visual_order, entry->type);
272 break;
273
274 case StarredEntry::URL: {
275 // Get the row for this URL.
276 URLRow url_row;
277 if (!GetRowForURL(entry->url, &url_row)) {
278 // Create a new URL row for this entry.
279 url_row = URLRow(entry->url);
280 url_row.set_title(entry->title);
281 url_row.set_hidden(false);
282 entry->url_id = this->AddURL(url_row);
283 } else {
284 entry->url_id = url_row.id(); // The caller doesn't have to set this.
285 }
286
287 // Create the star entry referring to the URL row.
288 entry->id = CreateStarredEntryRow(entry->url_id, entry->folder_id,
289 entry->parent_folder_id, entry->title, entry->date_added,
290 entry->visual_order, entry->type);
291
292 // Update the URL row to refer to this new starred entry.
293 UpdateURLRow(entry->url_id, url_row);
294 break;
295 }
296
297 default:
298 NOTREACHED();
299 break;
300 }
301 return entry->id;
302 }
303
GetMaxFolderID()304 UIStarID StarredURLDatabase::GetMaxFolderID() {
305 sql::Statement max_folder_id_statement(GetDB().GetUniqueStatement(
306 "SELECT MAX(group_id) FROM starred"));
307 if (!max_folder_id_statement) {
308 NOTREACHED() << GetDB().GetErrorMessage();
309 return 0;
310 }
311 if (!max_folder_id_statement.Step()) {
312 NOTREACHED() << GetDB().GetErrorMessage();
313 return 0;
314 }
315 return max_folder_id_statement.ColumnInt64(0);
316 }
317
BuildStarNodes(std::set<StarredURLDatabase::StarredNode * > * roots,std::set<StarID> * folders_with_duplicate_ids,std::set<StarredNode * > * unparented_urls,std::set<StarID> * empty_url_ids)318 bool StarredURLDatabase::BuildStarNodes(
319 std::set<StarredURLDatabase::StarredNode*>* roots,
320 std::set<StarID>* folders_with_duplicate_ids,
321 std::set<StarredNode*>* unparented_urls,
322 std::set<StarID>* empty_url_ids) {
323 std::vector<StarredEntry> star_entries;
324 if (!GetAllStarredEntries(&star_entries)) {
325 NOTREACHED() << "Unable to get bookmarks from database";
326 return false;
327 }
328
329 // Create the folder/bookmark-bar/other nodes.
330 std::map<UIStarID, StarredNode*> folder_id_to_node_map;
331 for (size_t i = 0; i < star_entries.size(); ++i) {
332 if (star_entries[i].type != StarredEntry::URL) {
333 if (folder_id_to_node_map.find(star_entries[i].folder_id) !=
334 folder_id_to_node_map.end()) {
335 // There's already a folder with this ID.
336 folders_with_duplicate_ids->insert(star_entries[i].id);
337 } else {
338 // Create the node and update the mapping.
339 StarredNode* node = new StarredNode(star_entries[i]);
340 folder_id_to_node_map[star_entries[i].folder_id] = node;
341 }
342 }
343 }
344
345 // Iterate again, creating nodes for URL bookmarks and parenting all
346 // bookmarks/folders. In addition populate the empty_url_ids with all entries
347 // of type URL that have an empty URL.
348 std::map<StarID, StarredNode*> id_to_node_map;
349 for (size_t i = 0; i < star_entries.size(); ++i) {
350 if (star_entries[i].type == StarredEntry::URL) {
351 if (star_entries[i].url.is_empty()) {
352 empty_url_ids->insert(star_entries[i].id);
353 } else if (!star_entries[i].parent_folder_id ||
354 folder_id_to_node_map.find(star_entries[i].parent_folder_id) ==
355 folder_id_to_node_map.end()) {
356 // This entry has no parent, or we couldn't find the parent.
357 StarredNode* node = new StarredNode(star_entries[i]);
358 unparented_urls->insert(node);
359 } else {
360 // Add the node to its parent.
361 StarredNode* parent =
362 folder_id_to_node_map[star_entries[i].parent_folder_id];
363 StarredNode* node = new StarredNode(star_entries[i]);
364 parent->Add(node, parent->child_count());
365 }
366 } else if (folders_with_duplicate_ids->find(star_entries[i].id) ==
367 folders_with_duplicate_ids->end()) {
368 // The entry is a folder (or bookmark bar/other node) that isn't
369 // marked as a duplicate.
370 if (!star_entries[i].parent_folder_id ||
371 folder_id_to_node_map.find(star_entries[i].parent_folder_id) ==
372 folder_id_to_node_map.end()) {
373 // Entry has no parent, or the parent wasn't found.
374 roots->insert(folder_id_to_node_map[star_entries[i].folder_id]);
375 } else {
376 // Parent the folder node.
377 StarredNode* parent =
378 folder_id_to_node_map[star_entries[i].parent_folder_id];
379 StarredNode* node = folder_id_to_node_map[star_entries[i].folder_id];
380 if (!node->HasAncestor(parent) && !parent->HasAncestor(node)) {
381 parent->Add(node, parent->child_count());
382 } else {
383 // The node has a cycle. Add it to the list of roots so the cycle is
384 // broken.
385 roots->insert(node);
386 }
387 }
388 }
389 }
390 return true;
391 }
392
GetNodeByType(const std::set<StarredURLDatabase::StarredNode * > & nodes,StarredEntry::Type type)393 StarredURLDatabase::StarredNode* StarredURLDatabase::GetNodeByType(
394 const std::set<StarredURLDatabase::StarredNode*>& nodes,
395 StarredEntry::Type type) {
396 for (std::set<StarredNode*>::const_iterator i = nodes.begin();
397 i != nodes.end(); ++i) {
398 if ((*i)->value.type == type)
399 return *i;
400 }
401 return NULL;
402 }
403
EnsureVisualOrder(StarredURLDatabase::StarredNode * node)404 bool StarredURLDatabase::EnsureVisualOrder(
405 StarredURLDatabase::StarredNode* node) {
406 for (int i = 0; i < node->child_count(); ++i) {
407 if (node->GetChild(i)->value.visual_order != i) {
408 StarredEntry& entry = node->GetChild(i)->value;
409 entry.visual_order = i;
410 LOG(WARNING) << "Bookmark visual order is wrong";
411 if (!UpdateStarredEntryRow(entry.id, entry.title, entry.parent_folder_id,
412 i, entry.date_folder_modified)) {
413 NOTREACHED() << "Unable to update visual order";
414 return false;
415 }
416 }
417 if (!EnsureVisualOrder(node->GetChild(i)))
418 return false;
419 }
420 return true;
421 }
422
EnsureStarredIntegrityImpl(std::set<StarredURLDatabase::StarredNode * > * roots,const std::set<StarID> & folders_with_duplicate_ids,std::set<StarredNode * > * unparented_urls,const std::set<StarID> & empty_url_ids)423 bool StarredURLDatabase::EnsureStarredIntegrityImpl(
424 std::set<StarredURLDatabase::StarredNode*>* roots,
425 const std::set<StarID>& folders_with_duplicate_ids,
426 std::set<StarredNode*>* unparented_urls,
427 const std::set<StarID>& empty_url_ids) {
428 // Make sure the bookmark bar entry exists.
429 StarredNode* bookmark_node =
430 GetNodeByType(*roots, StarredEntry::BOOKMARK_BAR);
431 if (!bookmark_node) {
432 LOG(WARNING) << "No bookmark bar folder in database";
433 // If there is no bookmark bar entry in the db things are really
434 // screwed. Return false, which won't trigger migration and we'll just
435 // drop the tables.
436 return false;
437 }
438
439 // Make sure the other node exists.
440 StarredNode* other_node = GetNodeByType(*roots, StarredEntry::OTHER);
441 if (!other_node) {
442 LOG(WARNING) << "No bookmark other folder in database";
443 StarredEntry entry;
444 entry.folder_id = GetMaxFolderID() + 1;
445 if (entry.folder_id == 1) {
446 NOTREACHED() << "Unable to get new id for other bookmarks folder";
447 return false;
448 }
449 entry.id = CreateStarredEntryRow(
450 0, entry.folder_id, 0, UTF8ToUTF16("other"), base::Time::Now(), 0,
451 history::StarredEntry::OTHER);
452 if (!entry.id) {
453 NOTREACHED() << "Unable to create other bookmarks folder";
454 return false;
455 }
456 entry.type = StarredEntry::OTHER;
457 StarredNode* other_node = new StarredNode(entry);
458 roots->insert(other_node);
459 }
460
461 // We could potentially make sure only one folder with type
462 // BOOKMARK_BAR/OTHER, but history backend enforces this.
463
464 // Nuke any entries with no url.
465 for (std::set<StarID>::const_iterator i = empty_url_ids.begin();
466 i != empty_url_ids.end(); ++i) {
467 LOG(WARNING) << "Bookmark exists with no URL";
468 if (!DeleteStarredEntryRow(*i)) {
469 NOTREACHED() << "Unable to delete bookmark";
470 return false;
471 }
472 }
473
474 // Make sure the visual order of the nodes is correct.
475 for (std::set<StarredNode*>::const_iterator i = roots->begin();
476 i != roots->end(); ++i) {
477 if (!EnsureVisualOrder(*i))
478 return false;
479 }
480
481 // Move any unparented bookmarks to the bookmark bar.
482 {
483 std::set<StarredNode*>::iterator i = unparented_urls->begin();
484 while (i != unparented_urls->end()) {
485 LOG(WARNING) << "Bookmark not in a bookmark folder found";
486 if (!Move(*i, bookmark_node))
487 return false;
488 unparented_urls->erase(i++);
489 }
490 }
491
492 // Nuke any folders with duplicate ids. A duplicate id means there are two
493 // folders in the starred table with the same folder_id. We only keep the
494 // first folder, all other folders are removed.
495 for (std::set<StarID>::const_iterator i = folders_with_duplicate_ids.begin();
496 i != folders_with_duplicate_ids.end(); ++i) {
497 LOG(WARNING) << "Duplicate folder id in bookmark database";
498 if (!DeleteStarredEntryRow(*i)) {
499 NOTREACHED() << "Unable to delete folder";
500 return false;
501 }
502 }
503
504 // Move unparented user folders back to the bookmark bar.
505 {
506 std::set<StarredNode*>::iterator i = roots->begin();
507 while (i != roots->end()) {
508 if ((*i)->value.type == StarredEntry::USER_FOLDER) {
509 LOG(WARNING) << "Bookmark folder not on bookmark bar found";
510 if (!Move(*i, bookmark_node))
511 return false;
512 roots->erase(i++);
513 } else {
514 ++i;
515 }
516 }
517 }
518
519 return true;
520 }
521
Move(StarredNode * source,StarredNode * new_parent)522 bool StarredURLDatabase::Move(StarredNode* source, StarredNode* new_parent) {
523 history::StarredEntry& entry = source->value;
524 entry.visual_order = new_parent->child_count();
525 entry.parent_folder_id = new_parent->value.folder_id;
526 if (!UpdateStarredEntryRow(entry.id, entry.title,
527 entry.parent_folder_id, entry.visual_order,
528 entry.date_folder_modified)) {
529 NOTREACHED() << "Unable to move folder";
530 return false;
531 }
532 new_parent->Add(source, new_parent->child_count());
533 return true;
534 }
535
MigrateBookmarksToFileImpl(const FilePath & path)536 bool StarredURLDatabase::MigrateBookmarksToFileImpl(const FilePath& path) {
537 std::vector<history::StarredEntry> entries;
538 if (!GetAllStarredEntries(&entries))
539 return false;
540
541 // Create the bookmark bar and other folder nodes.
542 history::StarredEntry entry;
543 entry.type = history::StarredEntry::BOOKMARK_BAR;
544 BookmarkNode bookmark_bar_node(0, GURL());
545 bookmark_bar_node.Reset(entry);
546 entry.type = history::StarredEntry::OTHER;
547 BookmarkNode other_node(0, GURL());
548 other_node.Reset(entry);
549
550 std::map<history::UIStarID, history::StarID> folder_id_to_id_map;
551 typedef std::map<history::StarID, BookmarkNode*> IDToNodeMap;
552 IDToNodeMap id_to_node_map;
553
554 history::UIStarID other_folder_folder_id = 0;
555 history::StarID other_folder_id = 0;
556
557 // Iterate through the entries building a mapping between folder_id and id.
558 for (std::vector<history::StarredEntry>::const_iterator i = entries.begin();
559 i != entries.end(); ++i) {
560 if (i->type != history::StarredEntry::URL) {
561 folder_id_to_id_map[i->folder_id] = i->id;
562 if (i->type == history::StarredEntry::OTHER) {
563 other_folder_id = i->id;
564 other_folder_folder_id = i->folder_id;
565 }
566 }
567 }
568
569 // Register the bookmark bar and other folder nodes in the maps.
570 id_to_node_map[HistoryService::kBookmarkBarID] = &bookmark_bar_node;
571 folder_id_to_id_map[HistoryService::kBookmarkBarID] =
572 HistoryService::kBookmarkBarID;
573 if (other_folder_folder_id) {
574 id_to_node_map[other_folder_id] = &other_node;
575 folder_id_to_id_map[other_folder_folder_id] = other_folder_id;
576 }
577
578 // Iterate through the entries again creating the nodes.
579 for (std::vector<history::StarredEntry>::iterator i = entries.begin();
580 i != entries.end(); ++i) {
581 if (!i->parent_folder_id) {
582 DCHECK(i->type == history::StarredEntry::BOOKMARK_BAR ||
583 i->type == history::StarredEntry::OTHER);
584 // Only entries with no parent should be the bookmark bar and other
585 // bookmarks folders.
586 continue;
587 }
588
589 BookmarkNode* node = id_to_node_map[i->id];
590 if (!node) {
591 // Creating a node results in creating the parent. As such, it is
592 // possible for the node representing a folder to have been created before
593 // encountering the details.
594
595 // The created nodes are owned by the root node.
596 node = new BookmarkNode(0, i->url);
597 id_to_node_map[i->id] = node;
598 }
599 node->Reset(*i);
600
601 DCHECK(folder_id_to_id_map.find(i->parent_folder_id) !=
602 folder_id_to_id_map.end());
603 history::StarID parent_id = folder_id_to_id_map[i->parent_folder_id];
604 BookmarkNode* parent = id_to_node_map[parent_id];
605 if (!parent) {
606 // Haven't encountered the parent yet, create it now.
607 parent = new BookmarkNode(0, GURL());
608 id_to_node_map[parent_id] = parent;
609 }
610
611 // Add the node to its parent. |entries| is ordered by parent then
612 // visual order so that we know we maintain visual order by always adding
613 // to the end.
614 parent->Add(node, parent->child_count());
615 }
616
617 // Save to file.
618 BookmarkCodec encoder;
619 scoped_ptr<Value> encoded_bookmarks(
620 encoder.Encode(&bookmark_bar_node, &other_node));
621 std::string content;
622 base::JSONWriter::Write(encoded_bookmarks.get(), true, &content);
623
624 return (file_util::WriteFile(path, content.c_str(),
625 static_cast<int>(content.length())) != -1);
626 }
627
628 } // namespace history
629