File: notification_image_retainer.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (196 lines) | stat: -rw-r--r-- 7,490 bytes parent folder | download | duplicates (11)
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright 2017 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/common/notifications/notification_image_retainer.h"

#include <algorithm>
#include <set>

#include "base/containers/contains.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/default_tick_clock.h"
#include "chrome/common/chrome_paths.h"
#include "ui/gfx/image/image.h"

namespace {

constexpr base::FilePath::CharType kImageRoot[] =
    FILE_PATH_LITERAL("Notification Resources");

// How long to keep the temp files before deleting them. The formula for picking
// the delay is t * (n + 1), where t is the default on-screen display time for
// an Action Center notification (6 seconds) and n is the number of
// notifications that can be shown on-screen at once (1).
constexpr base::TimeDelta kDeletionDelay = base::Seconds(12);

// Returns the temporary directory within the user data directory. The regular
// temporary directory is not used to minimize the risk of files getting deleted
// by accident. It is also not profile-bound because the notification bridge
// is profile-agnostic.
base::FilePath DetermineImageDirectory() {
  base::FilePath data_dir;
  bool success = base::PathService::Get(chrome::DIR_USER_DATA, &data_dir);
  DCHECK(success);
  return data_dir.Append(kImageRoot);
}

// Returns the full paths to all immediate file and directory children of |dir|,
// excluding those present in |registered_names|.
std::vector<base::FilePath> GetFilesFromPrevSessions(
    const base::FilePath& dir,
    const std::set<base::FilePath>& registered_names) {
  // |dir| may have sub-dirs, created by the old implementation.
  base::FileEnumerator file_enumerator(
      dir, /*recursive=*/false,
      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES,
      FILE_PATH_LITERAL("*"));
  std::vector<base::FilePath> files;

  for (base::FilePath current = file_enumerator.Next(); !current.empty();
       current = file_enumerator.Next()) {
    // Exclude any new file created in this session.
    if (!base::Contains(registered_names, current.BaseName()))
      files.push_back(std::move(current));
  }

  return files;
}

// Deletes files in |paths|.
void DeleteFiles(std::vector<base::FilePath> paths) {
  // |file_path| can be a directory, created by the old implementation, so
  // delete it recursively.
  for (const auto& file_path : paths)
    base::DeletePathRecursively(file_path);
}

}  // namespace

NotificationImageRetainer::NotificationImageRetainer(
    scoped_refptr<base::SequencedTaskRunner> deletion_task_runner,
    const base::TickClock* tick_clock)
    : deletion_task_runner_(std::move(deletion_task_runner)),
      image_dir_(DetermineImageDirectory()),
      tick_clock_(tick_clock),
      deletion_timer_(tick_clock) {
  DCHECK(deletion_task_runner_);
  DCHECK(tick_clock);

  DETACH_FROM_SEQUENCE(sequence_checker_);
}

NotificationImageRetainer::NotificationImageRetainer()
    : NotificationImageRetainer(
          base::ThreadPool::CreateSequencedTaskRunner(
              {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
               base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
          base::DefaultTickClock::GetInstance()) {}

NotificationImageRetainer::~NotificationImageRetainer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void NotificationImageRetainer::CleanupFilesFromPrevSessions() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Store all file names from registered_images in an ordered set for quick
  // search.
  std::set<base::FilePath> registered_names;
  for (const auto& pair : registered_images_)
    registered_names.insert(pair.first);

  std::vector<base::FilePath> files =
      GetFilesFromPrevSessions(image_dir_, registered_names);

  // This method is run in an "after startup" task, so it is fine to directly
  // post the DeleteFiles task to the runner.
  if (!files.empty()) {
    deletion_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&DeleteFiles, std::move(files)));
  }
}

base::FilePath NotificationImageRetainer::RegisterTemporaryImage(
    const gfx::Image& image) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  scoped_refptr<base::RefCountedMemory> data = image.As1xPNGBytes();
  if (data->size() == 0)
    return base::FilePath();

  // Create the image directory. Since Chrome doesn't delete this directory
  // after showing notifications, this directory creation should happen exactly
  // once until Chrome is re-installed.
  if (!base::CreateDirectory(image_dir_))
    return base::FilePath();

  base::FilePath temp_file;
  if (!base::CreateTemporaryFileInDir(image_dir_, &temp_file))
    return base::FilePath();

  const base::TimeTicks now = tick_clock_->NowTicks();
  DCHECK(registered_images_.empty() || now >= registered_images_.back().second);
  registered_images_.emplace_back(temp_file.BaseName(), now);

  // At this point, a temp file is already created. We need to clean it up even
  // if it fails to write the image data to this file.
  bool data_write_success = base::WriteFile(temp_file, *data);

  // Start the timer if it hasn't to delete the expired files in batch. This
  // avoids creating a deletion task for each file, otherwise the overhead can
  // be large when there is a steady stream of notifications coming rapidly.
  if (!deletion_timer_.IsRunning()) {
    deletion_timer_.Start(
        FROM_HERE, kDeletionDelay,
        base::BindRepeating(&NotificationImageRetainer::DeleteExpiredFiles,
                            weak_ptr_factory_.GetWeakPtr()));
  }

  return data_write_success ? temp_file : base::FilePath();
}

base::OnceClosure NotificationImageRetainer::GetCleanupTask() {
  return base::BindOnce(
      &NotificationImageRetainer::CleanupFilesFromPrevSessions,
      weak_ptr_factory_.GetWeakPtr());
}

void NotificationImageRetainer::DeleteExpiredFiles() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!registered_images_.empty());

  // Find the first file that should not be deleted.
  const base::TimeTicks then = tick_clock_->NowTicks() - kDeletionDelay;
  const auto end =
      std::upper_bound(registered_images_.begin(), registered_images_.end(),
                       std::make_pair(base::FilePath(), then),
                       [](const NameAndTime& a, const NameAndTime& b) {
                         return a.second < b.second;
                       });
  if (end == registered_images_.begin())
    return;  // Nothing to delete yet.

  // Ship the files to be deleted off to the deletion task runner.
  std::vector<base::FilePath> files_to_delete;
  files_to_delete.reserve(end - registered_images_.begin());
  for (auto iter = registered_images_.begin(); iter < end; ++iter)
    files_to_delete.push_back(image_dir_.Append(iter->first));

  deletion_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&DeleteFiles, std::move(files_to_delete)));

  // Erase the items to be deleted from registered_images_.
  registered_images_.erase(registered_images_.begin(), end);

  // Stop the recurring timer if all files have been deleted.
  if (registered_images_.empty())
    deletion_timer_.Stop();
}