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 <vector> | |
| 16 | |
| 17 #include "base/files/file_enumerator.h" | |
| 18 #include "base/files/file_path.h" | |
| 19 #include "base/files/file_util.h" | |
| 20 #include "base/memory/ref_counted.h" | |
| 21 #include "base/memory/scoped_ptr.h" | |
| 22 #include "base/strings/string16.h" | |
| 23 #include "base/strings/string_util.h" | |
| 24 #include "base/time/time.h" | |
| 25 #include "base/win/windows_version.h" | |
| 26 | |
|
Ilya Sherman
2015/11/26 02:04:45
nit: This line break is typically omitted in Chrom
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 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[1024] = {}; | |
|
Ilya Sherman
2015/11/26 02:04:45
nit: Why 1024?
forshaw
2015/11/30 12:57:58
Just an arbitrary size, the API provides no way of
| |
| 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 bool set_last_error(JET_ERR error) { | |
|
Ilya Sherman
2015/11/26 02:04:44
Please document what the return value means. Also
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 61 last_error_ = error; | |
| 62 if (error == JET_errSuccess) | |
| 63 return true; | |
| 64 return false; | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: "return error == JET_errSuccess;"
forshaw
2015/11/30 12:57:59
Acknowledged.
| |
| 65 } | |
| 66 | |
| 67 private: | |
| 68 JET_ERR last_error_; | |
| 69 | |
| 70 DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject); | |
| 71 }; | |
| 72 | |
| 73 class EdgeDatabaseTableEnumerator : public EdgeErrorObject { | |
| 74 public: | |
| 75 EdgeDatabaseTableEnumerator(const base::string16& table_name, | |
| 76 JET_SESID session_id, | |
| 77 JET_TABLEID table_id) | |
| 78 : table_id_(table_id), table_name_(table_name), session_id_(session_id) {} | |
| 79 | |
| 80 ~EdgeDatabaseTableEnumerator() { | |
| 81 if (table_id_ != JET_tableidNil) { | |
| 82 JetCloseTable(session_id_, table_id_); | |
| 83 table_id_ = JET_tableidNil; | |
|
Ilya Sherman
2015/11/26 02:04:44
Why is this line needed, given that it's in the de
forshaw
2015/11/30 12:57:58
I tend to do this to reduce risk of breakage if it
| |
| 84 } | |
| 85 } | |
| 86 | |
| 87 const base::string16& table_name() { return table_name_; } | |
| 88 | |
| 89 bool Reset() { | |
| 90 return set_last_error(JetMove(session_id_, table_id_, JET_MoveFirst, 0)); | |
| 91 } | |
| 92 | |
| 93 bool Next() { | |
| 94 return set_last_error(JetMove(session_id_, table_id_, JET_MoveNext, 0)); | |
| 95 } | |
| 96 | |
| 97 template <typename T> | |
| 98 bool RetrieveColumn(const base::string16& column_name, | |
| 99 T& value, | |
| 100 bool& is_null) { | |
|
Ilya Sherman
2015/11/26 02:04:44
Please pass these by pointer rather than by refere
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 101 const JET_COLUMNBASE& column_base = GetColumnByName(column_name); | |
| 102 if (column_base.cbMax == 0) | |
| 103 return false; | |
| 104 std::vector<uint8_t> column_data(column_base.cbMax); | |
| 105 unsigned long actual_size = 0; | |
| 106 JET_ERR err = JetRetrieveColumn( | |
| 107 session_id_, table_id_, column_base.columnid, &column_data[0], | |
| 108 column_data.size(), &actual_size, 0, nullptr); | |
| 109 if (err != JET_errSuccess && err != JET_wrnColumnNull) { | |
| 110 set_last_error(err); | |
|
Ilya Sherman
2015/11/26 02:04:44
Is it important whether or not set_last_error is e
forshaw
2015/11/30 12:57:59
Not really important, just in most cases there's n
| |
| 111 return false; | |
| 112 } | |
| 113 | |
| 114 if (err == JET_errSuccess) { | |
| 115 column_data.resize(actual_size); | |
| 116 if (!ValidateAndConvertValue(column_base.coltyp, column_data, value)) | |
| 117 return false; | |
| 118 } else { | |
| 119 value = T(); | |
| 120 } | |
| 121 | |
| 122 is_null = err == JET_wrnColumnNull; | |
| 123 return true; | |
| 124 } | |
| 125 | |
| 126 template <typename T> | |
| 127 bool RetrieveColumn(const base::string16& column_name, T& value) { | |
|
Ilya Sherman
2015/11/26 02:04:45
Chromium style generally speaking frowns upon defa
forshaw
2015/11/30 12:57:58
Well I'd argue that it isn't providing default par
| |
| 128 bool is_null; | |
| 129 return RetrieveColumn(column_name, value, is_null); | |
| 130 } | |
| 131 | |
| 132 private: | |
| 133 template <typename T> | |
| 134 bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type, | |
| 135 const JET_COLTYP column_type, | |
| 136 const std::vector<uint8_t>& column_data, | |
| 137 T& value) { | |
| 138 if ((column_type == match_column_type) && | |
| 139 (column_data.size() >= sizeof(value))) { | |
|
Ilya Sherman
2015/11/26 02:04:44
Why is this >= rather than simply =? Truncating d
forshaw
2015/11/30 12:57:58
The database engine is one of the ones where the t
| |
| 140 memcpy(&value, &column_data[0], sizeof(value)); | |
| 141 return true; | |
| 142 } | |
| 143 set_last_error(JET_errInvalidColumnType); | |
| 144 return false; | |
| 145 } | |
| 146 | |
| 147 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 148 const std::vector<uint8_t>& column_data, | |
| 149 bool& value) { | |
| 150 if ((column_type == JET_coltypBit) && (column_data.size() >= 1)) { | |
| 151 value = (column_data[0] & 1) == 1; | |
| 152 return true; | |
| 153 } | |
| 154 set_last_error(JET_errInvalidColumnType); | |
| 155 return false; | |
| 156 } | |
| 157 | |
| 158 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 159 const std::vector<uint8_t>& column_data, | |
| 160 base::string16& value) { | |
| 161 if ((column_type == JET_coltypLongText) && | |
| 162 ((column_data.size() % sizeof(base::char16)) == 0)) { | |
| 163 const base::char16* ptr = | |
| 164 reinterpret_cast<const base::char16*>(&column_data[0]); | |
|
Ilya Sherman
2015/11/26 02:04:44
I may be wrong, I don't think that this is a safe
forshaw
2015/11/30 12:57:59
Reworked
| |
| 165 size_t length = column_data.size() / sizeof(base::char16); | |
| 166 // Remove any trailing NUL characters. | |
| 167 while (length > 0) { | |
| 168 if (ptr[length - 1]) | |
| 169 break; | |
| 170 length--; | |
| 171 } | |
| 172 value = base::string16(ptr, length); | |
| 173 return true; | |
| 174 } | |
| 175 set_last_error(JET_errInvalidColumnType); | |
| 176 return false; | |
| 177 } | |
| 178 | |
| 179 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 180 const std::vector<uint8_t>& column_data, | |
| 181 GUID& value) { | |
| 182 return ValidateAndConvertValueGeneric(JET_coltypGUID, 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 int32_t& value) { | |
| 189 return ValidateAndConvertValueGeneric(JET_coltypLong, 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 int64_t& 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 FILETIME& value) { | |
| 203 return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type, | |
| 204 column_data, value); | |
| 205 } | |
| 206 | |
| 207 bool ValidateAndConvertValue(const JET_COLTYP column_type, | |
| 208 const std::vector<uint8_t>& column_data, | |
| 209 uint32_t& value) { | |
| 210 return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type, | |
| 211 column_data, value); | |
| 212 } | |
| 213 | |
| 214 const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name) { | |
| 215 auto found_col = columns_by_name_.find(column_name); | |
| 216 if (found_col == columns_by_name_.end()) { | |
| 217 JET_COLUMNBASE column_base = {}; | |
| 218 column_base.cbStruct = sizeof(JET_COLUMNBASE); | |
| 219 if (!set_last_error(JetGetTableColumnInfo( | |
| 220 session_id_, table_id_, column_name.c_str(), &column_base, | |
| 221 sizeof(column_base), JET_ColInfoBase))) { | |
| 222 // 0 means we'll fail to extract the column data. | |
| 223 column_base.cbMax = 0; | |
| 224 } | |
| 225 columns_by_name_[column_name] = column_base; | |
| 226 found_col = columns_by_name_.find(column_name); | |
| 227 } | |
| 228 return found_col->second; | |
| 229 } | |
| 230 | |
| 231 std::map<const base::string16, JET_COLUMNBASE> columns_by_name_; | |
| 232 JET_TABLEID table_id_; | |
| 233 base::string16 table_name_; | |
| 234 JET_SESID session_id_; | |
| 235 | |
| 236 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator); | |
| 237 }; | |
| 238 | |
| 239 class EdgeDatabaseSession : public EdgeErrorObject { | |
| 240 public: | |
| 241 EdgeDatabaseSession() | |
| 242 : db_id_(JET_dbidNil), | |
| 243 instance_id_(JET_instanceNil), | |
| 244 session_id_(JET_sesidNil) {} | |
| 245 | |
| 246 ~EdgeDatabaseSession() { | |
| 247 // We don't need to collect other ID handles, terminating instance | |
| 248 // is enough to shut the entire session down. | |
| 249 if (instance_id_ != JET_instanceNil) { | |
| 250 JetTerm(instance_id_); | |
| 251 instance_id_ = JET_instanceNil; | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Why is this line needed?
forshaw
2015/11/30 12:57:59
As above but removed.
| |
| 252 } | |
| 253 } | |
| 254 | |
| 255 bool OpenDatabase(const base::string16& database_file) { | |
| 256 if (IsOpen()) { | |
| 257 set_last_error(JET_errOneDatabasePerSession); | |
| 258 return false; | |
| 259 } | |
| 260 if (!set_last_error(JetSetSystemParameter( | |
| 261 nullptr, JET_sesidNil, JET_paramDatabasePageSize, 8192, nullptr))) | |
|
Ilya Sherman
2015/11/26 02:04:44
Why 8192? Is that a guaranteed constant, or might
forshaw
2015/11/30 12:57:58
In theory it could change but it's unlikely to do
| |
| 262 return false; | |
| 263 if (!set_last_error(JetCreateInstance(&instance_id_, L"EdgeDataImporter"))) | |
| 264 return false; | |
| 265 if (!set_last_error(JetSetSystemParameter(&instance_id_, JET_sesidNil, | |
| 266 JET_paramRecovery, 0, L"Off"))) | |
| 267 return false; | |
| 268 if (!set_last_error(JetInit(&instance_id_))) | |
| 269 return false; | |
| 270 if (!set_last_error( | |
| 271 JetBeginSession(instance_id_, &session_id_, nullptr, nullptr))) | |
| 272 return false; | |
| 273 if (!set_last_error(JetAttachDatabase2(session_id_, database_file.c_str(), | |
| 274 0, JET_bitDbReadOnly))) | |
| 275 return false; | |
| 276 if (!set_last_error(JetOpenDatabase(session_id_, database_file.c_str(), | |
| 277 nullptr, &db_id_, JET_bitDbReadOnly))) | |
| 278 return false; | |
| 279 return true; | |
| 280 } | |
| 281 | |
| 282 scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator( | |
| 283 const base::string16& table_name) { | |
| 284 JET_TABLEID table_id; | |
| 285 | |
| 286 if (!IsOpen()) { | |
| 287 set_last_error(JET_errDatabaseNotFound); | |
| 288 return nullptr; | |
| 289 } | |
| 290 | |
| 291 if (!set_last_error(JetOpenTable(session_id_, db_id_, table_name.c_str(), | |
| 292 nullptr, 0, JET_bitTableReadOnly, | |
| 293 &table_id))) | |
| 294 return nullptr; | |
| 295 | |
| 296 return scoped_ptr<EdgeDatabaseTableEnumerator>( | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: You can use make_scoped_ptr here.
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 297 new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id)); | |
| 298 } | |
| 299 | |
| 300 private: | |
| 301 bool IsOpen() { return instance_id_ != JET_instanceNil; } | |
| 302 | |
| 303 JET_DBID db_id_; | |
| 304 JET_INSTANCE instance_id_; | |
| 305 JET_SESID session_id_; | |
| 306 | |
| 307 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession); | |
| 308 }; | |
| 309 | |
| 310 struct EdgeFavoriteEntry { | |
| 311 EdgeFavoriteEntry() | |
| 312 : is_folder(false), | |
| 313 order_number(0), | |
| 314 item_id(GUID_NULL), | |
| 315 parent_id(GUID_NULL) {} | |
| 316 | |
| 317 base::string16 title; | |
| 318 GURL url; | |
| 319 base::FilePath favicon_file; | |
| 320 bool is_folder; | |
| 321 int64_t order_number; | |
| 322 base::Time date_updated; | |
| 323 GUID item_id; | |
| 324 GUID parent_id; | |
| 325 | |
| 326 std::vector<EdgeFavoriteEntry*> children; | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Can the items be const?
forshaw
2015/11/30 12:57:58
I've moved the sorting of the children out of the
| |
| 327 | |
| 328 ImportedBookmarkEntry ToBookmarkEntry( | |
| 329 bool in_toolbar, | |
| 330 const std::vector<base::string16>& path) { | |
| 331 ImportedBookmarkEntry entry; | |
| 332 entry.in_toolbar = in_toolbar; | |
| 333 entry.is_folder = is_folder; | |
| 334 entry.url = url; | |
| 335 entry.path = path; | |
| 336 entry.title = title; | |
| 337 entry.creation_time = date_updated; | |
| 338 return entry; | |
| 339 } | |
| 340 }; | |
| 341 | |
| 342 struct EdgeFavoriteEntryComparator { | |
| 343 bool operator()(const EdgeFavoriteEntry* lhs, | |
| 344 const EdgeFavoriteEntry* rhs) const { | |
| 345 if (lhs->order_number == rhs->order_number) | |
| 346 return lhs->title < rhs->title; | |
| 347 else | |
| 348 return lhs->order_number < rhs->order_number; | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Please implement this using std::tie: https:/
forshaw
2015/11/30 12:57:59
Acknowledged.
| |
| 349 } | |
| 350 }; | |
| 351 | |
| 352 // The name of the database file is spartan.edb, however it isn't clear how | |
| 353 // the intermediate path between the DataStore and the database is generated. | |
| 354 // Therefore we just do a simple recursive search until we find a matching name. | |
| 355 base::FilePath FindSpartanDatabase(base::FilePath profile_path) { | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Pass by const-ref?
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 356 base::FilePath data_path = | |
| 357 profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path; | |
|
Ilya Sherman
2015/11/26 02:04:44
Are both of these cases possible? I'd kind of exp
forshaw
2015/11/30 12:57:58
GetEdgeDataFilePath could also return an empty pat
| |
| 358 if (data_path.empty()) | |
| 359 return base::FilePath(); | |
| 360 | |
| 361 base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true, | |
| 362 base::FileEnumerator::FILES); | |
| 363 base::FilePath path = enumerator.Next(); | |
| 364 while (!path.empty()) { | |
| 365 if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(), | |
| 366 kSpartanDatabaseFile)) | |
| 367 return path; | |
| 368 path = enumerator.Next(); | |
| 369 } | |
| 370 return base::FilePath(); | |
| 371 } | |
| 372 | |
| 373 struct GuidComparator { | |
| 374 bool operator()(const GUID& a, const GUID& b) const { | |
| 375 return memcmp(&a, &b, sizeof(a)) < 0; | |
| 376 } | |
| 377 }; | |
| 378 | |
| 379 bool ReadFaviconData(const base::FilePath& file, | |
| 380 std::vector<unsigned char>* data) { | |
| 381 std::string image_data; | |
| 382 if (!base::ReadFileToString(file, &image_data)) | |
| 383 return false; | |
| 384 | |
| 385 const unsigned char* ptr = | |
| 386 reinterpret_cast<const unsigned char*>(image_data.c_str()); | |
| 387 return importer::ReencodeFavicon(ptr, image_data.size(), data); | |
| 388 } | |
| 389 | |
| 390 void BuildBookmarkEntries(EdgeFavoriteEntry* current_entry, | |
| 391 std::vector<ImportedBookmarkEntry>* bookmarks, | |
| 392 favicon_base::FaviconUsageDataList* favicons, | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Outparams are usually written after the in-pa
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 393 bool is_toolbar, | |
| 394 std::vector<base::string16>& path) { | |
| 395 // Sort the children first by order number and then by title. | |
| 396 std::sort(current_entry->children.begin(), current_entry->children.end(), | |
| 397 EdgeFavoriteEntryComparator()); | |
| 398 for (EdgeFavoriteEntry* entry : current_entry->children) { | |
| 399 if (entry->is_folder) { | |
| 400 // If the favorites bar then load all children as toolbar items. | |
| 401 if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) { | |
| 402 // Replace name with Links similar to IE. | |
| 403 path.push_back(L"Links"); | |
| 404 BuildBookmarkEntries(entry, bookmarks, favicons, true, path); | |
| 405 path.pop_back(); | |
| 406 } else { | |
| 407 path.push_back(entry->title); | |
| 408 BuildBookmarkEntries(entry, bookmarks, favicons, is_toolbar, path); | |
| 409 path.pop_back(); | |
| 410 } | |
| 411 } else { | |
| 412 bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, path)); | |
| 413 favicon_base::FaviconUsageData favicon; | |
| 414 if (entry->url.is_valid() && !entry->favicon_file.empty() && | |
| 415 ReadFaviconData(entry->favicon_file, &favicon.png_data)) { | |
| 416 // As the database doesn't provide us a favicon URL we'll fake one. | |
| 417 GURL::Replacements path_replace; | |
| 418 path_replace.SetPathStr("/favicon.ico"); | |
| 419 favicon.favicon_url = | |
| 420 entry->url.GetWithEmptyPath().ReplaceComponents(path_replace); | |
| 421 favicon.urls.insert(entry->url); | |
| 422 favicons->push_back(favicon); | |
| 423 } | |
| 424 } | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 } // namespace | |
| 429 | |
| 430 EdgeImporter::EdgeImporter() {} | |
| 431 | |
| 432 void EdgeImporter::StartImport(const importer::SourceProfile& source_profile, | |
| 433 uint16 items, | |
| 434 ImporterBridge* bridge) { | |
| 435 bridge_ = bridge; | |
| 436 bridge_->NotifyStarted(); | |
| 437 source_path_ = source_profile.source_path; | |
| 438 | |
| 439 if ((items & importer::FAVORITES) && !cancelled()) { | |
| 440 bridge_->NotifyItemStarted(importer::FAVORITES); | |
| 441 ImportFavorites(); | |
| 442 bridge_->NotifyItemEnded(importer::FAVORITES); | |
| 443 } | |
| 444 bridge_->NotifyEnded(); | |
| 445 } | |
| 446 | |
| 447 EdgeImporter::~EdgeImporter() {} | |
| 448 | |
| 449 void EdgeImporter::ImportFavorites() { | |
| 450 BookmarkVector bookmarks; | |
| 451 favicon_base::FaviconUsageDataList favicons; | |
| 452 ParseFavoritesDatabase(&bookmarks, &favicons); | |
| 453 | |
| 454 if (!bookmarks.empty() && !cancelled()) { | |
| 455 const base::string16& first_folder_name = | |
| 456 l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE); | |
| 457 bridge_->AddBookmarks(bookmarks, first_folder_name); | |
| 458 } | |
| 459 if (!favicons.empty() && !cancelled()) | |
| 460 bridge_->SetFavicons(favicons); | |
| 461 } | |
| 462 | |
| 463 // From Edge 13 (released with Windows 10 TH2) Favorites are stored in a JET | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Please add a comma after the closing paren.
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 464 // database within the Edge local storage. The import uses the ESE library to | |
| 465 // open and read the data file. The data is stored in a Favorites table with | |
| 466 // the following schema. | |
| 467 // Column Name Column Type | |
| 468 // ------------------------------------------ | |
| 469 // DateUpdated LongLong - FILETIME | |
| 470 // FaviconFile LongText - Relative path | |
| 471 // HashedUrl ULong | |
| 472 // IsDeleted Bit | |
| 473 // IsFolder Bit | |
| 474 // ItemId Guid | |
| 475 // OrderNumber LongLong | |
| 476 // ParentId Guid | |
| 477 // RoamDisabled Bit | |
| 478 // RowId Long | |
| 479 // Title LongText | |
| 480 // URL LongText | |
| 481 void EdgeImporter::ParseFavoritesDatabase( | |
| 482 BookmarkVector* bookmarks, | |
| 483 favicon_base::FaviconUsageDataList* favicons) { | |
| 484 base::FilePath database_path = FindSpartanDatabase(source_path_); | |
| 485 if (database_path.empty()) | |
| 486 return; | |
| 487 | |
| 488 EdgeDatabaseSession database; | |
| 489 if (!database.OpenDatabase(database_path.value())) { | |
| 490 DLOG(ERROR) << "Error opening database " << database.GetErrorMessage(); | |
|
Ilya Sherman
2015/11/26 02:04:44
Optional nit: You probably want DVLOG or just LOG.
forshaw
2015/11/30 12:57:58
Acknowledged.
| |
| 491 return; | |
| 492 } | |
| 493 | |
| 494 scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = | |
| 495 database.OpenTableEnumerator(L"Favorites"); | |
| 496 if (!enumerator) { | |
| 497 DLOG(ERROR) << "Error opening database table " | |
| 498 << database.GetErrorMessage(); | |
| 499 return; | |
| 500 } | |
| 501 | |
| 502 std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; | |
| 503 base::FilePath favicon_base = | |
| 504 source_path_.empty() | |
| 505 ? importer::GetEdgeDataFilePath().Append(L"DataStore") | |
| 506 : source_path_.Append(L"DataStore"); | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: I'd move the .Append(L"DataStore") out of the
forshaw
2015/11/30 12:57:59
Acknowledged.
| |
| 507 | |
| 508 enumerator->Reset(); | |
| 509 do { | |
|
Ilya Sherman
2015/11/26 02:04:45
Is it possible for the enumerator to be empty?
forshaw
2015/11/30 12:57:58
Yes it could be, this would be caught by the loop
| |
| 510 EdgeFavoriteEntry entry; | |
| 511 bool is_deleted = false; | |
| 512 if (!enumerator->RetrieveColumn(L"IsDeleted", is_deleted)) | |
| 513 continue; | |
| 514 if (is_deleted) | |
| 515 continue; | |
| 516 if (!enumerator->RetrieveColumn(L"IsFolder", entry.is_folder)) | |
| 517 continue; | |
| 518 base::string16 url; | |
| 519 if (!enumerator->RetrieveColumn(L"URL", url)) | |
| 520 continue; | |
| 521 entry.url = GURL(url); | |
| 522 if (!entry.is_folder && !entry.url.is_valid()) | |
| 523 continue; | |
| 524 if (!enumerator->RetrieveColumn(L"Title", entry.title)) | |
| 525 continue; | |
| 526 base::string16 favicon_file; | |
| 527 if (!enumerator->RetrieveColumn(L"FaviconFile", favicon_file)) | |
| 528 continue; | |
| 529 if (!favicon_file.empty()) | |
| 530 entry.favicon_file = favicon_base.Append(favicon_file); | |
| 531 if (!enumerator->RetrieveColumn(L"ParentId", entry.parent_id)) | |
| 532 continue; | |
| 533 if (!enumerator->RetrieveColumn(L"ItemId", entry.item_id)) | |
| 534 continue; | |
| 535 if (!enumerator->RetrieveColumn(L"OrderNumber", entry.order_number)) | |
| 536 continue; | |
| 537 FILETIME ft; | |
| 538 if (!enumerator->RetrieveColumn(L"DateUpdated", ft)) | |
| 539 continue; | |
| 540 entry.date_updated = base::Time::FromFileTime(ft); | |
| 541 database_entries[entry.item_id] = entry; | |
| 542 } while (enumerator->Next() && !cancelled()); | |
| 543 | |
| 544 // Build simple tree. | |
| 545 EdgeFavoriteEntry root_entry; | |
| 546 for (auto& e : database_entries) { | |
|
Ilya Sherman
2015/11/26 02:04:44
nit: Can the entry be const?
Ilya Sherman
2015/11/26 02:04:44
nit: s/e/entry
forshaw
2015/11/30 12:57:58
Acknowledged.
forshaw
2015/11/30 12:57:58
In the loop? Don't think so unless I'm missing som
forshaw
2015/11/30 12:57:58
Acknowledged.
forshaw
2015/11/30 12:57:59
Acknowledged.
| |
| 547 auto found_parent = database_entries.find(e.second.parent_id); | |
| 548 if (found_parent == database_entries.end() || | |
| 549 !found_parent->second.is_folder) { | |
| 550 root_entry.children.push_back(&e.second); | |
| 551 } else { | |
| 552 found_parent->second.children.push_back(&e.second); | |
| 553 } | |
| 554 } | |
| 555 std::vector<base::string16> path; | |
| 556 BuildBookmarkEntries(&root_entry, bookmarks, favicons, false, path); | |
| 557 } | |
| OLD | NEW |