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