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 #include <Shlobj.h> | |
8 | |
9 #include <algorithm> | |
10 #include <map> | |
11 #include <string> | |
12 #include <tuple> | |
13 #include <vector> | |
14 | |
15 #include "base/files/file_enumerator.h" | |
16 #include "base/files/file_path.h" | |
17 #include "base/files/file_util.h" | |
18 #include "base/memory/ref_counted.h" | |
19 #include "base/memory/scoped_ptr.h" | |
20 #include "base/strings/string16.h" | |
21 #include "base/strings/string_util.h" | |
22 #include "base/time/time.h" | |
23 #include "base/win/windows_version.h" | |
24 #include "chrome/common/importer/edge_importer_utils_win.h" | |
25 #include "chrome/common/importer/imported_bookmark_entry.h" | |
26 #include "chrome/common/importer/importer_bridge.h" | |
27 #include "chrome/grit/generated_resources.h" | |
28 #include "chrome/utility/importer/edge_database_reader_win.h" | |
29 #include "chrome/utility/importer/favicon_reencode.h" | |
30 #include "ui/base/l10n/l10n_util.h" | |
31 #include "url/gurl.h" | |
32 | |
33 namespace { | |
34 | |
35 // Toolbar favorites are placed under this special folder name. | |
36 const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_"; | |
37 const base::char16 kSpartanDatabaseFile[] = L"spartan.edb"; | |
38 | |
39 struct EdgeFavoriteEntry { | |
40 EdgeFavoriteEntry() | |
41 : is_folder(false), | |
42 order_number(0), | |
43 item_id(GUID_NULL), | |
44 parent_id(GUID_NULL) {} | |
45 | |
46 base::string16 title; | |
47 GURL url; | |
48 base::FilePath favicon_file; | |
49 bool is_folder; | |
50 int64_t order_number; | |
51 base::Time date_updated; | |
52 GUID item_id; | |
53 GUID parent_id; | |
54 | |
55 std::vector<const EdgeFavoriteEntry*> children; | |
56 | |
57 ImportedBookmarkEntry ToBookmarkEntry( | |
58 bool in_toolbar, | |
59 const std::vector<base::string16>& path) const { | |
60 ImportedBookmarkEntry entry; | |
61 entry.in_toolbar = in_toolbar; | |
62 entry.is_folder = is_folder; | |
63 entry.url = url; | |
64 entry.path = path; | |
65 entry.title = title; | |
66 entry.creation_time = date_updated; | |
67 return entry; | |
68 } | |
69 }; | |
70 | |
71 struct EdgeFavoriteEntryComparator { | |
72 bool operator()(const EdgeFavoriteEntry* lhs, | |
73 const EdgeFavoriteEntry* rhs) const { | |
74 return std::tie(lhs->order_number, lhs->title) < | |
75 std::tie(rhs->order_number, rhs->title); | |
76 } | |
77 }; | |
78 | |
79 // The name of the database file is spartan.edb, however it isn't clear how | |
80 // the intermediate path between the DataStore and the database is generated. | |
81 // Therefore we just do a simple recursive search until we find a matching name. | |
82 base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) { | |
83 base::FilePath data_path = | |
84 profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path; | |
85 if (data_path.empty()) | |
86 return base::FilePath(); | |
87 | |
88 base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true, | |
89 base::FileEnumerator::FILES); | |
90 base::FilePath path = enumerator.Next(); | |
91 while (!path.empty()) { | |
92 if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(), | |
93 kSpartanDatabaseFile)) | |
94 return path; | |
95 path = enumerator.Next(); | |
96 } | |
97 return base::FilePath(); | |
98 } | |
99 | |
100 struct GuidComparator { | |
101 bool operator()(const GUID& a, const GUID& b) const { | |
102 return memcmp(&a, &b, sizeof(a)) < 0; | |
103 } | |
104 }; | |
105 | |
106 bool ReadFaviconData(const base::FilePath& file, | |
107 std::vector<unsigned char>* data) { | |
108 std::string image_data; | |
109 if (!base::ReadFileToString(file, &image_data)) | |
110 return false; | |
111 | |
112 const unsigned char* ptr = | |
113 reinterpret_cast<const unsigned char*>(image_data.c_str()); | |
114 return importer::ReencodeFavicon(ptr, image_data.size(), data); | |
115 } | |
116 | |
117 void BuildBookmarkEntries(const EdgeFavoriteEntry& current_entry, | |
118 bool is_toolbar, | |
119 std::vector<ImportedBookmarkEntry>* bookmarks, | |
120 favicon_base::FaviconUsageDataList* favicons, | |
121 std::vector<base::string16>* path) { | |
122 for (const EdgeFavoriteEntry* entry : current_entry.children) { | |
123 if (entry->is_folder) { | |
124 // If the favorites bar then load all children as toolbar items. | |
125 if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) { | |
126 // Replace name with Links similar to IE. | |
127 path->push_back(L"Links"); | |
128 BuildBookmarkEntries(*entry, true, bookmarks, favicons, path); | |
129 path->pop_back(); | |
130 } else { | |
131 path->push_back(entry->title); | |
132 BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, path); | |
133 path->pop_back(); | |
134 } | |
135 } else { | |
136 bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path)); | |
137 favicon_base::FaviconUsageData favicon; | |
138 if (entry->url.is_valid() && !entry->favicon_file.empty() && | |
139 ReadFaviconData(entry->favicon_file, &favicon.png_data)) { | |
140 // As the database doesn't provide us a favicon URL we'll fake one. | |
141 GURL::Replacements path_replace; | |
142 path_replace.SetPathStr("/favicon.ico"); | |
143 favicon.favicon_url = | |
144 entry->url.GetWithEmptyPath().ReplaceComponents(path_replace); | |
145 favicon.urls.insert(entry->url); | |
146 favicons->push_back(favicon); | |
147 } | |
148 } | |
149 } | |
150 } | |
151 | |
152 } // namespace | |
153 | |
154 EdgeImporter::EdgeImporter() {} | |
155 | |
156 void EdgeImporter::StartImport(const importer::SourceProfile& source_profile, | |
157 uint16 items, | |
158 ImporterBridge* bridge) { | |
159 bridge_ = bridge; | |
160 bridge_->NotifyStarted(); | |
161 source_path_ = source_profile.source_path; | |
162 | |
163 if ((items & importer::FAVORITES) && !cancelled()) { | |
164 bridge_->NotifyItemStarted(importer::FAVORITES); | |
165 ImportFavorites(); | |
166 bridge_->NotifyItemEnded(importer::FAVORITES); | |
167 } | |
168 bridge_->NotifyEnded(); | |
169 } | |
170 | |
171 EdgeImporter::~EdgeImporter() {} | |
172 | |
173 void EdgeImporter::ImportFavorites() { | |
174 std::vector<ImportedBookmarkEntry> bookmarks; | |
175 favicon_base::FaviconUsageDataList favicons; | |
176 ParseFavoritesDatabase(&bookmarks, &favicons); | |
177 | |
178 if (!bookmarks.empty() && !cancelled()) { | |
179 const base::string16& first_folder_name = | |
180 l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE); | |
181 bridge_->AddBookmarks(bookmarks, first_folder_name); | |
182 } | |
183 if (!favicons.empty() && !cancelled()) | |
184 bridge_->SetFavicons(favicons); | |
185 } | |
186 | |
187 // From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET | |
188 // database within the Edge local storage. The import uses the ESE library to | |
189 // open and read the data file. The data is stored in a Favorites table with | |
190 // the following schema. | |
191 // Column Name Column Type | |
192 // ------------------------------------------ | |
193 // DateUpdated LongLong - FILETIME | |
194 // FaviconFile LongText - Relative path | |
195 // HashedUrl ULong | |
196 // IsDeleted Bit | |
197 // IsFolder Bit | |
198 // ItemId Guid | |
199 // OrderNumber LongLong | |
200 // ParentId Guid | |
201 // RoamDisabled Bit | |
202 // RowId Long | |
203 // Title LongText | |
204 // URL LongText | |
205 void EdgeImporter::ParseFavoritesDatabase( | |
206 std::vector<ImportedBookmarkEntry>* bookmarks, | |
207 favicon_base::FaviconUsageDataList* favicons) { | |
208 base::FilePath database_path = FindSpartanDatabase(source_path_); | |
209 if (database_path.empty()) | |
210 return; | |
211 | |
212 EdgeDatabaseReader database; | |
213 if (!database.OpenDatabase(database_path.value())) { | |
214 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
| |
215 return; | |
216 } | |
217 | |
218 scoped_ptr<EdgeDatabaseTableEnumerator> enumerator = | |
219 database.OpenTableEnumerator(L"Favorites"); | |
220 if (!enumerator) { | |
221 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
| |
222 return; | |
223 } | |
224 | |
225 if (!enumerator->Reset()) | |
226 return; | |
227 | |
228 std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries; | |
229 base::FilePath favicon_base = | |
230 source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_; | |
231 favicon_base = favicon_base.Append(L"DataStore"); | |
232 | |
233 do { | |
234 EdgeFavoriteEntry entry; | |
235 bool is_deleted = false; | |
236 if (!enumerator->RetrieveColumn(L"IsDeleted", &is_deleted)) | |
237 continue; | |
238 if (is_deleted) | |
239 continue; | |
240 if (!enumerator->RetrieveColumn(L"IsFolder", &entry.is_folder)) | |
241 continue; | |
242 base::string16 url; | |
243 if (!enumerator->RetrieveColumn(L"URL", &url)) | |
244 continue; | |
245 entry.url = GURL(url); | |
246 if (!entry.is_folder && !entry.url.is_valid()) | |
247 continue; | |
248 if (!enumerator->RetrieveColumn(L"Title", &entry.title)) | |
249 continue; | |
250 base::string16 favicon_file; | |
251 if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file)) | |
252 continue; | |
253 if (!favicon_file.empty()) | |
254 entry.favicon_file = favicon_base.Append(favicon_file); | |
255 if (!enumerator->RetrieveColumn(L"ParentId", &entry.parent_id)) | |
256 continue; | |
257 if (!enumerator->RetrieveColumn(L"ItemId", &entry.item_id)) | |
258 continue; | |
259 if (!enumerator->RetrieveColumn(L"OrderNumber", &entry.order_number)) | |
260 continue; | |
261 FILETIME data_updated; | |
262 if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated)) | |
263 continue; | |
264 entry.date_updated = base::Time::FromFileTime(data_updated); | |
265 database_entries[entry.item_id] = entry; | |
266 } while (enumerator->Next() && !cancelled()); | |
267 | |
268 // Build simple tree. | |
269 EdgeFavoriteEntry root_entry; | |
270 for (auto& entry : database_entries) { | |
271 auto found_parent = database_entries.find(entry.second.parent_id); | |
272 if (found_parent == database_entries.end() || | |
273 !found_parent->second.is_folder) { | |
274 root_entry.children.push_back(&entry.second); | |
275 } else { | |
276 found_parent->second.children.push_back(&entry.second); | |
277 } | |
278 } | |
279 // With tree built sort the children of each node including the root. | |
280 std::sort(root_entry.children.begin(), root_entry.children.end(), | |
281 EdgeFavoriteEntryComparator()); | |
282 for (auto& entry : database_entries) { | |
283 std::sort(entry.second.children.begin(), entry.second.children.end(), | |
284 EdgeFavoriteEntryComparator()); | |
285 } | |
286 std::vector<base::string16> path; | |
287 BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path); | |
288 } | |
OLD | NEW |