 Chromium Code Reviews
 Chromium Code Reviews Issue 1465853002:
  Implement support for importing favorites from Edge on Windows 10.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1465853002:
  Implement support for importing favorites from Edge on Windows 10.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| 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 | |
| 41 class EdgeErrorObject { | |
| 42 public: | |
| 43 EdgeErrorObject() : last_error_(JET_errSuccess) {} | |
| 44 | |
| 45 base::string16 GetErrorMessage() const { | |
| 46 WCHAR error_message[kErrorMessagesize] = {}; | |
| 47 JET_API_PTR err = last_error_; | |
| 48 JET_ERR result = JetGetSystemParameter( | |
| 49 JET_instanceNil, JET_sesidNil, JET_paramErrorToString, &err, | |
| 50 error_message, sizeof(error_message)); | |
| 51 if (result != JET_errSuccess) | |
| 52 return L""; | |
| 53 | |
| 54 return error_message; | |
| 55 } | |
| 56 | |
| 57 protected: | |
| 58 JET_ERR last_error() const { return last_error_; } | |
| 59 | |
| 60 // This function returns true if the passed error parameter is equal | |
| 61 // to JET_errSuccess | |
| 62 bool SetLastError(JET_ERR error) { | |
| 63 last_error_ = error; | |
| 64 return error == JET_errSuccess; | |
| 65 } | |
| 66 | |
| 67 private: | |
| 68 static const size_t kErrorMessagesize = 1024; | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: Please move this to the top of the file, as w
 
Ilya Sherman
2015/12/01 07:31:18
Since you mentioned that this is an arbitrarily ch
 
Ilya Sherman
2015/12/01 07:31:18
nit: s/size/Size
 
forshaw
2015/12/01 11:12:12
Acknowledged.
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 69 | |
| 70 JET_ERR last_error_; | |
| 71 | |
| 72 DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject); | |
| 73 }; | |
| 74 | |
| 75 class EdgeDatabaseTableEnumerator : public EdgeErrorObject { | |
| 76 public: | |
| 77 EdgeDatabaseTableEnumerator(const base::string16& table_name, | |
| 78 JET_SESID session_id, | |
| 79 JET_TABLEID table_id) | |
| 80 : table_id_(table_id), table_name_(table_name), session_id_(session_id) {} | |
| 81 | |
| 82 ~EdgeDatabaseTableEnumerator() { | |
| 83 if (table_id_ != JET_tableidNil) { | |
| 84 JetCloseTable(session_id_, table_id_); | |
| 85 } | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: You can now drop the curly braces.
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 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(column_data.size() / sizeof(base::char16)); | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: Reuse char_length here?
 
forshaw
2015/12/01 11:12:12
Acknowledged.
 | |
| 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 means indicates an invalid column. | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: s/means indicates/indicates
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 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 } | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: You can remove the curly braces here.
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 245 } | |
| 246 | |
| 247 bool OpenDatabase(const base::string16& database_file) { | |
| 248 if (IsOpen()) { | |
| 249 SetLastError(JET_errOneDatabasePerSession); | |
| 250 return false; | |
| 251 } | |
| 252 if (!SetLastError(JetSetSystemParameter(nullptr, JET_sesidNil, | |
| 253 JET_paramDatabasePageSize, | |
| 254 kEdgeDatabasePageSize, nullptr))) | |
| 255 return false; | |
| 256 if (!SetLastError(JetCreateInstance(&instance_id_, L"EdgeDataImporter"))) | |
| 257 return false; | |
| 258 if (!SetLastError(JetSetSystemParameter(&instance_id_, JET_sesidNil, | |
| 259 JET_paramRecovery, 0, L"Off"))) | |
| 260 return false; | |
| 261 if (!SetLastError(JetInit(&instance_id_))) | |
| 262 return false; | |
| 263 if (!SetLastError( | |
| 264 JetBeginSession(instance_id_, &session_id_, nullptr, nullptr))) | |
| 265 return false; | |
| 266 if (!SetLastError(JetAttachDatabase2(session_id_, database_file.c_str(), 0, | |
| 267 JET_bitDbReadOnly))) | |
| 268 return false; | |
| 269 if (!SetLastError(JetOpenDatabase(session_id_, database_file.c_str(), | |
| 270 nullptr, &db_id_, JET_bitDbReadOnly))) | |
| 271 return false; | |
| 272 return true; | |
| 273 } | |
| 274 | |
| 275 scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator( | |
| 276 const base::string16& table_name) { | |
| 277 JET_TABLEID table_id; | |
| 278 | |
| 279 if (!IsOpen()) { | |
| 280 SetLastError(JET_errDatabaseNotFound); | |
| 281 return nullptr; | |
| 282 } | |
| 283 | |
| 284 if (!SetLastError(JetOpenTable(session_id_, db_id_, table_name.c_str(), | |
| 285 nullptr, 0, JET_bitTableReadOnly, | |
| 286 &table_id))) | |
| 287 return nullptr; | |
| 288 | |
| 289 return make_scoped_ptr( | |
| 290 new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id)); | |
| 291 } | |
| 292 | |
| 293 private: | |
| 294 // This is the page size of the Edge data. It's unlikely to change. | |
| 295 static const JET_API_PTR kEdgeDatabasePageSize = 8192; | |
| 296 | |
| 297 bool IsOpen() { return instance_id_ != JET_instanceNil; } | |
| 298 | |
| 299 JET_DBID db_id_; | |
| 300 JET_INSTANCE instance_id_; | |
| 301 JET_SESID session_id_; | |
| 302 | |
| 303 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession); | |
| 304 }; | |
| 305 | |
| 306 struct EdgeFavoriteEntry { | |
| 307 EdgeFavoriteEntry() | |
| 308 : is_folder(false), | |
| 309 order_number(0), | |
| 310 item_id(GUID_NULL), | |
| 311 parent_id(GUID_NULL) {} | |
| 312 | |
| 313 base::string16 title; | |
| 314 GURL url; | |
| 315 base::FilePath favicon_file; | |
| 316 bool is_folder; | |
| 317 int64_t order_number; | |
| 318 base::Time date_updated; | |
| 319 GUID item_id; | |
| 320 GUID parent_id; | |
| 321 | |
| 322 std::vector<const EdgeFavoriteEntry*> children; | |
| 323 | |
| 324 ImportedBookmarkEntry ToBookmarkEntry( | |
| 325 bool in_toolbar, | |
| 326 const std::vector<base::string16>& path) const { | |
| 327 ImportedBookmarkEntry entry; | |
| 328 entry.in_toolbar = in_toolbar; | |
| 329 entry.is_folder = is_folder; | |
| 330 entry.url = url; | |
| 331 entry.path = path; | |
| 332 entry.title = title; | |
| 333 entry.creation_time = date_updated; | |
| 334 return entry; | |
| 335 } | |
| 336 }; | |
| 337 | |
| 338 struct EdgeFavoriteEntryComparator { | |
| 339 bool operator()(const EdgeFavoriteEntry* lhs, | |
| 340 const EdgeFavoriteEntry* rhs) const { | |
| 341 return std::tie(lhs->order_number, lhs->title) < | |
| 342 std::tie(rhs->order_number, rhs->title); | |
| 343 } | |
| 344 }; | |
| 345 | |
| 346 // The name of the database file is spartan.edb, however it isn't clear how | |
| 347 // the intermediate path between the DataStore and the database is generated. | |
| 348 // Therefore we just do a simple recursive search until we find a matching name. | |
| 349 base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) { | |
| 350 base::FilePath data_path = | |
| 351 profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path; | |
| 352 if (data_path.empty()) | |
| 353 return base::FilePath(); | |
| 354 | |
| 355 base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true, | |
| 356 base::FileEnumerator::FILES); | |
| 357 base::FilePath path = enumerator.Next(); | |
| 358 while (!path.empty()) { | |
| 359 if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(), | |
| 360 kSpartanDatabaseFile)) | |
| 361 return path; | |
| 362 path = enumerator.Next(); | |
| 363 } | |
| 364 return base::FilePath(); | |
| 365 } | |
| 366 | |
| 367 struct GuidComparator { | |
| 368 bool operator()(const GUID& a, const GUID& b) const { | |
| 369 return memcmp(&a, &b, sizeof(a)) < 0; | |
| 370 } | |
| 371 }; | |
| 372 | |
| 373 bool ReadFaviconData(const base::FilePath& file, | |
| 374 std::vector<unsigned char>* data) { | |
| 375 std::string image_data; | |
| 376 if (!base::ReadFileToString(file, &image_data)) | |
| 377 return false; | |
| 378 | |
| 379 const unsigned char* ptr = | |
| 380 reinterpret_cast<const unsigned char*>(image_data.c_str()); | |
| 381 return importer::ReencodeFavicon(ptr, image_data.size(), data); | |
| 382 } | |
| 383 | |
| 384 void BuildBookmarkEntries(const EdgeFavoriteEntry* current_entry, | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: Pass by const-ref?
 
forshaw
2015/12/01 11:12:12
Acknowledged.
 | |
| 385 bool is_toolbar, | |
| 386 std::vector<ImportedBookmarkEntry>* bookmarks, | |
| 387 favicon_base::FaviconUsageDataList* favicons, | |
| 388 std::vector<base::string16>* path) { | |
| 389 for (const EdgeFavoriteEntry* entry : current_entry->children) { | |
| 390 if (entry->is_folder) { | |
| 391 // If the favorites bar then load all children as toolbar items. | |
| 392 if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) { | |
| 393 // Replace name with Links similar to IE. | |
| 394 path->push_back(L"Links"); | |
| 395 BuildBookmarkEntries(entry, true, bookmarks, favicons, path); | |
| 396 path->pop_back(); | |
| 397 } else { | |
| 398 path->push_back(entry->title); | |
| 399 BuildBookmarkEntries(entry, is_toolbar, bookmarks, favicons, path); | |
| 400 path->pop_back(); | |
| 401 } | |
| 402 } else { | |
| 403 bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path)); | |
| 404 favicon_base::FaviconUsageData favicon; | |
| 405 if (entry->url.is_valid() && !entry->favicon_file.empty() && | |
| 406 ReadFaviconData(entry->favicon_file, &favicon.png_data)) { | |
| 407 // As the database doesn't provide us a favicon URL we'll fake one. | |
| 408 GURL::Replacements path_replace; | |
| 409 path_replace.SetPathStr("/favicon.ico"); | |
| 410 favicon.favicon_url = | |
| 411 entry->url.GetWithEmptyPath().ReplaceComponents(path_replace); | |
| 412 favicon.urls.insert(entry->url); | |
| 413 favicons->push_back(favicon); | |
| 414 } | |
| 415 } | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 } // namespace | |
| 420 | |
| 421 EdgeImporter::EdgeImporter() {} | |
| 422 | |
| 423 void EdgeImporter::StartImport(const importer::SourceProfile& source_profile, | |
| 424 uint16 items, | |
| 425 ImporterBridge* bridge) { | |
| 426 bridge_ = bridge; | |
| 427 bridge_->NotifyStarted(); | |
| 428 source_path_ = source_profile.source_path; | |
| 429 | |
| 430 if ((items & importer::FAVORITES) && !cancelled()) { | |
| 431 bridge_->NotifyItemStarted(importer::FAVORITES); | |
| 432 ImportFavorites(); | |
| 433 bridge_->NotifyItemEnded(importer::FAVORITES); | |
| 434 } | |
| 435 bridge_->NotifyEnded(); | |
| 436 } | |
| 437 | |
| 438 EdgeImporter::~EdgeImporter() {} | |
| 439 | |
| 440 void EdgeImporter::ImportFavorites() { | |
| 441 std::vector<ImportedBookmarkEntry> bookmarks; | |
| 442 favicon_base::FaviconUsageDataList favicons; | |
| 443 ParseFavoritesDatabase(&bookmarks, &favicons); | |
| 444 | |
| 445 if (!bookmarks.empty() && !cancelled()) { | |
| 446 const base::string16& first_folder_name = | |
| 447 l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE); | |
| 448 bridge_->AddBookmarks(bookmarks, first_folder_name); | |
| 449 } | |
| 450 if (!favicons.empty() && !cancelled()) | |
| 451 bridge_->SetFavicons(favicons); | |
| 452 } | |
| 453 | |
| 454 // From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET | |
| 455 // database within the Edge local storage. The import uses the ESE library to | |
| 456 // open and read the data file. The data is stored in a Favorites table with | |
| 457 // the following schema. | |
| 458 // Column Name Column Type | |
| 459 // ------------------------------------------ | |
| 460 // DateUpdated LongLong - FILETIME | |
| 461 // FaviconFile LongText - Relative path | |
| 462 // HashedUrl ULong | |
| 463 // IsDeleted Bit | |
| 464 // IsFolder Bit | |
| 465 // ItemId Guid | |
| 466 // OrderNumber LongLong | |
| 467 // ParentId Guid | |
| 468 // RoamDisabled Bit | |
| 469 // RowId Long | |
| 470 // Title LongText | |
| 471 // URL LongText | |
| 472 void EdgeImporter::ParseFavoritesDatabase( | |
| 473 std::vector<ImportedBookmarkEntry>* bookmarks, | |
| 474 favicon_base::FaviconUsageDataList* favicons) { | |
| 475 base::FilePath database_path = FindSpartanDatabase(source_path_); | |
| 476 if (database_path.empty()) | |
| 477 return; | |
| 478 | |
| 479 EdgeDatabaseSession database; | |
| 480 if (!database.OpenDatabase(database_path.value())) { | |
| 481 DVLOG(ERROR) << "Error opening database " << database.GetErrorMessage(); | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: Does this compile?  I think DVLOG expects a n
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 482 return; | |
| 483 } | |
| 484 | |
| 485 scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = | |
| 486 database.OpenTableEnumerator(L"Favorites"); | |
| 487 if (!enumerator) { | |
| 488 DVLOG(ERROR) << "Error opening database table " | |
| 489 << database.GetErrorMessage(); | |
| 490 return; | |
| 491 } | |
| 492 | |
| 493 std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; | |
| 494 base::FilePath favicon_base = | |
| 495 source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_; | |
| 496 favicon_base = favicon_base.Append(L"DataStore"); | |
| 497 | |
| 498 if (!enumerator->Reset()) | |
| 499 return; | |
| 
Ilya Sherman
2015/12/01 07:31:18
nit: I'd move this up, above the favicon_base comp
 
forshaw
2015/12/01 11:12:13
Acknowledged.
 | |
| 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 |