Chromium Code Reviews| 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..b84f06bd6f1be1e9ebc25f9ae90734787e2be156 |
| --- /dev/null |
| +++ b/chrome/utility/importer/edge_importer_win.cc |
| @@ -0,0 +1,288 @@ |
| +// 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" |
| + |
| +#include <Shlobj.h> |
| + |
| +#include <algorithm> |
| +#include <map> |
| +#include <string> |
| +#include <tuple> |
| +#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" |
| +#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/edge_database_reader_win.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"; |
| + |
| +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<const EdgeFavoriteEntry*> children; |
| + |
| + ImportedBookmarkEntry ToBookmarkEntry( |
| + bool in_toolbar, |
| + const std::vector<base::string16>& path) const { |
| + 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 { |
| + return std::tie(lhs->order_number, lhs->title) < |
| + std::tie(rhs->order_number, rhs->title); |
| + } |
| +}; |
| + |
| +// 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(const base::FilePath& profile_path) { |
| + base::FilePath data_path = |
| + profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path; |
| + 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(const EdgeFavoriteEntry& current_entry, |
| + bool is_toolbar, |
| + std::vector<ImportedBookmarkEntry>* bookmarks, |
| + favicon_base::FaviconUsageDataList* favicons, |
| + std::vector<base::string16>* path) { |
| + for (const 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, true, bookmarks, favicons, path); |
| + path->pop_back(); |
| + } else { |
| + path->push_back(entry->title); |
| + BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, 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() { |
| + std::vector<ImportedBookmarkEntry> 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 |
| +// 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( |
| + std::vector<ImportedBookmarkEntry>* bookmarks, |
| + favicon_base::FaviconUsageDataList* favicons) { |
| + base::FilePath database_path = FindSpartanDatabase(source_path_); |
| + if (database_path.empty()) |
| + return; |
| + |
| + EdgeDatabaseReader database; |
| + if (!database.OpenDatabase(database_path.value())) { |
| + DVLOG(1) << "Error opening database " << database.GetErrorMessage(); |
|
ananta
2015/12/02 21:33:11
Should this be a regular log error or a check perh
forshaw
2015/12/02 21:56:11
I did have this originally as a normal LOG but was
Ilya Sherman
2015/12/02 21:59:32
FWIW, you previously had DLOG, which isn't going t
|
| + return; |
| + } |
| + |
| + scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = |
| + database.OpenTableEnumerator(L"Favorites"); |
| + if (!enumerator) { |
| + DVLOG(1) << "Error opening database table " << database.GetErrorMessage(); |
|
ananta
2015/12/02 21:33:11
Ditto for replacing this with a log error and or a
|
| + return; |
| + } |
| + |
| + if (!enumerator->Reset()) |
| + return; |
| + |
| + std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; |
| + base::FilePath favicon_base = |
| + source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_; |
| + favicon_base = favicon_base.Append(L"DataStore"); |
| + |
| + do { |
| + 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 data_updated; |
| + if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated)) |
| + continue; |
| + entry.date_updated = base::Time::FromFileTime(data_updated); |
| + database_entries[entry.item_id] = entry; |
| + } while (enumerator->Next() && !cancelled()); |
| + |
| + // Build simple tree. |
| + EdgeFavoriteEntry root_entry; |
| + for (auto& entry : database_entries) { |
| + auto found_parent = database_entries.find(entry.second.parent_id); |
| + if (found_parent == database_entries.end() || |
| + !found_parent->second.is_folder) { |
| + root_entry.children.push_back(&entry.second); |
| + } else { |
| + found_parent->second.children.push_back(&entry.second); |
| + } |
| + } |
| + // With tree built sort the children of each node including the root. |
| + std::sort(root_entry.children.begin(), root_entry.children.end(), |
| + EdgeFavoriteEntryComparator()); |
| + for (auto& entry : database_entries) { |
| + std::sort(entry.second.children.begin(), entry.second.children.end(), |
| + EdgeFavoriteEntryComparator()); |
| + } |
| + std::vector<base::string16> path; |
| + BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path); |
| +} |