File: download_status_updater_mac.mm

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (159 lines) | stat: -rw-r--r-- 5,918 bytes parent folder | download | duplicates (4)
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];
  }
}