Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Side by Side Diff: chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc

Issue 2441563002: arc: Create intermediate directories in c/b/c/arc (Closed)
Patch Set: Re-rebase to ToT Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/arc/arc_downloads_watcher_service.h"
6
7 #include <string.h>
8
9 #include <algorithm>
10 #include <iterator>
11 #include <map>
12 #include <memory>
13 #include <string>
14 #include <utility>
15 #include <vector>
16
17 #include "base/callback.h"
18 #include "base/files/file_enumerator.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_path_watcher.h"
21 #include "base/memory/ptr_util.h"
22 #include "base/strings/string_util.h"
23 #include "base/time/time.h"
24 #include "chrome/browser/download/download_prefs.h"
25 #include "chrome/browser/profiles/profile_manager.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "components/arc/arc_bridge_service.h"
28 #include "content/public/browser/browser_thread.h"
29
30 using content::BrowserThread;
31
32 // Mapping from Android file paths to last modified timestamps.
33 using TimestampMap = std::map<base::FilePath, base::Time>;
34
35 namespace arc {
36
37 namespace {
38
39 const base::FilePath::CharType kAndroidDownloadDir[] =
40 FILE_PATH_LITERAL("/storage/emulated/0/Download");
41
42 // How long to wait for new inotify events before building the updated timestamp
43 // map.
44 const base::TimeDelta kBuildTimestampMapDelay =
45 base::TimeDelta::FromMilliseconds(1000);
46
47 // The set of media file extensions supported by Android MediaScanner.
48 // Entries must be lower-case and sorted by lexicographical order for
49 // binary search.
50 // The current list was taken from aosp-marshmallow version of
51 // frameworks/base/media/java/android/media/MediaFile.java.
52 const char* kAndroidSupportedMediaExtensions[] = {
53 ".3g2", // FILE_TYPE_3GPP2, video/3gpp2
54 ".3gp", // FILE_TYPE_3GPP, video/3gpp
55 ".3gpp", // FILE_TYPE_3GPP, video/3gpp
56 ".3gpp2", // FILE_TYPE_3GPP2, video/3gpp2
57 ".aac", // FILE_TYPE_AAC, audio/aac, audio/aac-adts
58 ".amr", // FILE_TYPE_AMR, audio/amr
59 ".asf", // FILE_TYPE_ASF, video/x-ms-asf
60 ".avi", // FILE_TYPE_AVI, video/avi
61 ".awb", // FILE_TYPE_AWB, audio/amr-wb
62 ".bmp", // FILE_TYPE_BMP, image/x-ms-bmp
63 ".fl", // FILE_TYPE_FL, application/x-android-drm-fl
64 ".gif", // FILE_TYPE_GIF, image/gif
65 ".imy", // FILE_TYPE_IMY, audio/imelody
66 ".jpeg", // FILE_TYPE_JPEG, image/jpeg
67 ".jpg", // FILE_TYPE_JPEG, image/jpeg
68 ".m4a", // FILE_TYPE_M4A, audio/mp4
69 ".m4v", // FILE_TYPE_M4V, video/mp4
70 ".mid", // FILE_TYPE_MID, audio/midi
71 ".midi", // FILE_TYPE_MID, audio/midi
72 ".mka", // FILE_TYPE_MKA, audio/x-matroska
73 ".mkv", // FILE_TYPE_MKV, video/x-matroska
74 ".mp3", // FILE_TYPE_MP3, audio/mpeg
75 ".mp4", // FILE_TYPE_MP4, video/mp4
76 ".mpeg", // FILE_TYPE_MP4, video/mpeg, video/mp2p
77 ".mpg", // FILE_TYPE_MP4, video/mpeg, video/mp2p
78 ".mpga", // FILE_TYPE_MP3, audio/mpeg
79 ".mxmf", // FILE_TYPE_MID, audio/midi
80 ".oga", // FILE_TYPE_OGG, application/ogg
81 ".ogg", // FILE_TYPE_OGG, audio/ogg, application/ogg
82 ".ota", // FILE_TYPE_MID, audio/midi
83 ".png", // FILE_TYPE_PNG, image/png
84 ".rtttl", // FILE_TYPE_MID, audio/midi
85 ".rtx", // FILE_TYPE_MID, audio/midi
86 ".smf", // FILE_TYPE_SMF, audio/sp-midi
87 ".ts", // FILE_TYPE_MP2TS, video/mp2ts
88 ".wav", // FILE_TYPE_WAV, audio/x-wav
89 ".wbmp", // FILE_TYPE_WBMP, image/vnd.wap.wbmp
90 ".webm", // FILE_TYPE_WEBM, video/webm
91 ".webp", // FILE_TYPE_WEBP, image/webp
92 ".wma", // FILE_TYPE_WMA, audio/x-ms-wma
93 ".wmv", // FILE_TYPE_WMV, video/x-ms-wmv
94 ".xmf", // FILE_TYPE_MID, audio/midi
95 };
96
97 // Compares two TimestampMaps and returns the list of file paths added/removed
98 // or whose timestamp have changed.
99 std::vector<base::FilePath> CollectChangedPaths(
100 const TimestampMap& timestamp_map_a,
101 const TimestampMap& timestamp_map_b) {
102 std::vector<base::FilePath> changed_paths;
103
104 TimestampMap::const_iterator iter_a = timestamp_map_a.begin();
105 TimestampMap::const_iterator iter_b = timestamp_map_b.begin();
106 while (iter_a != timestamp_map_a.end() && iter_b != timestamp_map_b.end()) {
107 if (iter_a->first == iter_b->first) {
108 if (iter_a->second != iter_b->second) {
109 changed_paths.emplace_back(iter_a->first);
110 }
111 ++iter_a;
112 ++iter_b;
113 } else if (iter_a->first < iter_b->first) {
114 changed_paths.emplace_back(iter_a->first);
115 ++iter_a;
116 } else { // iter_a->first > iter_b->first
117 changed_paths.emplace_back(iter_b->first);
118 ++iter_b;
119 }
120 }
121
122 while (iter_a != timestamp_map_a.end()) {
123 changed_paths.emplace_back(iter_a->first);
124 ++iter_a;
125 }
126 while (iter_b != timestamp_map_b.end()) {
127 changed_paths.emplace_back(iter_b->first);
128 ++iter_b;
129 }
130
131 return changed_paths;
132 }
133
134 // Scans files under |downloads_dir| recursively and builds a map from file
135 // paths (in Android filesystem) to last modified timestamps.
136 TimestampMap BuildTimestampMap(base::FilePath downloads_dir) {
137 DCHECK(!downloads_dir.EndsWithSeparator());
138 TimestampMap timestamp_map;
139
140 // Enumerate normal files only; directories and symlinks are skipped.
141 base::FileEnumerator enumerator(downloads_dir, true,
142 base::FileEnumerator::FILES);
143 for (base::FilePath cros_path = enumerator.Next(); !cros_path.empty();
144 cros_path = enumerator.Next()) {
145 // Skip non-media files for efficiency.
146 if (!HasAndroidSupportedMediaExtension(cros_path))
147 continue;
148 // Android file path can be obtained by replacing |downloads_dir| prefix
149 // with |kAndroidDownloadDir|.
150 base::FilePath android_path(kAndroidDownloadDir);
151 downloads_dir.AppendRelativePath(cros_path, &android_path);
152 const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
153 timestamp_map[android_path] = info.GetLastModifiedTime();
154 }
155 return timestamp_map;
156 }
157
158 std::pair<base::TimeTicks, TimestampMap> BuildTimestampMapCallback(
159 base::FilePath downloads_dir) {
160 // The TimestampMap may include changes form after snapshot_time.
161 // We must take the snapshot_time before we build the TimestampMap since
162 // changes that occur while building the map may not be captured.
163 base::TimeTicks snapshot_time = base::TimeTicks::Now();
164 TimestampMap current_timestamp_map = BuildTimestampMap(downloads_dir);
165 return std::make_pair(snapshot_time, std::move(current_timestamp_map));
166 }
167
168 } // namespace
169
170 bool HasAndroidSupportedMediaExtension(const base::FilePath& path) {
171 const std::string extension = base::ToLowerASCII(path.Extension());
172 const auto less_comparator = [](const char* a, const char* b) {
173 return strcmp(a, b) < 0;
174 };
175 DCHECK(std::is_sorted(std::begin(kAndroidSupportedMediaExtensions),
176 std::end(kAndroidSupportedMediaExtensions),
177 less_comparator));
178 auto iter = std::lower_bound(std::begin(kAndroidSupportedMediaExtensions),
179 std::end(kAndroidSupportedMediaExtensions),
180 extension.c_str(), less_comparator);
181 return iter != std::end(kAndroidSupportedMediaExtensions) &&
182 strcmp(*iter, extension.c_str()) == 0;
183 }
184
185 // The core part of ArcDownloadsWatcherService to watch for file changes in
186 // Downloads directory.
187 class ArcDownloadsWatcherService::DownloadsWatcher {
188 public:
189 using Callback = base::Callback<void(mojo::Array<mojo::String> paths)>;
190
191 explicit DownloadsWatcher(const Callback& callback);
192 ~DownloadsWatcher();
193
194 // Starts watching Downloads directory.
195 void Start();
196
197 private:
198 // Called by base::FilePathWatcher to notify file changes.
199 // Kicks off the update of last_timestamp_map_ if one is not already in
200 // progress.
201 void OnFilePathChanged(const base::FilePath& path, bool error);
202
203 // Called with a delay to allow additional inotify events for the same user
204 // action to queue up so that they can be dealt with in batch.
205 void DelayBuildTimestampMap();
206
207 // Called after a new timestamp map has been created and causes any recently
208 // modified files to be sent to the media scanner.
209 void OnBuildTimestampMap(
210 std::pair<base::TimeTicks, TimestampMap> timestamp_and_map);
211
212 Callback callback_;
213 base::FilePath downloads_dir_;
214 std::unique_ptr<base::FilePathWatcher> watcher_;
215 TimestampMap last_timestamp_map_;
216 // The timestamp of the last OnFilePathChanged callback received.
217 base::TimeTicks last_notify_time_;
218 // Whether or not there is an outstanding task to update last_timestamp_map_.
219 bool outstanding_task_;
220
221 // Note: This should remain the last member so it'll be destroyed and
222 // invalidate the weak pointers before any other members are destroyed.
223 base::WeakPtrFactory<DownloadsWatcher> weak_ptr_factory_;
224
225 DISALLOW_COPY_AND_ASSIGN(DownloadsWatcher);
226 };
227
228 ArcDownloadsWatcherService::DownloadsWatcher::DownloadsWatcher(
229 const Callback& callback)
230 : callback_(callback),
231 last_notify_time_(base::TimeTicks()),
232 outstanding_task_(false),
233 weak_ptr_factory_(this) {
234 DCHECK_CURRENTLY_ON(BrowserThread::UI);
235
236 downloads_dir_ = DownloadPrefs(ProfileManager::GetActiveUserProfile())
237 .GetDefaultDownloadDirectoryForProfile()
238 .StripTrailingSeparators();
239 }
240
241 ArcDownloadsWatcherService::DownloadsWatcher::~DownloadsWatcher() {
242 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
243 }
244
245 void ArcDownloadsWatcherService::DownloadsWatcher::Start() {
246 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
247
248 // Initialize with the current timestamp map and avoid initial notification.
249 // It is not needed since MediaProvider scans whole storage area on boot.
250 last_notify_time_ = base::TimeTicks::Now();
251 last_timestamp_map_ = BuildTimestampMap(downloads_dir_);
252
253 watcher_ = base::MakeUnique<base::FilePathWatcher>();
254 // On Linux, base::FilePathWatcher::Watch() always returns true.
255 watcher_->Watch(downloads_dir_, true,
256 base::Bind(&DownloadsWatcher::OnFilePathChanged,
257 weak_ptr_factory_.GetWeakPtr()));
258 }
259
260 void ArcDownloadsWatcherService::DownloadsWatcher::OnFilePathChanged(
261 const base::FilePath& path,
262 bool error) {
263 // On Linux, |error| is always false. Also, |path| is always the same path
264 // as one given to FilePathWatcher::Watch().
265 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
266 if (!outstanding_task_) {
267 outstanding_task_ = true;
268 BrowserThread::PostDelayedTask(
269 BrowserThread::FILE, FROM_HERE,
270 base::Bind(&DownloadsWatcher::DelayBuildTimestampMap,
271 weak_ptr_factory_.GetWeakPtr()),
272 kBuildTimestampMapDelay);
273 } else {
274 last_notify_time_ = base::TimeTicks::Now();
275 }
276 }
277
278 void ArcDownloadsWatcherService::DownloadsWatcher::DelayBuildTimestampMap() {
279 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
280 DCHECK(outstanding_task_);
281 base::PostTaskAndReplyWithResult(
282 BrowserThread::GetBlockingPool(), FROM_HERE,
283 base::Bind(&BuildTimestampMapCallback, downloads_dir_),
284 base::Bind(&DownloadsWatcher::OnBuildTimestampMap,
285 weak_ptr_factory_.GetWeakPtr()));
286 }
287
288 void ArcDownloadsWatcherService::DownloadsWatcher::OnBuildTimestampMap(
289 std::pair<base::TimeTicks, TimestampMap> timestamp_and_map) {
290 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
291 DCHECK(outstanding_task_);
292 base::TimeTicks snapshot_time = timestamp_and_map.first;
293 TimestampMap current_timestamp_map = std::move(timestamp_and_map.second);
294 std::vector<base::FilePath> changed_paths =
295 CollectChangedPaths(last_timestamp_map_, current_timestamp_map);
296
297 last_timestamp_map_ = std::move(current_timestamp_map);
298
299 mojo::Array<mojo::String> mojo_paths(changed_paths.size());
300 for (size_t i = 0; i < changed_paths.size(); ++i) {
301 mojo_paths[i] = changed_paths[i].value();
302 }
303 BrowserThread::PostTask(
304 BrowserThread::UI, FROM_HERE,
305 base::Bind(callback_, base::Passed(std::move(mojo_paths))));
306 if (last_notify_time_ > snapshot_time) {
307 base::PostTaskAndReplyWithResult(
308 BrowserThread::GetBlockingPool(), FROM_HERE,
309 base::Bind(&BuildTimestampMapCallback, downloads_dir_),
310 base::Bind(&DownloadsWatcher::OnBuildTimestampMap,
311 weak_ptr_factory_.GetWeakPtr()));
312 } else {
313 outstanding_task_ = false;
314 }
315 }
316
317 ArcDownloadsWatcherService::ArcDownloadsWatcherService(
318 ArcBridgeService* bridge_service)
319 : ArcService(bridge_service), weak_ptr_factory_(this) {
320 DCHECK_CURRENTLY_ON(BrowserThread::UI);
321 arc_bridge_service()->file_system()->AddObserver(this);
322 }
323
324 ArcDownloadsWatcherService::~ArcDownloadsWatcherService() {
325 DCHECK_CURRENTLY_ON(BrowserThread::UI);
326 arc_bridge_service()->file_system()->RemoveObserver(this);
327 StopWatchingDownloads();
328 DCHECK(!watcher_);
329 }
330
331 void ArcDownloadsWatcherService::OnInstanceReady() {
332 DCHECK_CURRENTLY_ON(BrowserThread::UI);
333 StartWatchingDownloads();
334 }
335
336 void ArcDownloadsWatcherService::OnInstanceClosed() {
337 DCHECK_CURRENTLY_ON(BrowserThread::UI);
338 StopWatchingDownloads();
339 }
340
341 void ArcDownloadsWatcherService::StartWatchingDownloads() {
342 DCHECK_CURRENTLY_ON(BrowserThread::UI);
343 StopWatchingDownloads();
344 DCHECK(!watcher_);
345 watcher_ = base::MakeUnique<DownloadsWatcher>(
346 base::Bind(&ArcDownloadsWatcherService::OnDownloadsChanged,
347 weak_ptr_factory_.GetWeakPtr()));
348 BrowserThread::PostTask(
349 BrowserThread::FILE, FROM_HERE,
350 base::Bind(&DownloadsWatcher::Start, base::Unretained(watcher_.get())));
351 }
352
353 void ArcDownloadsWatcherService::StopWatchingDownloads() {
354 DCHECK_CURRENTLY_ON(BrowserThread::UI);
355 if (watcher_) {
356 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
357 watcher_.release());
358 }
359 }
360
361 void ArcDownloadsWatcherService::OnDownloadsChanged(
362 mojo::Array<mojo::String> paths) {
363 DCHECK_CURRENTLY_ON(BrowserThread::UI);
364
365 auto* instance = arc_bridge_service()->file_system()->GetInstanceForMethod(
366 "RequestMediaScan");
367 if (!instance)
368 return;
369 instance->RequestMediaScan(std::move(paths));
370 }
371
372 } // namespace arc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698