Chromium Code Reviews| 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 |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0ed4019cc00b42dc69b8f2420d3dd0c8ce00039a |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/arc/arc_downloads_watcher_service.cc |
| @@ -0,0 +1,237 @@ |
| +// 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 <memory> |
| +#include <utility> |
| + |
| +#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/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" |
| +#include "mojo/public/cpp/bindings/array.h" |
| + |
| +using content::BrowserThread; |
| + |
| +// Mapping from Android file paths to last modified timestamps. |
| +using TimestampMap = std::map<base::FilePath, base::Time>; |
| + |
| +static const base::FilePath::CharType kAndroidDownloadDir[] = |
| + FILE_PATH_LITERAL("/storage/emulated/0/Download"); |
| + |
| +namespace arc { |
| + |
| +namespace { |
| + |
| +// 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; |
| +} |
| + |
| +} // namespace |
| + |
| +// The core part of ArcDownloadsWatcherService to watch for file changes in |
| +// Downloads directory. |
| +class ArcDownloadsWatcherService::DownloadsWatcher { |
| + public: |
| + using Callback = |
| + base::Callback<void(const std::vector<base::FilePath>& paths)>; |
| + |
| + explicit DownloadsWatcher(const Callback& callback); |
| + ~DownloadsWatcher(); |
| + |
| + // Starts watching Downloads directory. |
| + void Start(); |
| + |
| + private: |
| + // Called by base::FilePathWatcher to notify file changes. |
| + void OnFilePathChanged(const base::FilePath& path, bool error); |
| + |
| + // Scans files under |downloads_dir_| recursively and builds a map from file |
| + // paths (in Android filesystem) to last modified timestamps. |
| + TimestampMap BuildTimestampMap() const; |
| + |
| + Callback callback_; |
| + base::FilePath downloads_dir_; |
| + std::unique_ptr<base::FilePathWatcher> watcher_; |
| + TimestampMap last_timestamp_map_; |
| + |
| + // 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), 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_timestamp_map_ = BuildTimestampMap(); |
| + |
| + watcher_.reset(new base::FilePathWatcher()); |
|
Luis Héctor Chávez
2016/05/20 15:18:59
nit: watcher_ = base::MakeUnique<FilePathWatcher>(
Shuhei Takahashi
2016/05/24 08:46:33
Done.
|
| + // 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); |
| + |
| + TimestampMap current_timestamp_map = BuildTimestampMap(); |
| + |
| + std::vector<base::FilePath> changed_paths = |
| + CollectChangedPaths(last_timestamp_map_, current_timestamp_map); |
| + |
| + last_timestamp_map_ = std::move(current_timestamp_map); |
| + |
| + callback_.Run(changed_paths); |
| +} |
| + |
| +TimestampMap ArcDownloadsWatcherService::DownloadsWatcher::BuildTimestampMap() |
| + const { |
| + 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 (;;) { |
|
Luis Héctor Chávez
2016/05/20 15:18:59
nit: for (const base::FilePath& cros_path = enumer
Shuhei Takahashi
2016/05/24 08:46:33
Done, but I made |cros_path| non-reference to perm
|
| + const base::FilePath& cros_path = enumerator.Next(); |
| + if (cros_path.empty()) { |
| + break; |
| + } |
| + // Android file path can be obtained by replacing |downloads_dir_| prefix |
| + // with |kAndroidDownloadDir|. |
| + const base::FilePath& android_path = |
| + base::FilePath(kAndroidDownloadDir) |
| + .Append( |
| + cros_path.value().substr(downloads_dir_.value().length() + 1)); |
| + const base::FileEnumerator::FileInfo& info = enumerator.GetInfo(); |
| + timestamp_map[android_path] = info.GetLastModifiedTime(); |
| + } |
| + return timestamp_map; |
| +} |
| + |
| +ArcDownloadsWatcherService::ArcDownloadsWatcherService( |
| + ArcBridgeService* bridge_service) |
| + : ArcService(bridge_service), weak_ptr_factory_(this) { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + arc_bridge_service()->AddObserver(this); |
| +} |
| + |
| +ArcDownloadsWatcherService::~ArcDownloadsWatcherService() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + arc_bridge_service()->RemoveObserver(this); |
| + StopWatchDownloads(); |
| + DCHECK(!watcher_.get()); |
| +} |
| + |
| +void ArcDownloadsWatcherService::OnFileSystemInstanceReady() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + StartWatchDownloads(); |
| +} |
| + |
| +void ArcDownloadsWatcherService::OnFileSystemInstanceClosed() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + StopWatchDownloads(); |
| +} |
| + |
| +void ArcDownloadsWatcherService::StartWatchDownloads() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + StopWatchDownloads(); |
| + DCHECK(!watcher_.get()); |
| + watcher_.reset(new 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::StopWatchDownloads() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + if (watcher_.get()) { |
| + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, |
| + watcher_.release()); |
| + } |
| +} |
| + |
| +void ArcDownloadsWatcherService::OnDownloadsChanged( |
| + const std::vector<base::FilePath>& paths) { |
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| + |
| + auto instance = arc_bridge_service()->file_system_instance(); |
| + if (!instance) { |
| + return; |
| + } |
| + |
| + mojo::Array<mojo::String> mojo_paths(paths.size()); |
| + for (int i = 0; i < paths.size(); ++i) { |
| + mojo_paths[i] = paths[i].value(); |
| + } |
| + instance->RequestMediaScan(std::move(mojo_paths)); |
| +} |
| + |
| +} // namespace arc |