Index: chrome/browser/download/download_status_updater_mac.mm |
diff --git a/chrome/browser/download/download_status_updater_mac.mm b/chrome/browser/download/download_status_updater_mac.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8b015284decdbd3c3889209f353c8e6c97a8169b |
--- /dev/null |
+++ b/chrome/browser/download/download_status_updater_mac.mm |
@@ -0,0 +1,267 @@ |
+// Copyright (c) 2012 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/browser/download/download_status_updater.h" |
+ |
+#include "base/mac/foundation_util.h" |
+#include "base/memory/scoped_nsobject.h" |
+#include "base/supports_user_data.h" |
+#include "base/sys_string_conversions.h" |
+#include "content/public/browser/download_item.h" |
+#include "googleurl/src/gurl.h" |
+ |
+// --- Private 10.8 API for showing progress --- |
+// rdar://12058866 http://www.openradar.me/12058866 |
+ |
+namespace { |
+ |
+NSString* const kNSProgressAppBundleIdentifierKey = |
+ @"NSProgressAppBundleIdentifierKey"; |
+NSString* const kNSProgressEstimatedTimeKey = |
+ @"NSProgressEstimatedTimeKey"; |
+NSString* const kNSProgressFileCompletedCountKey = |
+ @"NSProgressFileCompletedCountKey"; |
+NSString* const kNSProgressFileContainerURLKey = |
+ @"NSProgressFileContainerURLKey"; |
+NSString* const kNSProgressFileDownloadingSourceURLKey = |
+ @"NSProgressFileDownloadingSourceURLKey"; |
+NSString* const kNSProgressFileIconKey = |
+ @"NSProgressFileIconKey"; |
+NSString* const kNSProgressFileIconOriginalRectKey = |
+ @"NSProgressFileIconOriginalRectKey"; |
+NSString* const kNSProgressFileLocationCanChangeKey = |
+ @"NSProgressFileLocationCanChangeKey"; |
+NSString* const kNSProgressFileOperationKindAirDropping = |
+ @"NSProgressFileOperationKindAirDropping"; |
+NSString* const kNSProgressFileOperationKindCopying = |
+ @"NSProgressFileOperationKindCopying"; |
+NSString* const kNSProgressFileOperationKindDecompressingAfterDownloading = |
+ @"NSProgressFileOperationKindDecompressingAfterDownloading"; |
+NSString* const kNSProgressFileOperationKindDownloading = |
+ @"NSProgressFileOperationKindDownloading"; |
+NSString* const kNSProgressFileOperationKindEncrypting = |
+ @"NSProgressFileOperationKindEncrypting"; |
+NSString* const kNSProgressFileOperationKindKey = |
+ @"NSProgressFileOperationKindKey"; |
+NSString* const kNSProgressFileTotalCountKey = |
+ @"NSProgressFileTotalCountKey"; |
+NSString* const kNSProgressFileURLKey = |
+ @"NSProgressFileURLKey"; |
+NSString* const kNSProgressIsWaitingKey = |
+ @"NSProgressIsWaitingKey"; |
+NSString* const kNSProgressKindFile = |
+ @"NSProgressKindFile"; |
+NSString* const kNSProgressThroughputKey = |
+ @"NSProgressThroughputKey"; |
+ |
+NSString* ProgressString(NSString* string) { |
+ static NSMutableDictionary* cache; |
+ static CFBundleRef foundation; |
+ if (!cache) { |
+ cache = [[NSMutableDictionary alloc] init]; |
+ foundation = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Foundation")); |
+ } |
+ |
+ NSString* result = [cache objectForKey:string]; |
+ if (!result) { |
+ NSString** ref = static_cast<NSString**>( |
+ CFBundleGetDataPointerForName(foundation, |
+ base::mac::NSToCFCast(string))); |
+ if (ref) { |
+ result = *ref; |
+ [cache setObject:result forKey:string]; |
+ } |
+ } |
+ |
+ return result; |
+} |
+ |
+} // namespace |
+ |
+@interface NSProgress : NSObject |
+ |
+- (id)initWithParent:(id)parent userInfo:(NSDictionary*)info; |
+@property(copy) NSString* kind; |
+ |
+- (void)unpublish; |
+- (void)publish; |
+ |
+- (void)setUserInfoObject:(id)object forKey:(NSString*)key; |
+- (NSDictionary*)userInfo; |
+ |
+@property(readonly) double fractionCompleted; |
+// Set the totalUnitCount to -1 to indicate an indeterminate download. The dock |
+// shows a non-filling progress bar; the Finder is lame and draws its progress |
+// bar off the right side. |
+@property(readonly, getter=isIndeterminate) BOOL indeterminate; |
+@property long long completedUnitCount; |
+@property long long totalUnitCount; |
+ |
+// Pausing appears to be unimplemented in 10.8.0. |
+- (void)pause; |
+@property(readonly, getter=isPaused) BOOL paused; |
+@property(getter=isPausable) BOOL pausable; |
+- (void)setPausingHandler:(id)blockOfUnknownSignature; |
+ |
+- (void)cancel; |
+@property(readonly, getter=isCancelled) BOOL cancelled; |
+@property(getter=isCancellable) BOOL cancellable; |
+// Note that the cancellation handler block will be called on a random thread. |
+- (void)setCancellationHandler:(void (^)())block; |
+ |
+// Allows other applications to provide feedback as to whether the progress is |
+// visible in that app. Note that the acknowledgement handler block will be |
+// called on a random thread. |
+// com.apple.dock => BOOL indicating whether the download target folder was |
+// successfully "flown to" at the beginning of the download. |
+// This primarily depends on whether the download target |
+// folder is in the dock. Note that if the download target |
+// folder is added or removed from the dock during the |
+// duration of the download, it will not trigger a callback. |
+// Note that if the "fly to the dock" keys were not set, the |
+// callback's parameter will always be NO. |
+// com.apple.Finder => always YES, no matter whether the download target |
+// folder's window is open. |
+- (void)handleAcknowledgementByAppWithBundleIdentifier:(NSString*)bundle |
+ usingBlock:(void (^)(BOOL success))block; |
+ |
+@end |
+ |
+// --- Private 10.8 API for showing progress --- |
+ |
+namespace { |
+ |
+bool NSProgressSupported() { |
+ static bool supported; |
+ static bool valid; |
+ if (!valid) { |
+ supported = NSClassFromString(@"NSProgress"); |
+ valid = true; |
+ } |
+ |
+ return supported; |
+} |
+ |
+const char kCrNSProgressUserDataKey[] = "CrNSProgressUserData"; |
+ |
+class CrNSProgressUserData : public base::SupportsUserData::Data { |
+ public: |
+ CrNSProgressUserData(NSProgress* progress, const FilePath& target) |
+ : target_(target) { |
+ progress_.reset(progress); |
+ } |
+ virtual ~CrNSProgressUserData() {} |
+ |
+ NSProgress* progress() const { return progress_.get(); } |
+ FilePath target() const { return target_; } |
+ void setTarget(const FilePath& target) { target_ = target; } |
+ |
+ private: |
+ scoped_nsobject<NSProgress> progress_; |
+ FilePath target_; |
+}; |
+ |
+} // namespace |
+ |
+void DownloadStatusUpdater::UpdateDownloadProgressForItemStarted( |
+ content::DownloadItem* download) { |
+ if (!NSProgressSupported()) |
+ return; |
+ |
+ NSURL* source_url = [NSURL URLWithString: |
+ base::SysUTF8ToNSString(download->GetURL().spec())]; |
+ FilePath destination_path = download->GetFullPath(); |
+ NSURL* destination_url = [NSURL fileURLWithPath: |
+ base::mac::FilePathToNSString(destination_path)]; |
+ |
+ // If there were an image to fly to the download folder in the dock, then |
+ // the keys in the userInfo to set would be: |
+ // - @"NSProgressFlyToImageKey" : NSImage |
+ // - kNSProgressFileIconOriginalRectKey : NSValue of NSRect in global coords |
+ |
+ NSDictionary* user_info = @{ |
+ ProgressString(kNSProgressFileDownloadingSourceURLKey) : source_url, |
+ ProgressString(kNSProgressFileLocationCanChangeKey) : @true, |
+ ProgressString(kNSProgressFileOperationKindKey) : |
+ ProgressString(kNSProgressFileOperationKindDownloading), |
+ ProgressString(kNSProgressFileURLKey) : destination_url |
+ }; |
+ Class progress_class = NSClassFromString(@"NSProgress"); |
+ NSProgress* progress = [progress_class performSelector:@selector(alloc)]; |
+ progress = [progress performSelector:@selector(initWithParent:userInfo:) |
+ withObject:nil |
+ withObject:user_info]; |
+ progress.kind = ProgressString(kNSProgressKindFile); |
+ |
+ progress.pausable = NO; |
+ progress.cancellable = YES; |
+ [progress setCancellationHandler:^{ |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ download->Cancel(true); |
+ }); |
+ }]; |
+ |
+ progress.totalUnitCount = download->GetTotalBytes(); |
+ progress.completedUnitCount = download->GetReceivedBytes(); |
+ |
+ [progress publish]; |
+ |
+ download->SetUserData(&kCrNSProgressUserDataKey, |
+ new CrNSProgressUserData(progress, destination_path)); |
+} |
+ |
+void DownloadStatusUpdater::UpdateDownloadProgressForItemProgressed( |
+ content::DownloadItem* download) { |
+ if (!NSProgressSupported()) |
+ return; |
+ |
+ CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>( |
+ download->GetUserData(&kCrNSProgressUserDataKey)); |
+ if (!progress_data) |
+ return; |
+ |
+ NSProgress* progress = progress_data->progress(); |
+ progress.totalUnitCount = download->GetTotalBytes(); |
+ progress.completedUnitCount = download->GetReceivedBytes(); |
+ |
+ FilePath download_path = download->GetFullPath(); |
+ if (progress_data->target() != download_path) { |
+ progress_data->setTarget(download_path); |
+ NSURL* download_url = [NSURL fileURLWithPath: |
+ base::mac::FilePathToNSString(download_path)]; |
+ [progress setUserInfoObject:download_url |
+ forKey:ProgressString(kNSProgressFileURLKey)]; |
+ } |
+} |
+ |
+void DownloadStatusUpdater::UpdateDownloadProgressForItemCompleted( |
+ content::DownloadItem* download) { |
+ // Bounce the dock icon. |
+ NSString* download_path = |
+ base::mac::FilePathToNSString(download->GetFullPath()); |
+ [[NSDistributedNotificationCenter defaultCenter] |
+ postNotificationName:@"com.apple.DownloadFileFinished" |
+ object:download_path]; |
+ |
+ // Notify the Finder. |
+ NSString* parent_path = [download_path stringByDeletingLastPathComponent]; |
+ FNNotifyByPath( |
+ reinterpret_cast<const UInt8*>([parent_path fileSystemRepresentation]), |
+ kFNDirectoryModifiedMessage, |
+ kNilOptions); |
+ |
+ // Clean up 10.8 NSProgress stuff. |
+ if (!NSProgressSupported()) |
+ return; |
+ |
+ CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>( |
+ download->GetUserData(&kCrNSProgressUserDataKey)); |
+ if (!progress_data) |
+ return; |
+ |
+ NSProgress* progress = progress_data->progress(); |
+ [progress unpublish]; |
+ |
+ download->RemoveUserData(&kCrNSProgressUserDataKey); |
+} |