| // Copyright 2012 The Chromium Authors |
| // 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" |
| |
| #import <Foundation/Foundation.h> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/memory/scoped_policy.h" |
| #include "base/supports_user_data.h" |
| #include "base/time/time.h" |
| #import "chrome/browser/ui/cocoa/dock_icon.h" |
| #include "components/download/public/common/download_item.h" |
| #import "net/base/mac/url_conversions.h" |
| |
| namespace { |
| |
| const char kCrNSProgressUserDataKey[] = "CrNSProgressUserData"; |
| |
| class CrNSProgressUserData : public base::SupportsUserData::Data { |
| public: |
| CrNSProgressUserData(NSProgress* progress, const base::FilePath& target) |
| : target_(target) { |
| progress_ = progress; |
| } |
| ~CrNSProgressUserData() override { [progress_ unpublish]; } |
| |
| NSProgress* progress() const { return progress_; } |
| base::FilePath target() const { return target_; } |
| void setTarget(const base::FilePath& target) { target_ = target; } |
| |
| private: |
| NSProgress* __strong progress_; |
| base::FilePath target_; |
| }; |
| |
| void UpdateAppDockIcon(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]; |
| } |
| |
| CrNSProgressUserData* CreateOrGetNSProgress(download::DownloadItem* download) { |
| CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>( |
| download->GetUserData(&kCrNSProgressUserDataKey)); |
| if (progress_data) |
| return progress_data; |
| |
| base::FilePath destination_path = download->GetFullPath(); |
| NSURL* destination_url = base::apple::FilePathToNSURL(destination_path); |
| |
| NSProgress* progress = [NSProgress progressWithTotalUnitCount:-1]; |
| progress.kind = NSProgressKindFile; |
| progress.fileOperationKind = NSProgressFileOperationKindDownloading; |
| progress.fileURL = destination_url; |
| |
| // Don't publish a pause/resume handler. The only users of `NSProgress` are |
| // outside of Chromium, and none currently implement pausing published |
| // progresses. Because there is no way to test pausing, do not implement or |
| // ship it. |
| progress.pausable = NO; |
| |
| // Do publish a cancellation handler. In icon view, the Finder provides a |
| // little (X) button on the icon, and using it will cause this callback. |
| progress.cancellable = YES; |
| progress.cancellationHandler = ^{ |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| download->Cancel(/*user_cancel=*/true); |
| }); |
| }; |
| |
| [progress publish]; |
| |
| download->SetUserData( |
| &kCrNSProgressUserDataKey, |
| std::make_unique<CrNSProgressUserData>(progress, destination_path)); |
| |
| return static_cast<CrNSProgressUserData*>( |
| download->GetUserData(&kCrNSProgressUserDataKey)); |
| } |
| |
| void UpdateNSProgress(download::DownloadItem* download) { |
| CrNSProgressUserData* progress_data = CreateOrGetNSProgress(download); |
| |
| NSProgress* progress = progress_data->progress(); |
| progress.totalUnitCount = download->GetTotalBytes(); |
| progress.completedUnitCount = download->GetReceivedBytes(); |
| progress.throughput = @(download->CurrentSpeed()); |
| |
| base::TimeDelta time_remaining; |
| NSNumber* ns_time_remaining = nil; |
| if (download->TimeRemaining(&time_remaining)) |
| ns_time_remaining = @(time_remaining.InSeconds()); |
| progress.estimatedTimeRemaining = ns_time_remaining; |
| |
| base::FilePath download_path = download->GetFullPath(); |
| if (progress_data->target() != download_path) { |
| progress_data->setTarget(download_path); |
| NSURL* download_url = base::apple::FilePathToNSURL(download_path); |
| progress.fileURL = download_url; |
| } |
| } |
| |
| void DestroyNSProgress(download::DownloadItem* download) { |
| download->RemoveUserData(&kCrNSProgressUserDataKey); |
| } |
| |
| } // namespace |
| |
| void DownloadStatusUpdater::UpdateAppIconDownloadProgress( |
| download::DownloadItem* download) { |
| // Always update overall progress in the Dock icon. |
| |
| float progress = 0; |
| int download_count = 0; |
| bool progress_known = GetProgress(&progress, &download_count); |
| UpdateAppDockIcon(download_count, progress_known, progress); |
| |
| // Update `NSProgress`-based indicators. Only show progress: |
| // - if the download is IN_PROGRESS, and |
| // - it has not yet saved all the data, and |
| // - it hasn't been renamed to its final name. |
| // |
| // There's a race condition in macOS code where unpublishing an `NSProgress` |
| // object for a file that was renamed will sometimes leave a progress |
| // indicator visible in the Finder (https://crbug.com/1304233). Therefore, as |
| // soon as `DownloadItem::AllDataSaved()` returns true, do the unpublish. |
| // As an additional bug to avoid (http://crbug.com/166683), never update the |
| // data of an `NSProgress` after the file name has changed, as that can result |
| // in the file being stuck in an in-progress state in the Dock. |
| if (download->GetState() == download::DownloadItem::IN_PROGRESS && |
| !download->AllDataSaved() && !download->GetFullPath().empty() && |
| download->GetFullPath() != download->GetTargetFilePath()) { |
| UpdateNSProgress(download); |
| } else { |
| DestroyNSProgress(download); |
| } |
| |
| // Handle downloads that ended. |
| if (download->GetState() != download::DownloadItem::IN_PROGRESS && |
| !download->GetTargetFilePath().empty()) { |
| NSString* download_path = |
| base::apple::FilePathToNSString(download->GetTargetFilePath()); |
| if (download->GetState() == download::DownloadItem::COMPLETE) { |
| // Bounce the dock icon. |
| [NSDistributedNotificationCenter.defaultCenter |
| postNotificationName:@"com.apple.DownloadFileFinished" |
| object:download_path]; |
| } |
| |
| // Notify the Finder. |
| [NSWorkspace.sharedWorkspace noteFileSystemChanged:download_path]; |
| } |
| } |