• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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