| 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 |