Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: chrome/browser/importer/safari_importer.mm

Issue 18501013: Move most importer code to chrome/utility/importer (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: another gyp attempt Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 <Cocoa/Cocoa.h>
6
7 #include "chrome/browser/importer/safari_importer.h"
8
9 #include <map>
10 #include <vector>
11
12 #include "base/file_util.h"
13 #include "base/mac/mac_util.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h"
18 #include "chrome/browser/importer/importer_bridge.h"
19 #include "chrome/browser/importer/reencode_favicon.h"
20 #include "chrome/common/importer/imported_bookmark_entry.h"
21 #include "chrome/common/importer/imported_favicon_usage.h"
22 #include "chrome/common/url_constants.h"
23 #include "grit/generated_resources.h"
24 #include "net/base/data_url.h"
25 #include "sql/statement.h"
26 #include "url/gurl.h"
27
28 namespace {
29
30 // A function like this is used by other importers in order to filter out
31 // URLS we don't want to import.
32 // For now it's pretty basic, but I've split it out so it's easy to slot
33 // in necessary logic for filtering URLS, should we need it.
34 bool CanImportSafariURL(const GURL& url) {
35 // The URL is not valid.
36 if (!url.is_valid())
37 return false;
38
39 return true;
40 }
41
42 } // namespace
43
44 SafariImporter::SafariImporter(const base::FilePath& library_dir)
45 : library_dir_(library_dir) {
46 }
47
48 SafariImporter::~SafariImporter() {
49 }
50
51 // static
52 bool SafariImporter::CanImport(const base::FilePath& library_dir,
53 uint16* services_supported) {
54 DCHECK(services_supported);
55 *services_supported = importer::NONE;
56
57 // Import features are toggled by the following:
58 // bookmarks import: existence of ~/Library/Safari/Bookmarks.plist file.
59 // history import: existence of ~/Library/Safari/History.plist file.
60 base::FilePath safari_dir = library_dir.Append("Safari");
61 base::FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist");
62 base::FilePath history_path = safari_dir.Append("History.plist");
63
64 if (base::PathExists(bookmarks_path))
65 *services_supported |= importer::FAVORITES;
66 if (base::PathExists(history_path))
67 *services_supported |= importer::HISTORY;
68
69 return *services_supported != importer::NONE;
70 }
71
72 void SafariImporter::StartImport(const importer::SourceProfile& source_profile,
73 uint16 items,
74 ImporterBridge* bridge) {
75 bridge_ = bridge;
76 // The order here is important!
77 bridge_->NotifyStarted();
78
79 // In keeping with import on other platforms (and for other browsers), we
80 // don't import the home page (since it may lead to a useless homepage); see
81 // crbug.com/25603.
82 if ((items & importer::HISTORY) && !cancelled()) {
83 bridge_->NotifyItemStarted(importer::HISTORY);
84 ImportHistory();
85 bridge_->NotifyItemEnded(importer::HISTORY);
86 }
87 if ((items & importer::FAVORITES) && !cancelled()) {
88 bridge_->NotifyItemStarted(importer::FAVORITES);
89 ImportBookmarks();
90 bridge_->NotifyItemEnded(importer::FAVORITES);
91 }
92 if ((items & importer::PASSWORDS) && !cancelled()) {
93 bridge_->NotifyItemStarted(importer::PASSWORDS);
94 ImportPasswords();
95 bridge_->NotifyItemEnded(importer::PASSWORDS);
96 }
97 bridge_->NotifyEnded();
98 }
99
100 void SafariImporter::ImportBookmarks() {
101 string16 toolbar_name =
102 bridge_->GetLocalizedString(IDS_BOOKMARK_BAR_FOLDER_NAME);
103 std::vector<ImportedBookmarkEntry> bookmarks;
104 ParseBookmarks(toolbar_name, &bookmarks);
105
106 // Write bookmarks into profile.
107 if (!bookmarks.empty() && !cancelled()) {
108 const string16& first_folder_name =
109 bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI);
110 bridge_->AddBookmarks(bookmarks, first_folder_name);
111 }
112
113 // Import favicons.
114 sql::Connection db;
115 if (!OpenDatabase(&db))
116 return;
117
118 FaviconMap favicon_map;
119 ImportFaviconURLs(&db, &favicon_map);
120 // Write favicons into profile.
121 if (!favicon_map.empty() && !cancelled()) {
122 std::vector<ImportedFaviconUsage> favicons;
123 LoadFaviconData(&db, favicon_map, &favicons);
124 bridge_->SetFavicons(favicons);
125 }
126 }
127
128 bool SafariImporter::OpenDatabase(sql::Connection* db) {
129 // Construct ~/Library/Safari/WebIcons.db path.
130 NSString* library_dir = [NSString
131 stringWithUTF8String:library_dir_.value().c_str()];
132 NSString* safari_dir = [library_dir
133 stringByAppendingPathComponent:@"Safari"];
134 NSString* favicons_db_path = [safari_dir
135 stringByAppendingPathComponent:@"WebpageIcons.db"];
136
137 const char* db_path = [favicons_db_path fileSystemRepresentation];
138 return db->Open(base::FilePath(db_path));
139 }
140
141 void SafariImporter::ImportFaviconURLs(sql::Connection* db,
142 FaviconMap* favicon_map) {
143 const char* query = "SELECT iconID, url FROM PageURL;";
144 sql::Statement s(db->GetUniqueStatement(query));
145
146 while (s.Step() && !cancelled()) {
147 int64 icon_id = s.ColumnInt64(0);
148 GURL url = GURL(s.ColumnString(1));
149 (*favicon_map)[icon_id].insert(url);
150 }
151 }
152
153 void SafariImporter::LoadFaviconData(
154 sql::Connection* db,
155 const FaviconMap& favicon_map,
156 std::vector<ImportedFaviconUsage>* favicons) {
157 const char* query = "SELECT i.url, d.data "
158 "FROM IconInfo i JOIN IconData d "
159 "ON i.iconID = d.iconID "
160 "WHERE i.iconID = ?;";
161 sql::Statement s(db->GetUniqueStatement(query));
162
163 for (FaviconMap::const_iterator i = favicon_map.begin();
164 i != favicon_map.end(); ++i) {
165 s.Reset(true);
166 s.BindInt64(0, i->first);
167 if (s.Step()) {
168 ImportedFaviconUsage usage;
169
170 usage.favicon_url = GURL(s.ColumnString(0));
171 if (!usage.favicon_url.is_valid())
172 continue; // Don't bother importing favicons with invalid URLs.
173
174 std::vector<unsigned char> data;
175 s.ColumnBlobAsVector(1, &data);
176 if (data.empty())
177 continue; // Data definitely invalid.
178
179 if (!ReencodeFavicon(&data[0], data.size(), &usage.png_data))
180 continue; // Unable to decode.
181
182 usage.urls = i->second;
183 favicons->push_back(usage);
184 }
185 }
186 }
187
188 void SafariImporter::RecursiveReadBookmarksFolder(
189 NSDictionary* bookmark_folder,
190 const std::vector<string16>& parent_path_elements,
191 bool is_in_toolbar,
192 const string16& toolbar_name,
193 std::vector<ImportedBookmarkEntry>* out_bookmarks) {
194 DCHECK(bookmark_folder);
195
196 NSString* type = [bookmark_folder objectForKey:@"WebBookmarkType"];
197 NSString* title = [bookmark_folder objectForKey:@"Title"];
198
199 // Are we the dictionary that contains all other bookmarks?
200 // We need to know this so we don't add it to the path.
201 bool is_top_level_bookmarks_container = [bookmark_folder
202 objectForKey:@"WebBookmarkFileVersion"] != nil;
203
204 // We're expecting a list of bookmarks here, if that isn't what we got, fail.
205 if (!is_top_level_bookmarks_container) {
206 // Top level containers sometimes don't have title attributes.
207 if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) {
208 NOTREACHED() << "Type=("
209 << (type ? base::SysNSStringToUTF8(type) : "Null type")
210 << ") Title=("
211 << (title ? base::SysNSStringToUTF8(title) : "Null title")
212 << ")";
213 return;
214 }
215 }
216
217 NSArray* elements = [bookmark_folder objectForKey:@"Children"];
218 if (!elements && (!parent_path_elements.empty() || !is_in_toolbar)) {
219 // This is an empty folder, so add it explicitly; but don't add the toolbar
220 // folder if it is empty. Note that all non-empty folders are added
221 // implicitly when their children are added.
222 ImportedBookmarkEntry entry;
223 // Safari doesn't specify a creation time for the folder.
224 entry.creation_time = base::Time::Now();
225 entry.title = base::SysNSStringToUTF16(title);
226 entry.path = parent_path_elements;
227 entry.in_toolbar = is_in_toolbar;
228 entry.is_folder = true;
229
230 out_bookmarks->push_back(entry);
231 return;
232 }
233
234 std::vector<string16> path_elements(parent_path_elements);
235 // Create a folder for the toolbar, but not for the bookmarks menu.
236 if (path_elements.empty() && [title isEqualToString:@"BookmarksBar"]) {
237 is_in_toolbar = true;
238 path_elements.push_back(toolbar_name);
239 } else if (!is_top_level_bookmarks_container &&
240 !(path_elements.empty() &&
241 [title isEqualToString:@"BookmarksMenu"])) {
242 if (title)
243 path_elements.push_back(base::SysNSStringToUTF16(title));
244 }
245
246 // Iterate over individual bookmarks.
247 for (NSDictionary* bookmark in elements) {
248 NSString* type = [bookmark objectForKey:@"WebBookmarkType"];
249 if (!type)
250 continue;
251
252 // If this is a folder, recurse.
253 if ([type isEqualToString:@"WebBookmarkTypeList"]) {
254 RecursiveReadBookmarksFolder(bookmark,
255 path_elements,
256 is_in_toolbar,
257 toolbar_name,
258 out_bookmarks);
259 }
260
261 // If we didn't see a bookmark folder, then we're expecting a bookmark
262 // item. If that's not what we got then ignore it.
263 if (![type isEqualToString:@"WebBookmarkTypeLeaf"])
264 continue;
265
266 NSString* url = [bookmark objectForKey:@"URLString"];
267 NSString* title = [[bookmark objectForKey:@"URIDictionary"]
268 objectForKey:@"title"];
269
270 if (!url || !title)
271 continue;
272
273 // Output Bookmark.
274 ImportedBookmarkEntry entry;
275 // Safari doesn't specify a creation time for the bookmark.
276 entry.creation_time = base::Time::Now();
277 entry.title = base::SysNSStringToUTF16(title);
278 entry.url = GURL(base::SysNSStringToUTF8(url));
279 entry.path = path_elements;
280 entry.in_toolbar = is_in_toolbar;
281
282 out_bookmarks->push_back(entry);
283 }
284 }
285
286 void SafariImporter::ParseBookmarks(
287 const string16& toolbar_name,
288 std::vector<ImportedBookmarkEntry>* bookmarks) {
289 DCHECK(bookmarks);
290
291 // Construct ~/Library/Safari/Bookmarks.plist path
292 NSString* library_dir = [NSString
293 stringWithUTF8String:library_dir_.value().c_str()];
294 NSString* safari_dir = [library_dir
295 stringByAppendingPathComponent:@"Safari"];
296 NSString* bookmarks_plist = [safari_dir
297 stringByAppendingPathComponent:@"Bookmarks.plist"];
298
299 // Load the plist file.
300 NSDictionary* bookmarks_dict = [NSDictionary
301 dictionaryWithContentsOfFile:bookmarks_plist];
302 if (!bookmarks_dict)
303 return;
304
305 // Recursively read in bookmarks.
306 std::vector<string16> parent_path_elements;
307 RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false,
308 toolbar_name, bookmarks);
309 }
310
311 void SafariImporter::ImportPasswords() {
312 // Safari stores it's passwords in the Keychain, same as us so we don't need
313 // to import them.
314 // Note: that we don't automatically pick them up, there is some logic around
315 // the user needing to explicitly input his username in a page and blurring
316 // the field before we pick it up, but the details of that are beyond the
317 // scope of this comment.
318 }
319
320 void SafariImporter::ImportHistory() {
321 std::vector<ImporterURLRow> rows;
322 ParseHistoryItems(&rows);
323
324 if (!rows.empty() && !cancelled()) {
325 bridge_->SetHistoryItems(rows, importer::VISIT_SOURCE_SAFARI_IMPORTED);
326 }
327 }
328
329 double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) {
330 DCHECK(history_time);
331 // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds.
332 // Unix epoch is 1970-01-01 00:00:00.0 UTC,
333 // CF epoch is 2001-01-01 00:00:00.0 UTC.
334 return CFStringGetDoubleValue(base::mac::NSToCFCast(history_time)) +
335 kCFAbsoluteTimeIntervalSince1970;
336 }
337
338 void SafariImporter::ParseHistoryItems(
339 std::vector<ImporterURLRow>* history_items) {
340 DCHECK(history_items);
341
342 // Construct ~/Library/Safari/History.plist path
343 NSString* library_dir = [NSString
344 stringWithUTF8String:library_dir_.value().c_str()];
345 NSString* safari_dir = [library_dir
346 stringByAppendingPathComponent:@"Safari"];
347 NSString* history_plist = [safari_dir
348 stringByAppendingPathComponent:@"History.plist"];
349
350 // Load the plist file.
351 NSDictionary* history_dict = [NSDictionary
352 dictionaryWithContentsOfFile:history_plist];
353 if (!history_dict)
354 return;
355
356 NSArray* safari_history_items = [history_dict
357 objectForKey:@"WebHistoryDates"];
358
359 for (NSDictionary* history_item in safari_history_items) {
360 NSString* url_ns = [history_item objectForKey:@""];
361 if (!url_ns)
362 continue;
363
364 GURL url(base::SysNSStringToUTF8(url_ns));
365
366 if (!CanImportSafariURL(url))
367 continue;
368
369 ImporterURLRow row(url);
370 NSString* title_ns = [history_item objectForKey:@"title"];
371
372 // Sometimes items don't have a title, in which case we just substitue
373 // the url.
374 if (!title_ns)
375 title_ns = url_ns;
376
377 row.title = base::SysNSStringToUTF16(title_ns);
378 int visit_count = [[history_item objectForKey:@"visitCount"]
379 intValue];
380 row.visit_count = visit_count;
381 // Include imported URLs in autocompletion - don't hide them.
382 row.hidden = 0;
383 // Item was never typed before in the omnibox.
384 row.typed_count = 0;
385
386 NSString* last_visit_str = [history_item objectForKey:@"lastVisitedDate"];
387 // The last visit time should always be in the history item, but if not
388 /// just continue without this item.
389 DCHECK(last_visit_str);
390 if (!last_visit_str)
391 continue;
392
393 // Convert Safari's last visit time to Unix Epoch time.
394 double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str);
395 row.last_visit = base::Time::FromDoubleT(seconds_since_unix_epoch);
396
397 history_items->push_back(row);
398 }
399 }
OLDNEW
« no previous file with comments | « chrome/browser/importer/safari_importer.h ('k') | chrome/browser/importer/safari_importer_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698