1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// 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/apple/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];
}
}
|