Home | History | Annotate | Download | only in history
      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 
     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 
     85 StarredURLDatabase::StarredURLDatabase() {
     86 }
     87 
     88 StarredURLDatabase::~StarredURLDatabase() {
     89 }
     90 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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