Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 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/utility/importer/edge_importer_win.h" | |
| 6 | |
| 7 #define JET_UNICODE | |
| 8 #include <esent.h> | |
| 9 #undef JET_UNICODE | |
| 10 #include <Shlobj.h> | |
| 11 | |
| 12 #include <algorithm> | |
| 13 #include <map> | |
| 14 #include <string> | |
| 15 #include <tuple> | |
| 16 #include <vector> | |
| 17 | |
| 18 #include "base/files/file_enumerator.h" | |
| 19 #include "base/files/file_path.h" | |
| 20 #include "base/files/file_util.h" | |
| 21 #include "base/memory/ref_counted.h" | |
| 22 #include "base/memory/scoped_ptr.h" | |
| 23 #include "base/strings/string16.h" | |
| 24 #include "base/strings/string_util.h" | |
| 25 #include "base/time/time.h" | |
| 26 #include "base/win/windows_version.h" | |
| 27 #include "chrome/common/importer/edge_importer_utils_win.h" | |
| 28 #include "chrome/common/importer/imported_bookmark_entry.h" | |
| 29 #include "chrome/common/importer/importer_bridge.h" | |
| 30 #include "chrome/grit/generated_resources.h" | |
| 31 #include "chrome/utility/importer/favicon_reencode.h" | |
| 32 #include "ui/base/l10n/l10n_util.h" | |
| 33 #include "url/gurl.h" | |
| 34 | |
| 35 namespace { | |
| 36 | |
| 37 // Toolbar favorites are placed under this special folder name. | |
| 38 const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_"; | |
| 39 const base::char16 kSpartanDatabaseFile[] = L"spartan.edb"; | |
| 40 // This is an arbitary size chosen for the database error message buffer. | |
| 41 const size_t kErrorMessageSize = 1024; | |
| 42 | |
| 43 class EdgeErrorObject { | |
|
ananta
2015/12/01 19:36:35
Please move the Edge database reading classes to t
forshaw
2015/12/02 18:21:54
Acknowledged.
| |
| 44 public: | |
| 45 EdgeErrorObject() : last_error_(JET_errSuccess) {} | |
| 46 | |
| 47 base::string16 GetErrorMessage() const { | |
| 48 WCHAR error_message[kErrorMessageSize] = {}; | |
| 49 JET_API_PTR err = last_error_; | |
| 50 JET_ERR result = JetGetSystemParameter( | |
| 51 JET_instanceNil, JET_sesidNil, JET_paramErrorToString, &err, | |
| 52 error_message, sizeof(error_message)); | |
| 53 if (result != JET_errSuccess) | |
| 54 return L""; | |
| 55 | |
| 56 return error_message; | |
| 57 } | |
| 58 | |
| 59 protected: | |
| 60 JET_ERR last_error() const { return last_error_; } | |
| 61 | |
| 62 // This function returns true if the passed error parameter is equal | |
| 63 // to JET_errSuccess | |
| 64 bool SetLastError(JET_ERR error) { | |
| 65 last_error_ = error; | |
| 66 return error == JET_errSuccess; | |
| 67 } | |
| 68 | |
| 69 private: | |
| 70 | |
| 71 JET_ERR last_error_; | |
| 72 | |
| 73 DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject); | |
| 74 }; | |
| 75 | |
| 76 class EdgeDatabaseTableEnumerator : public EdgeErrorObject { | |
| 77 public: | |
| 78 EdgeDatabaseTableEnumerator(const base::string16& table_name, | |
| 79 JET_SESID session_id, | |
| 80 JET_TABLEID table_id) | |
| 81 : table_id_(table_id), table_name_(table_name), session_id_(session_id) {} | |
| 82 | |
| 83 ~EdgeDatabaseTableEnumerator() { | |
| 84 if (table_id_ != JET_tableidNil) | |
| 85 JetCloseTable(session_id_, table_id_); | |
| 86 } | |
| 87 | |
| 88 const base::string16& table_name() { return table_name_; } | |
| 89 | |
| 90 bool Reset() { | |
| 91 return SetLastError(JetMove(session_id_, table_id_, JET_MoveFirst, 0)); | |
| 92 } | |
| 93 | |
| 94 bool Next() { | |
| 95 return SetLastError(JetMove(session_id_, table_id_, JET_MoveNext, 0)); | |
| 96 } | |
| 97 | |
| 98 template <typename T> | |
| 99 bool RetrieveColumn(const base::string16& column_name, T* value) { | |
| 100 const JET_COLUMNBASE& column_base = GetColumnByName(column_name); | |
| 101 if (column_base.cbMax == 0) | |
| 102 return false; | |
| 103 std::vector<uint8_t> column_data(column_base.cbMax); | |
| 104 unsigned long actual_size = 0; | |
| 105 JET_ERR err = JetRetrieveColumn( | |
| 106 session_id_, table_id_, column_base.columnid, &column_data[0], | |
| 107 column_data.size(), &actual_size, 0, nullptr); | |
| 108 SetLastError(err); | |
| 109 if (err != JET_errSuccess && err != JET_wrnColumnNull) { | |
| 110 return false; | |
| 111 } | |
| 112 | |
| 113 if (err == JET_errSuccess) { | |
| 114 column_data.resize(actual_size); | |
| 115 if (!ValidateAndConvertValue(column_base.coltyp, column_data, value)) | |
| 116 return false; | |
| 117 } else { | |
| 118 *value = T(); | |
| 119 } | |
| 120 | |
| 121 return true; | |
| 122 } | |
| 123 | |
| 124 private: | |
| 125 template <typename T> | |
| 126 bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type, | |
| 127 const JET_COLTYP column_type, | |
| 128 const std::vector<uint8_t>& column_data, | |
| 129 T* value) { | |
| 130 if ((column_type == match_column_type) && | |
| 131 (column_data.size() >= sizeof(T))) { | |
| 132 memcpy(value, &column_data[0], sizeof(T)); | |
| 133 return true; | |
| 134 } | |
| 135 SetLastError(JET_errInvalidColumnType); | |
| 136 return false; | |
| 137 } | |
| 138 | |
| 139 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 140 const std::vector<uint8_t>& column_data, | |
| 141 bool* value) { | |
| 142 if ((column_type == JET_coltypBit) && (column_data.size() >= 1)) { | |
| 143 *value = (column_data[0] & 1) == 1; | |
| 144 return true; | |
| 145 } | |
| 146 SetLastError(JET_errInvalidColumnType); | |
| 147 return false; | |
| 148 } | |
| 149 | |
| 150 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 151 const std::vector<uint8_t>& column_data, | |
| 152 base::string16* value) { | |
| 153 if ((column_type == JET_coltypLongText) && | |
| 154 ((column_data.size() % sizeof(base::char16)) == 0)) { | |
| 155 base::string16& value_ref = *value; | |
| 156 size_t char_length = column_data.size() / sizeof(base::char16); | |
| 157 value_ref.resize(char_length); | |
| 158 memcpy(&value_ref[0], &column_data[0], column_data.size()); | |
| 159 // Remove any trailing NUL characters. | |
| 160 while (char_length > 0) { | |
| 161 if (value_ref[char_length - 1]) | |
| 162 break; | |
| 163 char_length--; | |
| 164 } | |
| 165 value_ref.resize(char_length); | |
| 166 return true; | |
| 167 } | |
| 168 SetLastError(JET_errInvalidColumnType); | |
| 169 return false; | |
| 170 } | |
| 171 | |
| 172 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 173 const std::vector<uint8_t>& column_data, | |
| 174 GUID* value) { | |
| 175 return ValidateAndConvertValueGeneric(JET_coltypGUID, column_type, | |
| 176 column_data, value); | |
| 177 } | |
| 178 | |
| 179 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 180 const std::vector<uint8_t>& column_data, | |
| 181 int32_t* value) { | |
| 182 return ValidateAndConvertValueGeneric(JET_coltypLong, column_type, | |
| 183 column_data, value); | |
| 184 } | |
| 185 | |
| 186 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 187 const std::vector<uint8_t>& column_data, | |
| 188 int64_t* value) { | |
| 189 return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type, | |
| 190 column_data, value); | |
| 191 } | |
| 192 | |
| 193 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 194 const std::vector<uint8_t>& column_data, | |
| 195 FILETIME* value) { | |
| 196 return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type, | |
| 197 column_data, value); | |
| 198 } | |
| 199 | |
| 200 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 201 const std::vector<uint8_t>& column_data, | |
| 202 uint32_t* value) { | |
| 203 return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type, | |
| 204 column_data, value); | |
| 205 } | |
| 206 | |
| 207 const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name) { | |
| 208 auto found_col = columns_by_name_.find(column_name); | |
| 209 if (found_col == columns_by_name_.end()) { | |
| 210 JET_COLUMNBASE column_base = {}; | |
| 211 column_base.cbStruct = sizeof(JET_COLUMNBASE); | |
| 212 if (!SetLastError(JetGetTableColumnInfo( | |
| 213 session_id_, table_id_, column_name.c_str(), &column_base, | |
| 214 sizeof(column_base), JET_ColInfoBase))) { | |
| 215 // 0 indicates an invalid column. | |
| 216 column_base.cbMax = 0; | |
| 217 } | |
| 218 columns_by_name_[column_name] = column_base; | |
| 219 found_col = columns_by_name_.find(column_name); | |
| 220 } | |
| 221 return found_col->second; | |
| 222 } | |
| 223 | |
| 224 std::map<const base::string16, JET_COLUMNBASE> columns_by_name_; | |
| 225 JET_TABLEID table_id_; | |
| 226 base::string16 table_name_; | |
| 227 JET_SESID session_id_; | |
| 228 | |
| 229 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator); | |
| 230 }; | |
| 231 | |
| 232 class EdgeDatabaseSession : public EdgeErrorObject { | |
| 233 public: | |
| 234 EdgeDatabaseSession() | |
| 235 : db_id_(JET_dbidNil), | |
| 236 instance_id_(JET_instanceNil), | |
| 237 session_id_(JET_sesidNil) {} | |
| 238 | |
| 239 ~EdgeDatabaseSession() { | |
| 240 // We don't need to collect other ID handles, terminating instance | |
| 241 // is enough to shut the entire session down. | |
| 242 if (instance_id_ != JET_instanceNil) | |
| 243 JetTerm(instance_id_); | |
| 244 } | |
| 245 | |
| 246 bool OpenDatabase(const base::string16& database_file) { | |
| 247 if (IsOpen()) { | |
| 248 SetLastError(JET_errOneDatabasePerSession); | |
| 249 return false; | |
| 250 } | |
| 251 if (!SetLastError(JetSetSystemParameter(nullptr, JET_sesidNil, | |
| 252 JET_paramDatabasePageSize, | |
| 253 kEdgeDatabasePageSize, nullptr))) | |
| 254 return false; | |
| 255 if (!SetLastError(JetCreateInstance(&instance_id_, L"EdgeDataImporter"))) | |
| 256 return false; | |
| 257 if (!SetLastError(JetSetSystemParameter(&instance_id_, JET_sesidNil, | |
| 258 JET_paramRecovery, 0, L"Off"))) | |
| 259 return false; | |
| 260 if (!SetLastError(JetInit(&instance_id_))) | |
| 261 return false; | |
| 262 if (!SetLastError( | |
| 263 JetBeginSession(instance_id_, &session_id_, nullptr, nullptr))) | |
| 264 return false; | |
| 265 if (!SetLastError(JetAttachDatabase2(session_id_, database_file.c_str(), 0, | |
| 266 JET_bitDbReadOnly))) | |
| 267 return false; | |
| 268 if (!SetLastError(JetOpenDatabase(session_id_, database_file.c_str(), | |
| 269 nullptr, &db_id_, JET_bitDbReadOnly))) | |
| 270 return false; | |
| 271 return true; | |
| 272 } | |
| 273 | |
| 274 scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator( | |
| 275 const base::string16& table_name) { | |
| 276 JET_TABLEID table_id; | |
| 277 | |
| 278 if (!IsOpen()) { | |
| 279 SetLastError(JET_errDatabaseNotFound); | |
| 280 return nullptr; | |
| 281 } | |
| 282 | |
| 283 if (!SetLastError(JetOpenTable(session_id_, db_id_, table_name.c_str(), | |
| 284 nullptr, 0, JET_bitTableReadOnly, | |
| 285 &table_id))) | |
| 286 return nullptr; | |
| 287 | |
| 288 return make_scoped_ptr( | |
| 289 new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id)); | |
| 290 } | |
| 291 | |
| 292 private: | |
| 293 // This is the page size of the Edge data. It's unlikely to change. | |
| 294 static const JET_API_PTR kEdgeDatabasePageSize = 8192; | |
| 295 | |
| 296 bool IsOpen() { return instance_id_ != JET_instanceNil; } | |
| 297 | |
| 298 JET_DBID db_id_; | |
| 299 JET_INSTANCE instance_id_; | |
| 300 JET_SESID session_id_; | |
| 301 | |
| 302 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession); | |
| 303 }; | |
| 304 | |
| 305 struct EdgeFavoriteEntry { | |
| 306 EdgeFavoriteEntry() | |
| 307 : is_folder(false), | |
| 308 order_number(0), | |
| 309 item_id(GUID_NULL), | |
| 310 parent_id(GUID_NULL) {} | |
| 311 | |
| 312 base::string16 title; | |
| 313 GURL url; | |
| 314 base::FilePath favicon_file; | |
| 315 bool is_folder; | |
| 316 int64_t order_number; | |
| 317 base::Time date_updated; | |
| 318 GUID item_id; | |
| 319 GUID parent_id; | |
| 320 | |
| 321 std::vector<const EdgeFavoriteEntry*> children; | |
| 322 | |
| 323 ImportedBookmarkEntry ToBookmarkEntry( | |
| 324 bool in_toolbar, | |
| 325 const std::vector<base::string16>& path) const { | |
| 326 ImportedBookmarkEntry entry; | |
| 327 entry.in_toolbar = in_toolbar; | |
| 328 entry.is_folder = is_folder; | |
| 329 entry.url = url; | |
| 330 entry.path = path; | |
| 331 entry.title = title; | |
| 332 entry.creation_time = date_updated; | |
| 333 return entry; | |
| 334 } | |
| 335 }; | |
| 336 | |
| 337 struct EdgeFavoriteEntryComparator { | |
| 338 bool operator()(const EdgeFavoriteEntry* lhs, | |
| 339 const EdgeFavoriteEntry* rhs) const { | |
| 340 return std::tie(lhs->order_number, lhs->title) < | |
| 341 std::tie(rhs->order_number, rhs->title); | |
| 342 } | |
| 343 }; | |
| 344 | |
| 345 // The name of the database file is spartan.edb, however it isn't clear how | |
| 346 // the intermediate path between the DataStore and the database is generated. | |
| 347 // Therefore we just do a simple recursive search until we find a matching name. | |
| 348 base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) { | |
| 349 base::FilePath data_path = | |
| 350 profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path; | |
| 351 if (data_path.empty()) | |
| 352 return base::FilePath(); | |
| 353 | |
| 354 base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true, | |
| 355 base::FileEnumerator::FILES); | |
| 356 base::FilePath path = enumerator.Next(); | |
| 357 while (!path.empty()) { | |
| 358 if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(), | |
| 359 kSpartanDatabaseFile)) | |
| 360 return path; | |
| 361 path = enumerator.Next(); | |
| 362 } | |
| 363 return base::FilePath(); | |
| 364 } | |
| 365 | |
| 366 struct GuidComparator { | |
| 367 bool operator()(const GUID& a, const GUID& b) const { | |
| 368 return memcmp(&a, &b, sizeof(a)) < 0; | |
| 369 } | |
| 370 }; | |
| 371 | |
| 372 bool ReadFaviconData(const base::FilePath& file, | |
| 373 std::vector<unsigned char>* data) { | |
| 374 std::string image_data; | |
| 375 if (!base::ReadFileToString(file, &image_data)) | |
| 376 return false; | |
| 377 | |
| 378 const unsigned char* ptr = | |
| 379 reinterpret_cast<const unsigned char*>(image_data.c_str()); | |
| 380 return importer::ReencodeFavicon(ptr, image_data.size(), data); | |
| 381 } | |
| 382 | |
| 383 void BuildBookmarkEntries(const EdgeFavoriteEntry& current_entry, | |
| 384 bool is_toolbar, | |
| 385 std::vector<ImportedBookmarkEntry>* bookmarks, | |
| 386 favicon_base::FaviconUsageDataList* favicons, | |
| 387 std::vector<base::string16>* path) { | |
| 388 for (const EdgeFavoriteEntry* entry : current_entry.children) { | |
| 389 if (entry->is_folder) { | |
| 390 // If the favorites bar then load all children as toolbar items. | |
| 391 if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) { | |
| 392 // Replace name with Links similar to IE. | |
| 393 path->push_back(L"Links"); | |
| 394 BuildBookmarkEntries(*entry, true, bookmarks, favicons, path); | |
| 395 path->pop_back(); | |
| 396 } else { | |
| 397 path->push_back(entry->title); | |
| 398 BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, path); | |
| 399 path->pop_back(); | |
| 400 } | |
| 401 } else { | |
| 402 bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path)); | |
| 403 favicon_base::FaviconUsageData favicon; | |
| 404 if (entry->url.is_valid() && !entry->favicon_file.empty() && | |
| 405 ReadFaviconData(entry->favicon_file, &favicon.png_data)) { | |
| 406 // As the database doesn't provide us a favicon URL we'll fake one. | |
| 407 GURL::Replacements path_replace; | |
| 408 path_replace.SetPathStr("/favicon.ico"); | |
| 409 favicon.favicon_url = | |
| 410 entry->url.GetWithEmptyPath().ReplaceComponents(path_replace); | |
| 411 favicon.urls.insert(entry->url); | |
| 412 favicons->push_back(favicon); | |
| 413 } | |
| 414 } | |
| 415 } | |
| 416 } | |
| 417 | |
| 418 } // namespace | |
| 419 | |
| 420 EdgeImporter::EdgeImporter() {} | |
| 421 | |
| 422 void EdgeImporter::StartImport(const importer::SourceProfile& source_profile, | |
| 423 uint16 items, | |
| 424 ImporterBridge* bridge) { | |
| 425 bridge_ = bridge; | |
| 426 bridge_->NotifyStarted(); | |
| 427 source_path_ = source_profile.source_path; | |
| 428 | |
| 429 if ((items & importer::FAVORITES) && !cancelled()) { | |
| 430 bridge_->NotifyItemStarted(importer::FAVORITES); | |
| 431 ImportFavorites(); | |
| 432 bridge_->NotifyItemEnded(importer::FAVORITES); | |
| 433 } | |
| 434 bridge_->NotifyEnded(); | |
| 435 } | |
| 436 | |
| 437 EdgeImporter::~EdgeImporter() {} | |
| 438 | |
| 439 void EdgeImporter::ImportFavorites() { | |
| 440 std::vector<ImportedBookmarkEntry> bookmarks; | |
| 441 favicon_base::FaviconUsageDataList favicons; | |
| 442 ParseFavoritesDatabase(&bookmarks, &favicons); | |
| 443 | |
| 444 if (!bookmarks.empty() && !cancelled()) { | |
| 445 const base::string16& first_folder_name = | |
| 446 l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE); | |
| 447 bridge_->AddBookmarks(bookmarks, first_folder_name); | |
| 448 } | |
| 449 if (!favicons.empty() && !cancelled()) | |
| 450 bridge_->SetFavicons(favicons); | |
| 451 } | |
| 452 | |
| 453 // From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET | |
| 454 // database within the Edge local storage. The import uses the ESE library to | |
| 455 // open and read the data file. The data is stored in a Favorites table with | |
| 456 // the following schema. | |
| 457 // Column Name Column Type | |
| 458 // ------------------------------------------ | |
| 459 // DateUpdated LongLong - FILETIME | |
| 460 // FaviconFile LongText - Relative path | |
| 461 // HashedUrl ULong | |
| 462 // IsDeleted Bit | |
| 463 // IsFolder Bit | |
| 464 // ItemId Guid | |
| 465 // OrderNumber LongLong | |
| 466 // ParentId Guid | |
| 467 // RoamDisabled Bit | |
| 468 // RowId Long | |
| 469 // Title LongText | |
| 470 // URL LongText | |
| 471 void EdgeImporter::ParseFavoritesDatabase( | |
| 472 std::vector<ImportedBookmarkEntry>* bookmarks, | |
| 473 favicon_base::FaviconUsageDataList* favicons) { | |
| 474 base::FilePath database_path = FindSpartanDatabase(source_path_); | |
| 475 if (database_path.empty()) | |
| 476 return; | |
| 477 | |
| 478 EdgeDatabaseSession database; | |
| 479 if (!database.OpenDatabase(database_path.value())) { | |
| 480 DVLOG(1) << "Error opening database " << database.GetErrorMessage(); | |
| 481 return; | |
| 482 } | |
| 483 | |
| 484 scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = | |
| 485 database.OpenTableEnumerator(L"Favorites"); | |
| 486 if (!enumerator) { | |
| 487 DVLOG(1) << "Error opening database table " | |
| 488 << database.GetErrorMessage(); | |
| 489 return; | |
| 490 } | |
| 491 | |
| 492 if (!enumerator->Reset()) | |
| 493 return; | |
| 494 | |
| 495 std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; | |
| 496 base::FilePath favicon_base = | |
| 497 source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_; | |
| 498 favicon_base = favicon_base.Append(L"DataStore"); | |
| 499 | |
| 500 do { | |
| 501 EdgeFavoriteEntry entry; | |
| 502 bool is_deleted = false; | |
| 503 if (!enumerator->RetrieveColumn(L"IsDeleted", &is_deleted)) | |
| 504 continue; | |
| 505 if (is_deleted) | |
| 506 continue; | |
| 507 if (!enumerator->RetrieveColumn(L"IsFolder", &entry.is_folder)) | |
| 508 continue; | |
| 509 base::string16 url; | |
| 510 if (!enumerator->RetrieveColumn(L"URL", &url)) | |
| 511 continue; | |
| 512 entry.url = GURL(url); | |
| 513 if (!entry.is_folder && !entry.url.is_valid()) | |
| 514 continue; | |
| 515 if (!enumerator->RetrieveColumn(L"Title", &entry.title)) | |
| 516 continue; | |
| 517 base::string16 favicon_file; | |
| 518 if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file)) | |
| 519 continue; | |
| 520 if (!favicon_file.empty()) | |
| 521 entry.favicon_file = favicon_base.Append(favicon_file); | |
| 522 if (!enumerator->RetrieveColumn(L"ParentId", &entry.parent_id)) | |
| 523 continue; | |
| 524 if (!enumerator->RetrieveColumn(L"ItemId", &entry.item_id)) | |
| 525 continue; | |
| 526 if (!enumerator->RetrieveColumn(L"OrderNumber", &entry.order_number)) | |
| 527 continue; | |
| 528 FILETIME data_updated; | |
| 529 if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated)) | |
| 530 continue; | |
| 531 entry.date_updated = base::Time::FromFileTime(data_updated); | |
| 532 database_entries[entry.item_id] = entry; | |
| 533 } while (enumerator->Next() && !cancelled()); | |
| 534 | |
| 535 // Build simple tree. | |
| 536 EdgeFavoriteEntry root_entry; | |
| 537 for (auto& entry : database_entries) { | |
| 538 auto found_parent = database_entries.find(entry.second.parent_id); | |
| 539 if (found_parent == database_entries.end() || | |
| 540 !found_parent->second.is_folder) { | |
| 541 root_entry.children.push_back(&entry.second); | |
| 542 } else { | |
| 543 found_parent->second.children.push_back(&entry.second); | |
| 544 } | |
| 545 } | |
| 546 // With tree built sort the children of each node including the root. | |
| 547 std::sort(root_entry.children.begin(), root_entry.children.end(), | |
| 548 EdgeFavoriteEntryComparator()); | |
| 549 for (auto& entry : database_entries) { | |
| 550 std::sort(entry.second.children.begin(), entry.second.children.end(), | |
| 551 EdgeFavoriteEntryComparator()); | |
| 552 } | |
| 553 std::vector<base::string16> path; | |
| 554 BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path); | |
| 555 } | |
| OLD | NEW |