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 |