OLD | NEW |
| (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 | |
OLD | NEW |