OLD | NEW |
(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 "chrome/browser/download/download_status_updater.h" |
| 6 |
| 7 #include "base/mac/foundation_util.h" |
| 8 #include "base/memory/scoped_nsobject.h" |
| 9 #include "base/supports_user_data.h" |
| 10 #include "base/sys_string_conversions.h" |
| 11 #include "content/public/browser/download_item.h" |
| 12 #include "googleurl/src/gurl.h" |
| 13 |
| 14 // --- Private 10.8 API for showing progress --- |
| 15 // rdar://12058866 http://www.openradar.me/12058866 |
| 16 |
| 17 namespace { |
| 18 |
| 19 NSString* const kNSProgressAppBundleIdentifierKey = |
| 20 @"NSProgressAppBundleIdentifierKey"; |
| 21 NSString* const kNSProgressEstimatedTimeKey = |
| 22 @"NSProgressEstimatedTimeKey"; |
| 23 NSString* const kNSProgressFileCompletedCountKey = |
| 24 @"NSProgressFileCompletedCountKey"; |
| 25 NSString* const kNSProgressFileContainerURLKey = |
| 26 @"NSProgressFileContainerURLKey"; |
| 27 NSString* const kNSProgressFileDownloadingSourceURLKey = |
| 28 @"NSProgressFileDownloadingSourceURLKey"; |
| 29 NSString* const kNSProgressFileIconKey = |
| 30 @"NSProgressFileIconKey"; |
| 31 NSString* const kNSProgressFileIconOriginalRectKey = |
| 32 @"NSProgressFileIconOriginalRectKey"; |
| 33 NSString* const kNSProgressFileLocationCanChangeKey = |
| 34 @"NSProgressFileLocationCanChangeKey"; |
| 35 NSString* const kNSProgressFileOperationKindAirDropping = |
| 36 @"NSProgressFileOperationKindAirDropping"; |
| 37 NSString* const kNSProgressFileOperationKindCopying = |
| 38 @"NSProgressFileOperationKindCopying"; |
| 39 NSString* const kNSProgressFileOperationKindDecompressingAfterDownloading = |
| 40 @"NSProgressFileOperationKindDecompressingAfterDownloading"; |
| 41 NSString* const kNSProgressFileOperationKindDownloading = |
| 42 @"NSProgressFileOperationKindDownloading"; |
| 43 NSString* const kNSProgressFileOperationKindEncrypting = |
| 44 @"NSProgressFileOperationKindEncrypting"; |
| 45 NSString* const kNSProgressFileOperationKindKey = |
| 46 @"NSProgressFileOperationKindKey"; |
| 47 NSString* const kNSProgressFileTotalCountKey = |
| 48 @"NSProgressFileTotalCountKey"; |
| 49 NSString* const kNSProgressFileURLKey = |
| 50 @"NSProgressFileURLKey"; |
| 51 NSString* const kNSProgressIsWaitingKey = |
| 52 @"NSProgressIsWaitingKey"; |
| 53 NSString* const kNSProgressKindFile = |
| 54 @"NSProgressKindFile"; |
| 55 NSString* const kNSProgressThroughputKey = |
| 56 @"NSProgressThroughputKey"; |
| 57 |
| 58 NSString* ProgressString(NSString* string) { |
| 59 static NSMutableDictionary* cache; |
| 60 static CFBundleRef foundation; |
| 61 if (!cache) { |
| 62 cache = [[NSMutableDictionary alloc] init]; |
| 63 foundation = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Foundation")); |
| 64 } |
| 65 |
| 66 NSString* result = [cache objectForKey:string]; |
| 67 if (!result) { |
| 68 NSString** ref = static_cast<NSString**>( |
| 69 CFBundleGetDataPointerForName(foundation, |
| 70 base::mac::NSToCFCast(string))); |
| 71 if (ref) { |
| 72 result = *ref; |
| 73 [cache setObject:result forKey:string]; |
| 74 } |
| 75 } |
| 76 |
| 77 return result; |
| 78 } |
| 79 |
| 80 } // namespace |
| 81 |
| 82 @interface NSProgress : NSObject |
| 83 |
| 84 - (id)initWithParent:(id)parent userInfo:(NSDictionary*)info; |
| 85 @property(copy) NSString* kind; |
| 86 |
| 87 - (void)unpublish; |
| 88 - (void)publish; |
| 89 |
| 90 - (void)setUserInfoObject:(id)object forKey:(NSString*)key; |
| 91 - (NSDictionary*)userInfo; |
| 92 |
| 93 @property(readonly) double fractionCompleted; |
| 94 // Set the totalUnitCount to -1 to indicate an indeterminate download. The dock |
| 95 // shows a non-filling progress bar; the Finder is lame and draws its progress |
| 96 // bar off the right side. |
| 97 @property(readonly, getter=isIndeterminate) BOOL indeterminate; |
| 98 @property long long completedUnitCount; |
| 99 @property long long totalUnitCount; |
| 100 |
| 101 // Pausing appears to be unimplemented in 10.8.0. |
| 102 - (void)pause; |
| 103 @property(readonly, getter=isPaused) BOOL paused; |
| 104 @property(getter=isPausable) BOOL pausable; |
| 105 - (void)setPausingHandler:(id)blockOfUnknownSignature; |
| 106 |
| 107 - (void)cancel; |
| 108 @property(readonly, getter=isCancelled) BOOL cancelled; |
| 109 @property(getter=isCancellable) BOOL cancellable; |
| 110 // Note that the cancellation handler block will be called on a random thread. |
| 111 - (void)setCancellationHandler:(void (^)())block; |
| 112 |
| 113 // Allows other applications to provide feedback as to whether the progress is |
| 114 // visible in that app. Note that the acknowledgement handler block will be |
| 115 // called on a random thread. |
| 116 // com.apple.dock => BOOL indicating whether the download target folder was |
| 117 // successfully "flown to" at the beginning of the download. |
| 118 // This primarily depends on whether the download target |
| 119 // folder is in the dock. Note that if the download target |
| 120 // folder is added or removed from the dock during the |
| 121 // duration of the download, it will not trigger a callback. |
| 122 // Note that if the "fly to the dock" keys were not set, the |
| 123 // callback's parameter will always be NO. |
| 124 // com.apple.Finder => always YES, no matter whether the download target |
| 125 // folder's window is open. |
| 126 - (void)handleAcknowledgementByAppWithBundleIdentifier:(NSString*)bundle |
| 127 usingBlock:(void (^)(BOOL success))block; |
| 128 |
| 129 @end |
| 130 |
| 131 // --- Private 10.8 API for showing progress --- |
| 132 |
| 133 namespace { |
| 134 |
| 135 bool NSProgressSupported() { |
| 136 static bool supported; |
| 137 static bool valid; |
| 138 if (!valid) { |
| 139 supported = NSClassFromString(@"NSProgress"); |
| 140 valid = true; |
| 141 } |
| 142 |
| 143 return supported; |
| 144 } |
| 145 |
| 146 const char kCrNSProgressUserDataKey[] = "CrNSProgressUserData"; |
| 147 |
| 148 class CrNSProgressUserData : public base::SupportsUserData::Data { |
| 149 public: |
| 150 CrNSProgressUserData(NSProgress* progress, const FilePath& target) |
| 151 : target_(target) { |
| 152 progress_.reset(progress); |
| 153 } |
| 154 virtual ~CrNSProgressUserData() {} |
| 155 |
| 156 NSProgress* progress() const { return progress_.get(); } |
| 157 FilePath target() const { return target_; } |
| 158 void setTarget(const FilePath& target) { target_ = target; } |
| 159 |
| 160 private: |
| 161 scoped_nsobject<NSProgress> progress_; |
| 162 FilePath target_; |
| 163 }; |
| 164 |
| 165 } // namespace |
| 166 |
| 167 void DownloadStatusUpdater::UpdateDownloadProgressForItemStarted( |
| 168 content::DownloadItem* download) { |
| 169 if (!NSProgressSupported()) |
| 170 return; |
| 171 |
| 172 NSURL* source_url = [NSURL URLWithString: |
| 173 base::SysUTF8ToNSString(download->GetURL().spec())]; |
| 174 FilePath destination_path = download->GetFullPath(); |
| 175 NSURL* destination_url = [NSURL fileURLWithPath: |
| 176 base::mac::FilePathToNSString(destination_path)]; |
| 177 |
| 178 // If there were an image to fly to the download folder in the dock, then |
| 179 // the keys in the userInfo to set would be: |
| 180 // - @"NSProgressFlyToImageKey" : NSImage |
| 181 // - kNSProgressFileIconOriginalRectKey : NSValue of NSRect in global coords |
| 182 |
| 183 NSDictionary* user_info = @{ |
| 184 ProgressString(kNSProgressFileDownloadingSourceURLKey) : source_url, |
| 185 ProgressString(kNSProgressFileLocationCanChangeKey) : @true, |
| 186 ProgressString(kNSProgressFileOperationKindKey) : |
| 187 ProgressString(kNSProgressFileOperationKindDownloading), |
| 188 ProgressString(kNSProgressFileURLKey) : destination_url |
| 189 }; |
| 190 Class progress_class = NSClassFromString(@"NSProgress"); |
| 191 NSProgress* progress = [progress_class performSelector:@selector(alloc)]; |
| 192 progress = [progress performSelector:@selector(initWithParent:userInfo:) |
| 193 withObject:nil |
| 194 withObject:user_info]; |
| 195 progress.kind = ProgressString(kNSProgressKindFile); |
| 196 |
| 197 progress.pausable = NO; |
| 198 progress.cancellable = YES; |
| 199 [progress setCancellationHandler:^{ |
| 200 dispatch_async(dispatch_get_main_queue(), ^{ |
| 201 download->Cancel(true); |
| 202 }); |
| 203 }]; |
| 204 |
| 205 progress.totalUnitCount = download->GetTotalBytes(); |
| 206 progress.completedUnitCount = download->GetReceivedBytes(); |
| 207 |
| 208 [progress publish]; |
| 209 |
| 210 download->SetUserData(&kCrNSProgressUserDataKey, |
| 211 new CrNSProgressUserData(progress, destination_path)); |
| 212 } |
| 213 |
| 214 void DownloadStatusUpdater::UpdateDownloadProgressForItemProgressed( |
| 215 content::DownloadItem* download) { |
| 216 if (!NSProgressSupported()) |
| 217 return; |
| 218 |
| 219 CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>( |
| 220 download->GetUserData(&kCrNSProgressUserDataKey)); |
| 221 if (!progress_data) |
| 222 return; |
| 223 |
| 224 NSProgress* progress = progress_data->progress(); |
| 225 progress.totalUnitCount = download->GetTotalBytes(); |
| 226 progress.completedUnitCount = download->GetReceivedBytes(); |
| 227 |
| 228 FilePath download_path = download->GetFullPath(); |
| 229 if (progress_data->target() != download_path) { |
| 230 progress_data->setTarget(download_path); |
| 231 NSURL* download_url = [NSURL fileURLWithPath: |
| 232 base::mac::FilePathToNSString(download_path)]; |
| 233 [progress setUserInfoObject:download_url |
| 234 forKey:ProgressString(kNSProgressFileURLKey)]; |
| 235 } |
| 236 } |
| 237 |
| 238 void DownloadStatusUpdater::UpdateDownloadProgressForItemCompleted( |
| 239 content::DownloadItem* download) { |
| 240 // Bounce the dock icon. |
| 241 NSString* download_path = |
| 242 base::mac::FilePathToNSString(download->GetFullPath()); |
| 243 [[NSDistributedNotificationCenter defaultCenter] |
| 244 postNotificationName:@"com.apple.DownloadFileFinished" |
| 245 object:download_path]; |
| 246 |
| 247 // Notify the Finder. |
| 248 NSString* parent_path = [download_path stringByDeletingLastPathComponent]; |
| 249 FNNotifyByPath( |
| 250 reinterpret_cast<const UInt8*>([parent_path fileSystemRepresentation]), |
| 251 kFNDirectoryModifiedMessage, |
| 252 kNilOptions); |
| 253 |
| 254 // Clean up 10.8 NSProgress stuff. |
| 255 if (!NSProgressSupported()) |
| 256 return; |
| 257 |
| 258 CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>( |
| 259 download->GetUserData(&kCrNSProgressUserDataKey)); |
| 260 if (!progress_data) |
| 261 return; |
| 262 |
| 263 NSProgress* progress = progress_data->progress(); |
| 264 [progress unpublish]; |
| 265 |
| 266 download->RemoveUserData(&kCrNSProgressUserDataKey); |
| 267 } |
OLD | NEW |