| Index: chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc
|
| diff --git a/chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc b/chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc
|
| deleted file mode 100644
|
| index fe2b98a42531906b887056651431122ec361505e..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc
|
| +++ /dev/null
|
| @@ -1,372 +0,0 @@
|
| -// Copyright 2016 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "chrome/browser/chromeos/arc/arc_downloads_watcher_service.h"
|
| -
|
| -#include <string.h>
|
| -
|
| -#include <algorithm>
|
| -#include <iterator>
|
| -#include <map>
|
| -#include <memory>
|
| -#include <string>
|
| -#include <utility>
|
| -#include <vector>
|
| -
|
| -#include "base/callback.h"
|
| -#include "base/files/file_enumerator.h"
|
| -#include "base/files/file_path.h"
|
| -#include "base/files/file_path_watcher.h"
|
| -#include "base/memory/ptr_util.h"
|
| -#include "base/strings/string_util.h"
|
| -#include "base/time/time.h"
|
| -#include "chrome/browser/download/download_prefs.h"
|
| -#include "chrome/browser/profiles/profile_manager.h"
|
| -#include "chrome/common/chrome_paths.h"
|
| -#include "components/arc/arc_bridge_service.h"
|
| -#include "content/public/browser/browser_thread.h"
|
| -
|
| -using content::BrowserThread;
|
| -
|
| -// Mapping from Android file paths to last modified timestamps.
|
| -using TimestampMap = std::map<base::FilePath, base::Time>;
|
| -
|
| -namespace arc {
|
| -
|
| -namespace {
|
| -
|
| -const base::FilePath::CharType kAndroidDownloadDir[] =
|
| - FILE_PATH_LITERAL("/storage/emulated/0/Download");
|
| -
|
| -// How long to wait for new inotify events before building the updated timestamp
|
| -// map.
|
| -const base::TimeDelta kBuildTimestampMapDelay =
|
| - base::TimeDelta::FromMilliseconds(1000);
|
| -
|
| -// The set of media file extensions supported by Android MediaScanner.
|
| -// Entries must be lower-case and sorted by lexicographical order for
|
| -// binary search.
|
| -// The current list was taken from aosp-marshmallow version of
|
| -// frameworks/base/media/java/android/media/MediaFile.java.
|
| -const char* kAndroidSupportedMediaExtensions[] = {
|
| - ".3g2", // FILE_TYPE_3GPP2, video/3gpp2
|
| - ".3gp", // FILE_TYPE_3GPP, video/3gpp
|
| - ".3gpp", // FILE_TYPE_3GPP, video/3gpp
|
| - ".3gpp2", // FILE_TYPE_3GPP2, video/3gpp2
|
| - ".aac", // FILE_TYPE_AAC, audio/aac, audio/aac-adts
|
| - ".amr", // FILE_TYPE_AMR, audio/amr
|
| - ".asf", // FILE_TYPE_ASF, video/x-ms-asf
|
| - ".avi", // FILE_TYPE_AVI, video/avi
|
| - ".awb", // FILE_TYPE_AWB, audio/amr-wb
|
| - ".bmp", // FILE_TYPE_BMP, image/x-ms-bmp
|
| - ".fl", // FILE_TYPE_FL, application/x-android-drm-fl
|
| - ".gif", // FILE_TYPE_GIF, image/gif
|
| - ".imy", // FILE_TYPE_IMY, audio/imelody
|
| - ".jpeg", // FILE_TYPE_JPEG, image/jpeg
|
| - ".jpg", // FILE_TYPE_JPEG, image/jpeg
|
| - ".m4a", // FILE_TYPE_M4A, audio/mp4
|
| - ".m4v", // FILE_TYPE_M4V, video/mp4
|
| - ".mid", // FILE_TYPE_MID, audio/midi
|
| - ".midi", // FILE_TYPE_MID, audio/midi
|
| - ".mka", // FILE_TYPE_MKA, audio/x-matroska
|
| - ".mkv", // FILE_TYPE_MKV, video/x-matroska
|
| - ".mp3", // FILE_TYPE_MP3, audio/mpeg
|
| - ".mp4", // FILE_TYPE_MP4, video/mp4
|
| - ".mpeg", // FILE_TYPE_MP4, video/mpeg, video/mp2p
|
| - ".mpg", // FILE_TYPE_MP4, video/mpeg, video/mp2p
|
| - ".mpga", // FILE_TYPE_MP3, audio/mpeg
|
| - ".mxmf", // FILE_TYPE_MID, audio/midi
|
| - ".oga", // FILE_TYPE_OGG, application/ogg
|
| - ".ogg", // FILE_TYPE_OGG, audio/ogg, application/ogg
|
| - ".ota", // FILE_TYPE_MID, audio/midi
|
| - ".png", // FILE_TYPE_PNG, image/png
|
| - ".rtttl", // FILE_TYPE_MID, audio/midi
|
| - ".rtx", // FILE_TYPE_MID, audio/midi
|
| - ".smf", // FILE_TYPE_SMF, audio/sp-midi
|
| - ".ts", // FILE_TYPE_MP2TS, video/mp2ts
|
| - ".wav", // FILE_TYPE_WAV, audio/x-wav
|
| - ".wbmp", // FILE_TYPE_WBMP, image/vnd.wap.wbmp
|
| - ".webm", // FILE_TYPE_WEBM, video/webm
|
| - ".webp", // FILE_TYPE_WEBP, image/webp
|
| - ".wma", // FILE_TYPE_WMA, audio/x-ms-wma
|
| - ".wmv", // FILE_TYPE_WMV, video/x-ms-wmv
|
| - ".xmf", // FILE_TYPE_MID, audio/midi
|
| -};
|
| -
|
| -// Compares two TimestampMaps and returns the list of file paths added/removed
|
| -// or whose timestamp have changed.
|
| -std::vector<base::FilePath> CollectChangedPaths(
|
| - const TimestampMap& timestamp_map_a,
|
| - const TimestampMap& timestamp_map_b) {
|
| - std::vector<base::FilePath> changed_paths;
|
| -
|
| - TimestampMap::const_iterator iter_a = timestamp_map_a.begin();
|
| - TimestampMap::const_iterator iter_b = timestamp_map_b.begin();
|
| - while (iter_a != timestamp_map_a.end() && iter_b != timestamp_map_b.end()) {
|
| - if (iter_a->first == iter_b->first) {
|
| - if (iter_a->second != iter_b->second) {
|
| - changed_paths.emplace_back(iter_a->first);
|
| - }
|
| - ++iter_a;
|
| - ++iter_b;
|
| - } else if (iter_a->first < iter_b->first) {
|
| - changed_paths.emplace_back(iter_a->first);
|
| - ++iter_a;
|
| - } else { // iter_a->first > iter_b->first
|
| - changed_paths.emplace_back(iter_b->first);
|
| - ++iter_b;
|
| - }
|
| - }
|
| -
|
| - while (iter_a != timestamp_map_a.end()) {
|
| - changed_paths.emplace_back(iter_a->first);
|
| - ++iter_a;
|
| - }
|
| - while (iter_b != timestamp_map_b.end()) {
|
| - changed_paths.emplace_back(iter_b->first);
|
| - ++iter_b;
|
| - }
|
| -
|
| - return changed_paths;
|
| -}
|
| -
|
| -// Scans files under |downloads_dir| recursively and builds a map from file
|
| -// paths (in Android filesystem) to last modified timestamps.
|
| -TimestampMap BuildTimestampMap(base::FilePath downloads_dir) {
|
| - DCHECK(!downloads_dir.EndsWithSeparator());
|
| - TimestampMap timestamp_map;
|
| -
|
| - // Enumerate normal files only; directories and symlinks are skipped.
|
| - base::FileEnumerator enumerator(downloads_dir, true,
|
| - base::FileEnumerator::FILES);
|
| - for (base::FilePath cros_path = enumerator.Next(); !cros_path.empty();
|
| - cros_path = enumerator.Next()) {
|
| - // Skip non-media files for efficiency.
|
| - if (!HasAndroidSupportedMediaExtension(cros_path))
|
| - continue;
|
| - // Android file path can be obtained by replacing |downloads_dir| prefix
|
| - // with |kAndroidDownloadDir|.
|
| - base::FilePath android_path(kAndroidDownloadDir);
|
| - downloads_dir.AppendRelativePath(cros_path, &android_path);
|
| - const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
|
| - timestamp_map[android_path] = info.GetLastModifiedTime();
|
| - }
|
| - return timestamp_map;
|
| -}
|
| -
|
| -std::pair<base::TimeTicks, TimestampMap> BuildTimestampMapCallback(
|
| - base::FilePath downloads_dir) {
|
| - // The TimestampMap may include changes form after snapshot_time.
|
| - // We must take the snapshot_time before we build the TimestampMap since
|
| - // changes that occur while building the map may not be captured.
|
| - base::TimeTicks snapshot_time = base::TimeTicks::Now();
|
| - TimestampMap current_timestamp_map = BuildTimestampMap(downloads_dir);
|
| - return std::make_pair(snapshot_time, std::move(current_timestamp_map));
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -bool HasAndroidSupportedMediaExtension(const base::FilePath& path) {
|
| - const std::string extension = base::ToLowerASCII(path.Extension());
|
| - const auto less_comparator = [](const char* a, const char* b) {
|
| - return strcmp(a, b) < 0;
|
| - };
|
| - DCHECK(std::is_sorted(std::begin(kAndroidSupportedMediaExtensions),
|
| - std::end(kAndroidSupportedMediaExtensions),
|
| - less_comparator));
|
| - auto iter = std::lower_bound(std::begin(kAndroidSupportedMediaExtensions),
|
| - std::end(kAndroidSupportedMediaExtensions),
|
| - extension.c_str(), less_comparator);
|
| - return iter != std::end(kAndroidSupportedMediaExtensions) &&
|
| - strcmp(*iter, extension.c_str()) == 0;
|
| -}
|
| -
|
| -// The core part of ArcDownloadsWatcherService to watch for file changes in
|
| -// Downloads directory.
|
| -class ArcDownloadsWatcherService::DownloadsWatcher {
|
| - public:
|
| - using Callback = base::Callback<void(mojo::Array<mojo::String> paths)>;
|
| -
|
| - explicit DownloadsWatcher(const Callback& callback);
|
| - ~DownloadsWatcher();
|
| -
|
| - // Starts watching Downloads directory.
|
| - void Start();
|
| -
|
| - private:
|
| - // Called by base::FilePathWatcher to notify file changes.
|
| - // Kicks off the update of last_timestamp_map_ if one is not already in
|
| - // progress.
|
| - void OnFilePathChanged(const base::FilePath& path, bool error);
|
| -
|
| - // Called with a delay to allow additional inotify events for the same user
|
| - // action to queue up so that they can be dealt with in batch.
|
| - void DelayBuildTimestampMap();
|
| -
|
| - // Called after a new timestamp map has been created and causes any recently
|
| - // modified files to be sent to the media scanner.
|
| - void OnBuildTimestampMap(
|
| - std::pair<base::TimeTicks, TimestampMap> timestamp_and_map);
|
| -
|
| - Callback callback_;
|
| - base::FilePath downloads_dir_;
|
| - std::unique_ptr<base::FilePathWatcher> watcher_;
|
| - TimestampMap last_timestamp_map_;
|
| - // The timestamp of the last OnFilePathChanged callback received.
|
| - base::TimeTicks last_notify_time_;
|
| - // Whether or not there is an outstanding task to update last_timestamp_map_.
|
| - bool outstanding_task_;
|
| -
|
| - // Note: This should remain the last member so it'll be destroyed and
|
| - // invalidate the weak pointers before any other members are destroyed.
|
| - base::WeakPtrFactory<DownloadsWatcher> weak_ptr_factory_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(DownloadsWatcher);
|
| -};
|
| -
|
| -ArcDownloadsWatcherService::DownloadsWatcher::DownloadsWatcher(
|
| - const Callback& callback)
|
| - : callback_(callback),
|
| - last_notify_time_(base::TimeTicks()),
|
| - outstanding_task_(false),
|
| - weak_ptr_factory_(this) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| -
|
| - downloads_dir_ = DownloadPrefs(ProfileManager::GetActiveUserProfile())
|
| - .GetDefaultDownloadDirectoryForProfile()
|
| - .StripTrailingSeparators();
|
| -}
|
| -
|
| -ArcDownloadsWatcherService::DownloadsWatcher::~DownloadsWatcher() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::DownloadsWatcher::Start() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| -
|
| - // Initialize with the current timestamp map and avoid initial notification.
|
| - // It is not needed since MediaProvider scans whole storage area on boot.
|
| - last_notify_time_ = base::TimeTicks::Now();
|
| - last_timestamp_map_ = BuildTimestampMap(downloads_dir_);
|
| -
|
| - watcher_ = base::MakeUnique<base::FilePathWatcher>();
|
| - // On Linux, base::FilePathWatcher::Watch() always returns true.
|
| - watcher_->Watch(downloads_dir_, true,
|
| - base::Bind(&DownloadsWatcher::OnFilePathChanged,
|
| - weak_ptr_factory_.GetWeakPtr()));
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::DownloadsWatcher::OnFilePathChanged(
|
| - const base::FilePath& path,
|
| - bool error) {
|
| - // On Linux, |error| is always false. Also, |path| is always the same path
|
| - // as one given to FilePathWatcher::Watch().
|
| - DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| - if (!outstanding_task_) {
|
| - outstanding_task_ = true;
|
| - BrowserThread::PostDelayedTask(
|
| - BrowserThread::FILE, FROM_HERE,
|
| - base::Bind(&DownloadsWatcher::DelayBuildTimestampMap,
|
| - weak_ptr_factory_.GetWeakPtr()),
|
| - kBuildTimestampMapDelay);
|
| - } else {
|
| - last_notify_time_ = base::TimeTicks::Now();
|
| - }
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::DownloadsWatcher::DelayBuildTimestampMap() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| - DCHECK(outstanding_task_);
|
| - base::PostTaskAndReplyWithResult(
|
| - BrowserThread::GetBlockingPool(), FROM_HERE,
|
| - base::Bind(&BuildTimestampMapCallback, downloads_dir_),
|
| - base::Bind(&DownloadsWatcher::OnBuildTimestampMap,
|
| - weak_ptr_factory_.GetWeakPtr()));
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::DownloadsWatcher::OnBuildTimestampMap(
|
| - std::pair<base::TimeTicks, TimestampMap> timestamp_and_map) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| - DCHECK(outstanding_task_);
|
| - base::TimeTicks snapshot_time = timestamp_and_map.first;
|
| - TimestampMap current_timestamp_map = std::move(timestamp_and_map.second);
|
| - std::vector<base::FilePath> changed_paths =
|
| - CollectChangedPaths(last_timestamp_map_, current_timestamp_map);
|
| -
|
| - last_timestamp_map_ = std::move(current_timestamp_map);
|
| -
|
| - mojo::Array<mojo::String> mojo_paths(changed_paths.size());
|
| - for (size_t i = 0; i < changed_paths.size(); ++i) {
|
| - mojo_paths[i] = changed_paths[i].value();
|
| - }
|
| - BrowserThread::PostTask(
|
| - BrowserThread::UI, FROM_HERE,
|
| - base::Bind(callback_, base::Passed(std::move(mojo_paths))));
|
| - if (last_notify_time_ > snapshot_time) {
|
| - base::PostTaskAndReplyWithResult(
|
| - BrowserThread::GetBlockingPool(), FROM_HERE,
|
| - base::Bind(&BuildTimestampMapCallback, downloads_dir_),
|
| - base::Bind(&DownloadsWatcher::OnBuildTimestampMap,
|
| - weak_ptr_factory_.GetWeakPtr()));
|
| - } else {
|
| - outstanding_task_ = false;
|
| - }
|
| -}
|
| -
|
| -ArcDownloadsWatcherService::ArcDownloadsWatcherService(
|
| - ArcBridgeService* bridge_service)
|
| - : ArcService(bridge_service), weak_ptr_factory_(this) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - arc_bridge_service()->file_system()->AddObserver(this);
|
| -}
|
| -
|
| -ArcDownloadsWatcherService::~ArcDownloadsWatcherService() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - arc_bridge_service()->file_system()->RemoveObserver(this);
|
| - StopWatchingDownloads();
|
| - DCHECK(!watcher_);
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::OnInstanceReady() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - StartWatchingDownloads();
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::OnInstanceClosed() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - StopWatchingDownloads();
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::StartWatchingDownloads() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - StopWatchingDownloads();
|
| - DCHECK(!watcher_);
|
| - watcher_ = base::MakeUnique<DownloadsWatcher>(
|
| - base::Bind(&ArcDownloadsWatcherService::OnDownloadsChanged,
|
| - weak_ptr_factory_.GetWeakPtr()));
|
| - BrowserThread::PostTask(
|
| - BrowserThread::FILE, FROM_HERE,
|
| - base::Bind(&DownloadsWatcher::Start, base::Unretained(watcher_.get())));
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::StopWatchingDownloads() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - if (watcher_) {
|
| - BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
|
| - watcher_.release());
|
| - }
|
| -}
|
| -
|
| -void ArcDownloadsWatcherService::OnDownloadsChanged(
|
| - mojo::Array<mojo::String> paths) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| -
|
| - auto* instance = arc_bridge_service()->file_system()->GetInstanceForMethod(
|
| - "RequestMediaScan");
|
| - if (!instance)
|
| - return;
|
| - instance->RequestMediaScan(std::move(paths));
|
| -}
|
| -
|
| -} // namespace arc
|
|
|