Index: chrome/browser/importer/safari_importer.mm |
diff --git a/chrome/browser/importer/safari_importer.mm b/chrome/browser/importer/safari_importer.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b97bef4b87cef1a075497a113dba63cc615c2859 |
--- /dev/null |
+++ b/chrome/browser/importer/safari_importer.mm |
@@ -0,0 +1,222 @@ |
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h> |
+ |
+#include "chrome/browser/importer/safari_importer.h" |
+ |
+#include <vector> |
+ |
+#include "base/message_loop.h" |
+#include "base/string16.h" |
+#include "base/sys_string_conversions.h" |
+#include "base/time.h" |
+#include "chrome/common/url_constants.h" |
+#include "googleurl/src/gurl.h" |
+#include "grit/generated_resources.h" |
+#include "net/base/data_url.h" |
+ |
+namespace { |
+ |
+// A function like this is used by other importers in order to filter out |
+// URLS we don't want to import. |
+// For now it's pretty basic, but I've split it out so it's easy to slot |
+// in necessary logic for filtering URLS, should we need it. |
+bool CanImportSafariURL(const GURL& url) { |
+ // The URL is not valid. |
+ if (!url.is_valid()) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+SafariImporter::SafariImporter(const FilePath& library_dir) |
+ : writer_(NULL), library_dir_(library_dir) { |
+} |
+ |
+SafariImporter::~SafariImporter() { |
+} |
+ |
+void SafariImporter::StartImport(ProfileInfo profile_info, |
+ uint16 items, ProfileWriter* writer, |
+ MessageLoop* delegate_loop, |
+ ImporterHost* host) { |
+ writer_ = writer; |
+ importer_host_ = host; |
+ |
+ // The order here is important! |
+ NotifyStarted(); |
+ if ((items & HOME_PAGE) && !cancelled()) |
+ ImportHomepage(); // Doesn't have a UI item. |
+ if ((items & FAVORITES) && !cancelled()) { |
+ NotifyItemStarted(FAVORITES); |
+ ImportBookmarks(); |
+ NotifyItemEnded(FAVORITES); |
+ } |
+ if ((items & SEARCH_ENGINES) && !cancelled()) { |
+ NotifyItemStarted(SEARCH_ENGINES); |
+ ImportSearchEngines(); |
+ NotifyItemEnded(SEARCH_ENGINES); |
+ } |
+ if ((items & PASSWORDS) && !cancelled()) { |
+ NotifyItemStarted(PASSWORDS); |
+ ImportPasswords(); |
+ NotifyItemEnded(PASSWORDS); |
+ } |
+ if ((items & HISTORY) && !cancelled()) { |
+ NotifyItemStarted(HISTORY); |
+ ImportHistory(); |
+ NotifyItemEnded(HISTORY); |
+ } |
+ NotifyEnded(); |
+} |
+ |
+void SafariImporter::ImportBookmarks() { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void SafariImporter::ImportSearchEngines() { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void SafariImporter::ImportPasswords() { |
+ // Safari stores it's passwords in the Keychain, same as us so we don't need |
+ // to import them. |
+ // Note: that we don't automatically pick them up, there is some logic around |
+ // the user needing to explicitly input his username in a page and bluring |
+ // the field before we pick it up, but the details of that are beyond the |
+ // scope of this comment. |
+} |
+ |
+void SafariImporter::ImportHistory() { |
+ std::vector<history::URLRow> rows; |
+ ParseHistoryItems(&rows); |
+ |
+ if (!rows.empty() && !cancelled()) { |
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, |
+ &ProfileWriter::AddHistoryPage, rows)); |
+ } |
+} |
+ |
+double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) { |
+ DCHECK(history_time); |
+ // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds. |
+ // Unix epoch is 1970-01-01 00:00:00.0 UTC, |
+ // CF epoch is 2001-01-01 00:00:00.0 UTC. |
+ return CFStringGetDoubleValue(reinterpret_cast<CFStringRef>(history_time)) + |
+ kCFAbsoluteTimeIntervalSince1970; |
+} |
+ |
+void SafariImporter::ParseHistoryItems( |
+ std::vector<history::URLRow>* history_items) { |
+ DCHECK(history_items); |
+ |
+ // Construct ~/Library/Safari/History.plist path |
+ NSString* library_dir = [NSString |
+ stringWithUTF8String:library_dir_.value().c_str()]; |
+ NSString* safari_dir = [library_dir |
+ stringByAppendingPathComponent:@"Safari"]; |
+ NSString* history_plist = [safari_dir |
+ stringByAppendingPathComponent:@"History.plist"]; |
+ |
+ // Load the plist file. |
+ NSDictionary* history_dict = [NSDictionary |
+ dictionaryWithContentsOfFile:history_plist]; |
+ |
+ NSArray* safari_history_items = [history_dict valueForKey:@"WebHistoryDates"]; |
+ |
+ for (NSDictionary* history_item in safari_history_items) { |
+ using base::SysNSStringToUTF8; |
+ using base::SysNSStringToWide; |
+ NSString* url_ns = [history_item valueForKey:@""]; |
+ if (!url_ns) |
+ continue; |
+ |
+ GURL url(SysNSStringToUTF8(url_ns)); |
+ |
+ if (!CanImportSafariURL(url)) |
+ continue; |
+ |
+ history::URLRow row(url); |
+ NSString* title_ns = [history_item valueForKey:@"title"]; |
+ |
+ // Sometimes items don't have a title, in which case we just substitue |
+ // the url. |
+ if (!title_ns) |
+ title_ns = url_ns; |
+ |
+ row.set_title(SysNSStringToWide(title_ns)); |
+ int visit_count = [[history_item valueForKey:@"visitCount"] |
+ intValue]; |
+ row.set_visit_count(visit_count); |
+ // Include imported URLs in autocompletion - don't hide them. |
+ row.set_hidden(0); |
+ // Item was never typed before in the omnibox. |
+ row.set_typed_count(0); |
+ |
+ NSString* last_visit_str = [history_item valueForKey:@"lastVisitedDate"]; |
+ // The last visit time should always be in the history item, but if not |
+ /// just continue without this item. |
+ DCHECK(last_visit_str); |
+ if (!last_visit_str) |
+ continue; |
+ |
+ // Convert Safari's last visit time to Unix Epoch time. |
+ double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str); |
+ row.set_last_visit(base::Time::FromDoubleT(seconds_since_unix_epoch)); |
+ |
+ history_items->push_back(row); |
+ } |
+} |
+ |
+void SafariImporter::ImportHomepage() { |
+ const NSString* homepage_ns = |
+ reinterpret_cast<const NSString*>( |
+ CFPreferencesCopyAppValue(CFSTR("HomePage"), |
+ CFSTR("com.apple.Safari"))); |
stuartmorgan
2009/07/31 19:57:01
This needs releasing; Copy gives you an owned refe
|
+ if (!homepage_ns) |
+ return; |
+ |
+ string16 hompeage_str = base::SysNSStringToUTF16(homepage_ns); |
+ GURL homepage(hompeage_str); |
+ if (homepage.is_valid()) { |
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, |
+ &ProfileWriter::AddHomepage, homepage)); |
+ } |
+} |
+ |
+// TODO(jeremy): This is temporary, just copied from the FF import code in case |
+// we need it, clean this up when writing favicon import code. |
+// static |
+void SafariImporter::DataURLToFaviconUsage( |
+ const GURL& link_url, |
+ const GURL& favicon_data, |
+ std::vector<history::ImportedFavIconUsage>* favicons) { |
+ if (!link_url.is_valid() || !favicon_data.is_valid() || |
+ !favicon_data.SchemeIs(chrome::kDataScheme)) |
+ return; |
+ |
+ // Parse the data URL. |
+ std::string mime_type, char_set, data; |
+ if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) || |
+ data.empty()) |
+ return; |
+ |
+ history::ImportedFavIconUsage usage; |
+ if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]), |
+ data.size(), &usage.png_data)) |
+ return; // Unable to decode. |
+ |
+ // We need to make up a URL for the favicon. We use a version of the page's |
+ // URL so that we can be sure it will not collide. |
+ usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec()); |
+ |
+ // We only have one URL per favicon for Firefox 2 bookmarks. |
+ usage.urls.insert(link_url); |
+ |
+ favicons->push_back(usage); |
+} |
+ |