Index: chrome/utility/importer/edge_importer_win.cc |
diff --git a/chrome/utility/importer/edge_importer_win.cc b/chrome/utility/importer/edge_importer_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8497035ff9d1a5c1ccda1ea30ffd4586717cf905 |
--- /dev/null |
+++ b/chrome/utility/importer/edge_importer_win.cc |
@@ -0,0 +1,557 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/utility/importer/edge_importer_win.h" |
+ |
+#define JET_UNICODE |
+#include <esent.h> |
+#undef JET_UNICODE |
+#include <Shlobj.h> |
+ |
+#include <algorithm> |
+#include <map> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/files/file_enumerator.h" |
+#include "base/files/file_path.h" |
+#include "base/files/file_util.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/strings/string16.h" |
+#include "base/strings/string_util.h" |
+#include "base/time/time.h" |
+#include "base/win/windows_version.h" |
+ |
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.
|
+#include "chrome/common/importer/edge_importer_utils_win.h" |
+#include "chrome/common/importer/imported_bookmark_entry.h" |
+#include "chrome/common/importer/importer_bridge.h" |
+#include "chrome/grit/generated_resources.h" |
+#include "chrome/utility/importer/favicon_reencode.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+ |
+// Toolbar favorites are placed under this special folder name. |
+const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_"; |
+const base::char16 kSpartanDatabaseFile[] = L"spartan.edb"; |
+ |
+class EdgeErrorObject { |
+ public: |
+ EdgeErrorObject() : last_error_(JET_errSuccess) {} |
+ |
+ base::string16 GetErrorMessage() const { |
+ 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
|
+ JET_API_PTR err = last_error_; |
+ JET_ERR result = JetGetSystemParameter( |
+ JET_instanceNil, JET_sesidNil, JET_paramErrorToString, &err, |
+ error_message, sizeof(error_message)); |
+ if (result != JET_errSuccess) |
+ return L""; |
+ |
+ return error_message; |
+ } |
+ |
+ protected: |
+ JET_ERR last_error() const { return last_error_; } |
+ |
+ 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.
|
+ last_error_ = error; |
+ if (error == JET_errSuccess) |
+ return true; |
+ return false; |
Ilya Sherman
2015/11/26 02:04:44
nit: "return error == JET_errSuccess;"
forshaw
2015/11/30 12:57:59
Acknowledged.
|
+ } |
+ |
+ private: |
+ JET_ERR last_error_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject); |
+}; |
+ |
+class EdgeDatabaseTableEnumerator : public EdgeErrorObject { |
+ public: |
+ EdgeDatabaseTableEnumerator(const base::string16& table_name, |
+ JET_SESID session_id, |
+ JET_TABLEID table_id) |
+ : table_id_(table_id), table_name_(table_name), session_id_(session_id) {} |
+ |
+ ~EdgeDatabaseTableEnumerator() { |
+ if (table_id_ != JET_tableidNil) { |
+ JetCloseTable(session_id_, table_id_); |
+ 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
|
+ } |
+ } |
+ |
+ const base::string16& table_name() { return table_name_; } |
+ |
+ bool Reset() { |
+ return set_last_error(JetMove(session_id_, table_id_, JET_MoveFirst, 0)); |
+ } |
+ |
+ bool Next() { |
+ return set_last_error(JetMove(session_id_, table_id_, JET_MoveNext, 0)); |
+ } |
+ |
+ template <typename T> |
+ bool RetrieveColumn(const base::string16& column_name, |
+ T& value, |
+ 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.
|
+ const JET_COLUMNBASE& column_base = GetColumnByName(column_name); |
+ if (column_base.cbMax == 0) |
+ return false; |
+ std::vector<uint8_t> column_data(column_base.cbMax); |
+ unsigned long actual_size = 0; |
+ JET_ERR err = JetRetrieveColumn( |
+ session_id_, table_id_, column_base.columnid, &column_data[0], |
+ column_data.size(), &actual_size, 0, nullptr); |
+ if (err != JET_errSuccess && err != JET_wrnColumnNull) { |
+ 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
|
+ return false; |
+ } |
+ |
+ if (err == JET_errSuccess) { |
+ column_data.resize(actual_size); |
+ if (!ValidateAndConvertValue(column_base.coltyp, column_data, value)) |
+ return false; |
+ } else { |
+ value = T(); |
+ } |
+ |
+ is_null = err == JET_wrnColumnNull; |
+ return true; |
+ } |
+ |
+ template <typename T> |
+ 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
|
+ bool is_null; |
+ return RetrieveColumn(column_name, value, is_null); |
+ } |
+ |
+ private: |
+ template <typename T> |
+ bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type, |
+ const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ T& value) { |
+ if ((column_type == match_column_type) && |
+ (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
|
+ memcpy(&value, &column_data[0], sizeof(value)); |
+ return true; |
+ } |
+ set_last_error(JET_errInvalidColumnType); |
+ return false; |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ bool& value) { |
+ if ((column_type == JET_coltypBit) && (column_data.size() >= 1)) { |
+ value = (column_data[0] & 1) == 1; |
+ return true; |
+ } |
+ set_last_error(JET_errInvalidColumnType); |
+ return false; |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ base::string16& value) { |
+ if ((column_type == JET_coltypLongText) && |
+ ((column_data.size() % sizeof(base::char16)) == 0)) { |
+ const base::char16* ptr = |
+ 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
|
+ size_t length = column_data.size() / sizeof(base::char16); |
+ // Remove any trailing NUL characters. |
+ while (length > 0) { |
+ if (ptr[length - 1]) |
+ break; |
+ length--; |
+ } |
+ value = base::string16(ptr, length); |
+ return true; |
+ } |
+ set_last_error(JET_errInvalidColumnType); |
+ return false; |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ GUID& value) { |
+ return ValidateAndConvertValueGeneric(JET_coltypGUID, column_type, |
+ column_data, value); |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ int32_t& value) { |
+ return ValidateAndConvertValueGeneric(JET_coltypLong, column_type, |
+ column_data, value); |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ int64_t& value) { |
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type, |
+ column_data, value); |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ FILETIME& value) { |
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type, |
+ column_data, value); |
+ } |
+ |
+ bool ValidateAndConvertValue(const JET_COLTYP column_type, |
+ const std::vector<uint8_t>& column_data, |
+ uint32_t& value) { |
+ return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type, |
+ column_data, value); |
+ } |
+ |
+ const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name) { |
+ auto found_col = columns_by_name_.find(column_name); |
+ if (found_col == columns_by_name_.end()) { |
+ JET_COLUMNBASE column_base = {}; |
+ column_base.cbStruct = sizeof(JET_COLUMNBASE); |
+ if (!set_last_error(JetGetTableColumnInfo( |
+ session_id_, table_id_, column_name.c_str(), &column_base, |
+ sizeof(column_base), JET_ColInfoBase))) { |
+ // 0 means we'll fail to extract the column data. |
+ column_base.cbMax = 0; |
+ } |
+ columns_by_name_[column_name] = column_base; |
+ found_col = columns_by_name_.find(column_name); |
+ } |
+ return found_col->second; |
+ } |
+ |
+ std::map<const base::string16, JET_COLUMNBASE> columns_by_name_; |
+ JET_TABLEID table_id_; |
+ base::string16 table_name_; |
+ JET_SESID session_id_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator); |
+}; |
+ |
+class EdgeDatabaseSession : public EdgeErrorObject { |
+ public: |
+ EdgeDatabaseSession() |
+ : db_id_(JET_dbidNil), |
+ instance_id_(JET_instanceNil), |
+ session_id_(JET_sesidNil) {} |
+ |
+ ~EdgeDatabaseSession() { |
+ // We don't need to collect other ID handles, terminating instance |
+ // is enough to shut the entire session down. |
+ if (instance_id_ != JET_instanceNil) { |
+ JetTerm(instance_id_); |
+ 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.
|
+ } |
+ } |
+ |
+ bool OpenDatabase(const base::string16& database_file) { |
+ if (IsOpen()) { |
+ set_last_error(JET_errOneDatabasePerSession); |
+ return false; |
+ } |
+ if (!set_last_error(JetSetSystemParameter( |
+ 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
|
+ return false; |
+ if (!set_last_error(JetCreateInstance(&instance_id_, L"EdgeDataImporter"))) |
+ return false; |
+ if (!set_last_error(JetSetSystemParameter(&instance_id_, JET_sesidNil, |
+ JET_paramRecovery, 0, L"Off"))) |
+ return false; |
+ if (!set_last_error(JetInit(&instance_id_))) |
+ return false; |
+ if (!set_last_error( |
+ JetBeginSession(instance_id_, &session_id_, nullptr, nullptr))) |
+ return false; |
+ if (!set_last_error(JetAttachDatabase2(session_id_, database_file.c_str(), |
+ 0, JET_bitDbReadOnly))) |
+ return false; |
+ if (!set_last_error(JetOpenDatabase(session_id_, database_file.c_str(), |
+ nullptr, &db_id_, JET_bitDbReadOnly))) |
+ return false; |
+ return true; |
+ } |
+ |
+ scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator( |
+ const base::string16& table_name) { |
+ JET_TABLEID table_id; |
+ |
+ if (!IsOpen()) { |
+ set_last_error(JET_errDatabaseNotFound); |
+ return nullptr; |
+ } |
+ |
+ if (!set_last_error(JetOpenTable(session_id_, db_id_, table_name.c_str(), |
+ nullptr, 0, JET_bitTableReadOnly, |
+ &table_id))) |
+ return nullptr; |
+ |
+ 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.
|
+ new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id)); |
+ } |
+ |
+ private: |
+ bool IsOpen() { return instance_id_ != JET_instanceNil; } |
+ |
+ JET_DBID db_id_; |
+ JET_INSTANCE instance_id_; |
+ JET_SESID session_id_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession); |
+}; |
+ |
+struct EdgeFavoriteEntry { |
+ EdgeFavoriteEntry() |
+ : is_folder(false), |
+ order_number(0), |
+ item_id(GUID_NULL), |
+ parent_id(GUID_NULL) {} |
+ |
+ base::string16 title; |
+ GURL url; |
+ base::FilePath favicon_file; |
+ bool is_folder; |
+ int64_t order_number; |
+ base::Time date_updated; |
+ GUID item_id; |
+ GUID parent_id; |
+ |
+ 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
|
+ |
+ ImportedBookmarkEntry ToBookmarkEntry( |
+ bool in_toolbar, |
+ const std::vector<base::string16>& path) { |
+ ImportedBookmarkEntry entry; |
+ entry.in_toolbar = in_toolbar; |
+ entry.is_folder = is_folder; |
+ entry.url = url; |
+ entry.path = path; |
+ entry.title = title; |
+ entry.creation_time = date_updated; |
+ return entry; |
+ } |
+}; |
+ |
+struct EdgeFavoriteEntryComparator { |
+ bool operator()(const EdgeFavoriteEntry* lhs, |
+ const EdgeFavoriteEntry* rhs) const { |
+ if (lhs->order_number == rhs->order_number) |
+ return lhs->title < rhs->title; |
+ else |
+ 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.
|
+ } |
+}; |
+ |
+// The name of the database file is spartan.edb, however it isn't clear how |
+// the intermediate path between the DataStore and the database is generated. |
+// Therefore we just do a simple recursive search until we find a matching name. |
+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.
|
+ base::FilePath data_path = |
+ 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
|
+ if (data_path.empty()) |
+ return base::FilePath(); |
+ |
+ base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true, |
+ base::FileEnumerator::FILES); |
+ base::FilePath path = enumerator.Next(); |
+ while (!path.empty()) { |
+ if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(), |
+ kSpartanDatabaseFile)) |
+ return path; |
+ path = enumerator.Next(); |
+ } |
+ return base::FilePath(); |
+} |
+ |
+struct GuidComparator { |
+ bool operator()(const GUID& a, const GUID& b) const { |
+ return memcmp(&a, &b, sizeof(a)) < 0; |
+ } |
+}; |
+ |
+bool ReadFaviconData(const base::FilePath& file, |
+ std::vector<unsigned char>* data) { |
+ std::string image_data; |
+ if (!base::ReadFileToString(file, &image_data)) |
+ return false; |
+ |
+ const unsigned char* ptr = |
+ reinterpret_cast<const unsigned char*>(image_data.c_str()); |
+ return importer::ReencodeFavicon(ptr, image_data.size(), data); |
+} |
+ |
+void BuildBookmarkEntries(EdgeFavoriteEntry* current_entry, |
+ std::vector<ImportedBookmarkEntry>* bookmarks, |
+ 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.
|
+ bool is_toolbar, |
+ std::vector<base::string16>& path) { |
+ // Sort the children first by order number and then by title. |
+ std::sort(current_entry->children.begin(), current_entry->children.end(), |
+ EdgeFavoriteEntryComparator()); |
+ for (EdgeFavoriteEntry* entry : current_entry->children) { |
+ if (entry->is_folder) { |
+ // If the favorites bar then load all children as toolbar items. |
+ if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) { |
+ // Replace name with Links similar to IE. |
+ path.push_back(L"Links"); |
+ BuildBookmarkEntries(entry, bookmarks, favicons, true, path); |
+ path.pop_back(); |
+ } else { |
+ path.push_back(entry->title); |
+ BuildBookmarkEntries(entry, bookmarks, favicons, is_toolbar, path); |
+ path.pop_back(); |
+ } |
+ } else { |
+ bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, path)); |
+ favicon_base::FaviconUsageData favicon; |
+ if (entry->url.is_valid() && !entry->favicon_file.empty() && |
+ ReadFaviconData(entry->favicon_file, &favicon.png_data)) { |
+ // As the database doesn't provide us a favicon URL we'll fake one. |
+ GURL::Replacements path_replace; |
+ path_replace.SetPathStr("/favicon.ico"); |
+ favicon.favicon_url = |
+ entry->url.GetWithEmptyPath().ReplaceComponents(path_replace); |
+ favicon.urls.insert(entry->url); |
+ favicons->push_back(favicon); |
+ } |
+ } |
+ } |
+} |
+ |
+} // namespace |
+ |
+EdgeImporter::EdgeImporter() {} |
+ |
+void EdgeImporter::StartImport(const importer::SourceProfile& source_profile, |
+ uint16 items, |
+ ImporterBridge* bridge) { |
+ bridge_ = bridge; |
+ bridge_->NotifyStarted(); |
+ source_path_ = source_profile.source_path; |
+ |
+ if ((items & importer::FAVORITES) && !cancelled()) { |
+ bridge_->NotifyItemStarted(importer::FAVORITES); |
+ ImportFavorites(); |
+ bridge_->NotifyItemEnded(importer::FAVORITES); |
+ } |
+ bridge_->NotifyEnded(); |
+} |
+ |
+EdgeImporter::~EdgeImporter() {} |
+ |
+void EdgeImporter::ImportFavorites() { |
+ BookmarkVector bookmarks; |
+ favicon_base::FaviconUsageDataList favicons; |
+ ParseFavoritesDatabase(&bookmarks, &favicons); |
+ |
+ if (!bookmarks.empty() && !cancelled()) { |
+ const base::string16& first_folder_name = |
+ l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE); |
+ bridge_->AddBookmarks(bookmarks, first_folder_name); |
+ } |
+ if (!favicons.empty() && !cancelled()) |
+ bridge_->SetFavicons(favicons); |
+} |
+ |
+// 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.
|
+// database within the Edge local storage. The import uses the ESE library to |
+// open and read the data file. The data is stored in a Favorites table with |
+// the following schema. |
+// Column Name Column Type |
+// ------------------------------------------ |
+// DateUpdated LongLong - FILETIME |
+// FaviconFile LongText - Relative path |
+// HashedUrl ULong |
+// IsDeleted Bit |
+// IsFolder Bit |
+// ItemId Guid |
+// OrderNumber LongLong |
+// ParentId Guid |
+// RoamDisabled Bit |
+// RowId Long |
+// Title LongText |
+// URL LongText |
+void EdgeImporter::ParseFavoritesDatabase( |
+ BookmarkVector* bookmarks, |
+ favicon_base::FaviconUsageDataList* favicons) { |
+ base::FilePath database_path = FindSpartanDatabase(source_path_); |
+ if (database_path.empty()) |
+ return; |
+ |
+ EdgeDatabaseSession database; |
+ if (!database.OpenDatabase(database_path.value())) { |
+ 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.
|
+ return; |
+ } |
+ |
+ scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = |
+ database.OpenTableEnumerator(L"Favorites"); |
+ if (!enumerator) { |
+ DLOG(ERROR) << "Error opening database table " |
+ << database.GetErrorMessage(); |
+ return; |
+ } |
+ |
+ std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; |
+ base::FilePath favicon_base = |
+ source_path_.empty() |
+ ? importer::GetEdgeDataFilePath().Append(L"DataStore") |
+ : 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.
|
+ |
+ enumerator->Reset(); |
+ 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
|
+ EdgeFavoriteEntry entry; |
+ bool is_deleted = false; |
+ if (!enumerator->RetrieveColumn(L"IsDeleted", is_deleted)) |
+ continue; |
+ if (is_deleted) |
+ continue; |
+ if (!enumerator->RetrieveColumn(L"IsFolder", entry.is_folder)) |
+ continue; |
+ base::string16 url; |
+ if (!enumerator->RetrieveColumn(L"URL", url)) |
+ continue; |
+ entry.url = GURL(url); |
+ if (!entry.is_folder && !entry.url.is_valid()) |
+ continue; |
+ if (!enumerator->RetrieveColumn(L"Title", entry.title)) |
+ continue; |
+ base::string16 favicon_file; |
+ if (!enumerator->RetrieveColumn(L"FaviconFile", favicon_file)) |
+ continue; |
+ if (!favicon_file.empty()) |
+ entry.favicon_file = favicon_base.Append(favicon_file); |
+ if (!enumerator->RetrieveColumn(L"ParentId", entry.parent_id)) |
+ continue; |
+ if (!enumerator->RetrieveColumn(L"ItemId", entry.item_id)) |
+ continue; |
+ if (!enumerator->RetrieveColumn(L"OrderNumber", entry.order_number)) |
+ continue; |
+ FILETIME ft; |
+ if (!enumerator->RetrieveColumn(L"DateUpdated", ft)) |
+ continue; |
+ entry.date_updated = base::Time::FromFileTime(ft); |
+ database_entries[entry.item_id] = entry; |
+ } while (enumerator->Next() && !cancelled()); |
+ |
+ // Build simple tree. |
+ EdgeFavoriteEntry root_entry; |
+ 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.
|
+ auto found_parent = database_entries.find(e.second.parent_id); |
+ if (found_parent == database_entries.end() || |
+ !found_parent->second.is_folder) { |
+ root_entry.children.push_back(&e.second); |
+ } else { |
+ found_parent->second.children.push_back(&e.second); |
+ } |
+ } |
+ std::vector<base::string16> path; |
+ BuildBookmarkEntries(&root_entry, bookmarks, favicons, false, path); |
+} |