| 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..240f7ebe6cb649979696709e0b9a2ff3e13eeea3
|
| --- /dev/null
|
| +++ b/chrome/browser/download/download_status_updater_mac.mm
|
| @@ -0,0 +1,287 @@
|
| +// 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"
|
| +#import "chrome/browser/ui/cocoa/dock_icon.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_;
|
| +};
|
| +
|
| +void UpdateAppIcon(int download_count,
|
| + bool progress_known,
|
| + float progress) {
|
| + DockIcon* dock_icon = [DockIcon sharedDockIcon];
|
| + [dock_icon setDownloads:download_count];
|
| + [dock_icon setIndeterminate:!progress_known];
|
| + [dock_icon setProgress:progress];
|
| + [dock_icon updateIcon];
|
| +}
|
| +
|
| +void CreateNSProgress(content::DownloadItem* download) {
|
| + 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 UpdateNSProgress(content::DownloadItem* download,
|
| + CrNSProgressUserData* progress_data) {
|
| + 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 DestroyNSProgress(content::DownloadItem* download,
|
| + CrNSProgressUserData* progress_data) {
|
| + NSProgress* progress = progress_data->progress();
|
| + [progress unpublish];
|
| +
|
| + download->RemoveUserData(&kCrNSProgressUserDataKey);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +void DownloadStatusUpdater::UpdateAppIconDownloadProgress(
|
| + content::DownloadItem* download) {
|
| +
|
| + // Always update overall progress.
|
| +
|
| + float progress = 0;
|
| + int download_count = 0;
|
| + bool progress_known = GetProgress(&progress, &download_count);
|
| + UpdateAppIcon(download_count, progress_known, progress);
|
| +
|
| + // Update NSProgress-based indicators.
|
| +
|
| + if (NSProgressSupported()) {
|
| + CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>(
|
| + download->GetUserData(&kCrNSProgressUserDataKey));
|
| + if (!progress_data)
|
| + CreateNSProgress(download);
|
| + else
|
| + UpdateNSProgress(download, progress_data);
|
| +
|
| + if (download->GetState() != content::DownloadItem::IN_PROGRESS)
|
| + DestroyNSProgress(download, progress_data);
|
| + }
|
| +
|
| + // Handle downloads that ended.
|
| + if (download->GetState() != content::DownloadItem::IN_PROGRESS) {
|
| + NSString* download_path =
|
| + base::mac::FilePathToNSString(download->GetFullPath());
|
| + if (download->GetState() == content::DownloadItem::COMPLETE) {
|
| + // Bounce the dock icon.
|
| + [[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);
|
| + }
|
| +}
|
|
|