Chromium Code Reviews| 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 <memory> | |
| 8 #include <utility> | |
| 9 | |
| 10 #include "base/callback.h" | |
| 11 #include "base/files/file_enumerator.h" | |
| 12 #include "base/files/file_path.h" | |
| 13 #include "base/files/file_path_watcher.h" | |
| 14 #include "base/memory/ptr_util.h" | |
| 15 #include "base/time/time.h" | |
| 16 #include "chrome/browser/download/download_prefs.h" | |
| 17 #include "chrome/browser/profiles/profile_manager.h" | |
| 18 #include "chrome/common/chrome_paths.h" | |
| 19 #include "components/arc/arc_bridge_service.h" | |
| 20 #include "content/public/browser/browser_thread.h" | |
| 21 #include "mojo/public/cpp/bindings/array.h" | |
| 22 | |
| 23 using content::BrowserThread; | |
| 24 | |
| 25 // Mapping from Android file paths to last modified timestamps. | |
| 26 using TimestampMap = std::map<base::FilePath, base::Time>; | |
| 27 | |
| 28 static const base::FilePath::CharType kAndroidDownloadDir[] = | |
| 29 FILE_PATH_LITERAL("/storage/emulated/0/Download"); | |
| 30 | |
| 31 namespace arc { | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 // Compares two TimestampMaps and returns the list of file paths added/removed | |
| 36 // or whose timestamp have changed. | |
| 37 std::vector<base::FilePath> CollectChangedPaths( | |
| 38 const TimestampMap& timestamp_map_a, | |
| 39 const TimestampMap& timestamp_map_b) { | |
| 40 std::vector<base::FilePath> changed_paths; | |
| 41 | |
| 42 TimestampMap::const_iterator iter_a = timestamp_map_a.begin(); | |
| 43 TimestampMap::const_iterator iter_b = timestamp_map_b.begin(); | |
| 44 while (iter_a != timestamp_map_a.end() && iter_b != timestamp_map_b.end()) { | |
| 45 if (iter_a->first == iter_b->first) { | |
| 46 if (iter_a->second != iter_b->second) { | |
| 47 changed_paths.emplace_back(iter_a->first); | |
| 48 } | |
| 49 ++iter_a; | |
| 50 ++iter_b; | |
| 51 } else if (iter_a->first < iter_b->first) { | |
| 52 changed_paths.emplace_back(iter_a->first); | |
| 53 ++iter_a; | |
| 54 } else { // iter_a->first > iter_b->first | |
| 55 changed_paths.emplace_back(iter_b->first); | |
| 56 ++iter_b; | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 while (iter_a != timestamp_map_a.end()) { | |
| 61 changed_paths.emplace_back(iter_a->first); | |
| 62 ++iter_a; | |
| 63 } | |
| 64 while (iter_b != timestamp_map_b.end()) { | |
| 65 changed_paths.emplace_back(iter_b->first); | |
| 66 ++iter_b; | |
| 67 } | |
| 68 | |
| 69 return changed_paths; | |
| 70 } | |
| 71 | |
| 72 } // namespace | |
| 73 | |
| 74 // The core part of ArcDownloadsWatcherService to watch for file changes in | |
| 75 // Downloads directory. | |
| 76 class ArcDownloadsWatcherService::DownloadsWatcher { | |
| 77 public: | |
| 78 using Callback = | |
| 79 base::Callback<void(const std::vector<base::FilePath>& paths)>; | |
| 80 | |
| 81 explicit DownloadsWatcher(const Callback& callback); | |
| 82 ~DownloadsWatcher(); | |
| 83 | |
| 84 // Starts watching Downloads directory. | |
| 85 void Start(); | |
| 86 | |
| 87 private: | |
| 88 // Called by base::FilePathWatcher to notify file changes. | |
| 89 void OnFilePathChanged(const base::FilePath& path, bool error); | |
| 90 | |
| 91 // Scans files under |downloads_dir_| recursively and builds a map from file | |
| 92 // paths (in Android filesystem) to last modified timestamps. | |
| 93 TimestampMap BuildTimestampMap() const; | |
| 94 | |
| 95 Callback callback_; | |
| 96 base::FilePath downloads_dir_; | |
| 97 std::unique_ptr<base::FilePathWatcher> watcher_; | |
| 98 TimestampMap last_timestamp_map_; | |
| 99 | |
| 100 // Note: This should remain the last member so it'll be destroyed and | |
| 101 // invalidate the weak pointers before any other members are destroyed. | |
| 102 base::WeakPtrFactory<DownloadsWatcher> weak_ptr_factory_; | |
| 103 | |
| 104 DISALLOW_COPY_AND_ASSIGN(DownloadsWatcher); | |
| 105 }; | |
| 106 | |
| 107 ArcDownloadsWatcherService::DownloadsWatcher::DownloadsWatcher( | |
| 108 const Callback& callback) | |
| 109 : callback_(callback), weak_ptr_factory_(this) { | |
| 110 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 111 | |
| 112 downloads_dir_ = DownloadPrefs(ProfileManager::GetActiveUserProfile()) | |
| 113 .GetDefaultDownloadDirectoryForProfile() | |
| 114 .StripTrailingSeparators(); | |
| 115 } | |
| 116 | |
| 117 ArcDownloadsWatcherService::DownloadsWatcher::~DownloadsWatcher() { | |
| 118 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | |
| 119 } | |
| 120 | |
| 121 void ArcDownloadsWatcherService::DownloadsWatcher::Start() { | |
| 122 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | |
| 123 | |
| 124 // Initialize with the current timestamp map and avoid initial notification. | |
| 125 // It is not needed since MediaProvider scans whole storage area on boot. | |
| 126 last_timestamp_map_ = BuildTimestampMap(); | |
| 127 | |
| 128 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.
| |
| 129 // On Linux, base::FilePathWatcher::Watch() always returns true. | |
| 130 watcher_->Watch(downloads_dir_, true, | |
| 131 base::Bind(&DownloadsWatcher::OnFilePathChanged, | |
| 132 weak_ptr_factory_.GetWeakPtr())); | |
| 133 } | |
| 134 | |
| 135 void ArcDownloadsWatcherService::DownloadsWatcher::OnFilePathChanged( | |
| 136 const base::FilePath& path, | |
| 137 bool error) { | |
| 138 // On Linux, |error| is always false. Also, |path| is always the same path | |
| 139 // as one given to FilePathWatcher::Watch(). | |
| 140 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | |
| 141 | |
| 142 TimestampMap current_timestamp_map = BuildTimestampMap(); | |
| 143 | |
| 144 std::vector<base::FilePath> changed_paths = | |
| 145 CollectChangedPaths(last_timestamp_map_, current_timestamp_map); | |
| 146 | |
| 147 last_timestamp_map_ = std::move(current_timestamp_map); | |
| 148 | |
| 149 callback_.Run(changed_paths); | |
| 150 } | |
| 151 | |
| 152 TimestampMap ArcDownloadsWatcherService::DownloadsWatcher::BuildTimestampMap() | |
| 153 const { | |
| 154 DCHECK(!downloads_dir_.EndsWithSeparator()); | |
| 155 TimestampMap timestamp_map; | |
| 156 | |
| 157 // Enumerate normal files only; directories and symlinks are skipped. | |
| 158 base::FileEnumerator enumerator(downloads_dir_, true, | |
| 159 base::FileEnumerator::FILES); | |
| 160 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
| |
| 161 const base::FilePath& cros_path = enumerator.Next(); | |
| 162 if (cros_path.empty()) { | |
| 163 break; | |
| 164 } | |
| 165 // Android file path can be obtained by replacing |downloads_dir_| prefix | |
| 166 // with |kAndroidDownloadDir|. | |
| 167 const base::FilePath& android_path = | |
| 168 base::FilePath(kAndroidDownloadDir) | |
| 169 .Append( | |
| 170 cros_path.value().substr(downloads_dir_.value().length() + 1)); | |
| 171 const base::FileEnumerator::FileInfo& info = enumerator.GetInfo(); | |
| 172 timestamp_map[android_path] = info.GetLastModifiedTime(); | |
| 173 } | |
| 174 return timestamp_map; | |
| 175 } | |
| 176 | |
| 177 ArcDownloadsWatcherService::ArcDownloadsWatcherService( | |
| 178 ArcBridgeService* bridge_service) | |
| 179 : ArcService(bridge_service), weak_ptr_factory_(this) { | |
| 180 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 181 arc_bridge_service()->AddObserver(this); | |
| 182 } | |
| 183 | |
| 184 ArcDownloadsWatcherService::~ArcDownloadsWatcherService() { | |
| 185 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 186 arc_bridge_service()->RemoveObserver(this); | |
| 187 StopWatchDownloads(); | |
| 188 DCHECK(!watcher_.get()); | |
| 189 } | |
| 190 | |
| 191 void ArcDownloadsWatcherService::OnFileSystemInstanceReady() { | |
| 192 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 193 StartWatchDownloads(); | |
| 194 } | |
| 195 | |
| 196 void ArcDownloadsWatcherService::OnFileSystemInstanceClosed() { | |
| 197 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 198 StopWatchDownloads(); | |
| 199 } | |
| 200 | |
| 201 void ArcDownloadsWatcherService::StartWatchDownloads() { | |
| 202 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 203 StopWatchDownloads(); | |
| 204 DCHECK(!watcher_.get()); | |
| 205 watcher_.reset(new DownloadsWatcher( | |
| 206 base::Bind(&ArcDownloadsWatcherService::OnDownloadsChanged, | |
| 207 weak_ptr_factory_.GetWeakPtr()))); | |
| 208 BrowserThread::PostTask( | |
| 209 BrowserThread::FILE, FROM_HERE, | |
| 210 base::Bind(&DownloadsWatcher::Start, base::Unretained(watcher_.get()))); | |
| 211 } | |
| 212 | |
| 213 void ArcDownloadsWatcherService::StopWatchDownloads() { | |
| 214 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 215 if (watcher_.get()) { | |
| 216 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, | |
| 217 watcher_.release()); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 void ArcDownloadsWatcherService::OnDownloadsChanged( | |
| 222 const std::vector<base::FilePath>& paths) { | |
| 223 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | |
| 224 | |
| 225 auto instance = arc_bridge_service()->file_system_instance(); | |
| 226 if (!instance) { | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 mojo::Array<mojo::String> mojo_paths(paths.size()); | |
| 231 for (int i = 0; i < paths.size(); ++i) { | |
| 232 mojo_paths[i] = paths[i].value(); | |
| 233 } | |
| 234 instance->RequestMediaScan(std::move(mojo_paths)); | |
| 235 } | |
| 236 | |
| 237 } // namespace arc | |
| OLD | NEW |