OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/arc/arc_downloads_watcher_service.h" | 5 #include "chrome/browser/chromeos/arc/arc_downloads_watcher_service.h" |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 #include <memory> | 8 #include <memory> |
9 #include <utility> | 9 #include <utility> |
10 | 10 |
(...skipping 15 matching lines...) Expand all Loading... |
26 // Mapping from Android file paths to last modified timestamps. | 26 // Mapping from Android file paths to last modified timestamps. |
27 using TimestampMap = std::map<base::FilePath, base::Time>; | 27 using TimestampMap = std::map<base::FilePath, base::Time>; |
28 | 28 |
29 namespace arc { | 29 namespace arc { |
30 | 30 |
31 namespace { | 31 namespace { |
32 | 32 |
33 const base::FilePath::CharType kAndroidDownloadDir[] = | 33 const base::FilePath::CharType kAndroidDownloadDir[] = |
34 FILE_PATH_LITERAL("/storage/emulated/0/Download"); | 34 FILE_PATH_LITERAL("/storage/emulated/0/Download"); |
35 | 35 |
| 36 // How long to wait for new inotify events before building the updated timestamp |
| 37 // map. |
| 38 const base::TimeDelta kBuildTimestampMapDelay = |
| 39 base::TimeDelta::FromMilliseconds(1000); |
| 40 |
36 // Compares two TimestampMaps and returns the list of file paths added/removed | 41 // Compares two TimestampMaps and returns the list of file paths added/removed |
37 // or whose timestamp have changed. | 42 // or whose timestamp have changed. |
38 std::vector<base::FilePath> CollectChangedPaths( | 43 std::vector<base::FilePath> CollectChangedPaths( |
39 const TimestampMap& timestamp_map_a, | 44 const TimestampMap& timestamp_map_a, |
40 const TimestampMap& timestamp_map_b) { | 45 const TimestampMap& timestamp_map_b) { |
41 std::vector<base::FilePath> changed_paths; | 46 std::vector<base::FilePath> changed_paths; |
42 | 47 |
43 TimestampMap::const_iterator iter_a = timestamp_map_a.begin(); | 48 TimestampMap::const_iterator iter_a = timestamp_map_a.begin(); |
44 TimestampMap::const_iterator iter_b = timestamp_map_b.begin(); | 49 TimestampMap::const_iterator iter_b = timestamp_map_b.begin(); |
45 while (iter_a != timestamp_map_a.end() && iter_b != timestamp_map_b.end()) { | 50 while (iter_a != timestamp_map_a.end() && iter_b != timestamp_map_b.end()) { |
(...skipping 17 matching lines...) Expand all Loading... |
63 ++iter_a; | 68 ++iter_a; |
64 } | 69 } |
65 while (iter_b != timestamp_map_b.end()) { | 70 while (iter_b != timestamp_map_b.end()) { |
66 changed_paths.emplace_back(iter_b->first); | 71 changed_paths.emplace_back(iter_b->first); |
67 ++iter_b; | 72 ++iter_b; |
68 } | 73 } |
69 | 74 |
70 return changed_paths; | 75 return changed_paths; |
71 } | 76 } |
72 | 77 |
| 78 // Scans files under |downloads_dir| recursively and builds a map from file |
| 79 // paths (in Android filesystem) to last modified timestamps. |
| 80 TimestampMap BuildTimestampMap(base::FilePath downloads_dir) { |
| 81 DCHECK(!downloads_dir.EndsWithSeparator()); |
| 82 TimestampMap timestamp_map; |
| 83 |
| 84 // Enumerate normal files only; directories and symlinks are skipped. |
| 85 base::FileEnumerator enumerator(downloads_dir, true, |
| 86 base::FileEnumerator::FILES); |
| 87 for (base::FilePath cros_path = enumerator.Next(); !cros_path.empty(); |
| 88 cros_path = enumerator.Next()) { |
| 89 // Android file path can be obtained by replacing |downloads_dir| prefix |
| 90 // with |kAndroidDownloadDir|. |
| 91 base::FilePath android_path(kAndroidDownloadDir); |
| 92 downloads_dir.AppendRelativePath(cros_path, &android_path); |
| 93 const base::FileEnumerator::FileInfo& info = enumerator.GetInfo(); |
| 94 timestamp_map[android_path] = info.GetLastModifiedTime(); |
| 95 } |
| 96 return timestamp_map; |
| 97 } |
| 98 |
| 99 std::pair<base::TimeTicks, TimestampMap> BuildTimestampMapCallback( |
| 100 base::FilePath downloads_dir) { |
| 101 // The TimestampMap may include changes form after snapshot_time. |
| 102 // We must take the snapshot_time before we build the TimestampMap since |
| 103 // changes that occur while building the map may not be captured. |
| 104 base::TimeTicks snapshot_time = base::TimeTicks::Now(); |
| 105 TimestampMap current_timestamp_map = BuildTimestampMap(downloads_dir); |
| 106 return std::make_pair(snapshot_time, std::move(current_timestamp_map)); |
| 107 } |
| 108 |
73 } // namespace | 109 } // namespace |
74 | 110 |
75 // The core part of ArcDownloadsWatcherService to watch for file changes in | 111 // The core part of ArcDownloadsWatcherService to watch for file changes in |
76 // Downloads directory. | 112 // Downloads directory. |
77 class ArcDownloadsWatcherService::DownloadsWatcher { | 113 class ArcDownloadsWatcherService::DownloadsWatcher { |
78 public: | 114 public: |
79 using Callback = base::Callback<void(mojo::Array<mojo::String> paths)>; | 115 using Callback = base::Callback<void(mojo::Array<mojo::String> paths)>; |
80 | 116 |
81 explicit DownloadsWatcher(const Callback& callback); | 117 explicit DownloadsWatcher(const Callback& callback); |
82 ~DownloadsWatcher(); | 118 ~DownloadsWatcher(); |
83 | 119 |
84 // Starts watching Downloads directory. | 120 // Starts watching Downloads directory. |
85 void Start(); | 121 void Start(); |
86 | 122 |
87 private: | 123 private: |
88 // Called by base::FilePathWatcher to notify file changes. | 124 // Called by base::FilePathWatcher to notify file changes. |
| 125 // Kicks off the update of last_timestamp_map_ if one is not already in |
| 126 // progress. |
89 void OnFilePathChanged(const base::FilePath& path, bool error); | 127 void OnFilePathChanged(const base::FilePath& path, bool error); |
90 | 128 |
91 // Scans files under |downloads_dir_| recursively and builds a map from file | 129 // Called with a delay to allow additional inotify events for the same user |
92 // paths (in Android filesystem) to last modified timestamps. | 130 // action to queue up so that they can be dealt with in batch. |
93 TimestampMap BuildTimestampMap() const; | 131 void DelayBuildTimestampMap(); |
| 132 |
| 133 // Called after a new timestamp map has been created and causes any recently |
| 134 // modified files to be sent to the media scanner. |
| 135 void OnBuildTimestampMap( |
| 136 std::pair<base::TimeTicks, TimestampMap> timestamp_and_map); |
94 | 137 |
95 Callback callback_; | 138 Callback callback_; |
96 base::FilePath downloads_dir_; | 139 base::FilePath downloads_dir_; |
97 std::unique_ptr<base::FilePathWatcher> watcher_; | 140 std::unique_ptr<base::FilePathWatcher> watcher_; |
98 TimestampMap last_timestamp_map_; | 141 TimestampMap last_timestamp_map_; |
| 142 // The timestamp of the last OnFilePathChanged callback received. |
| 143 base::TimeTicks last_notify_time_; |
| 144 // Whether or not there is an outstanding task to update last_timestamp_map_. |
| 145 bool outstanding_task_; |
99 | 146 |
100 // Note: This should remain the last member so it'll be destroyed and | 147 // Note: This should remain the last member so it'll be destroyed and |
101 // invalidate the weak pointers before any other members are destroyed. | 148 // invalidate the weak pointers before any other members are destroyed. |
102 base::WeakPtrFactory<DownloadsWatcher> weak_ptr_factory_; | 149 base::WeakPtrFactory<DownloadsWatcher> weak_ptr_factory_; |
103 | 150 |
104 DISALLOW_COPY_AND_ASSIGN(DownloadsWatcher); | 151 DISALLOW_COPY_AND_ASSIGN(DownloadsWatcher); |
105 }; | 152 }; |
106 | 153 |
107 ArcDownloadsWatcherService::DownloadsWatcher::DownloadsWatcher( | 154 ArcDownloadsWatcherService::DownloadsWatcher::DownloadsWatcher( |
108 const Callback& callback) | 155 const Callback& callback) |
109 : callback_(callback), weak_ptr_factory_(this) { | 156 : callback_(callback), |
| 157 last_notify_time_(base::TimeTicks()), |
| 158 outstanding_task_(false), |
| 159 weak_ptr_factory_(this) { |
110 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 160 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
111 | 161 |
112 downloads_dir_ = DownloadPrefs(ProfileManager::GetActiveUserProfile()) | 162 downloads_dir_ = DownloadPrefs(ProfileManager::GetActiveUserProfile()) |
113 .GetDefaultDownloadDirectoryForProfile() | 163 .GetDefaultDownloadDirectoryForProfile() |
114 .StripTrailingSeparators(); | 164 .StripTrailingSeparators(); |
115 } | 165 } |
116 | 166 |
117 ArcDownloadsWatcherService::DownloadsWatcher::~DownloadsWatcher() { | 167 ArcDownloadsWatcherService::DownloadsWatcher::~DownloadsWatcher() { |
118 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 168 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
119 } | 169 } |
120 | 170 |
121 void ArcDownloadsWatcherService::DownloadsWatcher::Start() { | 171 void ArcDownloadsWatcherService::DownloadsWatcher::Start() { |
122 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 172 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
123 | 173 |
124 // Initialize with the current timestamp map and avoid initial notification. | 174 // Initialize with the current timestamp map and avoid initial notification. |
125 // It is not needed since MediaProvider scans whole storage area on boot. | 175 // It is not needed since MediaProvider scans whole storage area on boot. |
126 last_timestamp_map_ = BuildTimestampMap(); | 176 last_notify_time_ = base::TimeTicks::Now(); |
| 177 last_timestamp_map_ = BuildTimestampMap(downloads_dir_); |
127 | 178 |
128 watcher_ = base::MakeUnique<base::FilePathWatcher>(); | 179 watcher_ = base::MakeUnique<base::FilePathWatcher>(); |
129 // On Linux, base::FilePathWatcher::Watch() always returns true. | 180 // On Linux, base::FilePathWatcher::Watch() always returns true. |
130 watcher_->Watch(downloads_dir_, true, | 181 watcher_->Watch(downloads_dir_, true, |
131 base::Bind(&DownloadsWatcher::OnFilePathChanged, | 182 base::Bind(&DownloadsWatcher::OnFilePathChanged, |
132 weak_ptr_factory_.GetWeakPtr())); | 183 weak_ptr_factory_.GetWeakPtr())); |
133 } | 184 } |
134 | 185 |
135 void ArcDownloadsWatcherService::DownloadsWatcher::OnFilePathChanged( | 186 void ArcDownloadsWatcherService::DownloadsWatcher::OnFilePathChanged( |
136 const base::FilePath& path, | 187 const base::FilePath& path, |
137 bool error) { | 188 bool error) { |
138 // On Linux, |error| is always false. Also, |path| is always the same path | 189 // On Linux, |error| is always false. Also, |path| is always the same path |
139 // as one given to FilePathWatcher::Watch(). | 190 // as one given to FilePathWatcher::Watch(). |
140 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 191 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 192 if (!outstanding_task_) { |
| 193 outstanding_task_ = true; |
| 194 BrowserThread::PostDelayedTask( |
| 195 BrowserThread::FILE, FROM_HERE, |
| 196 base::Bind(&DownloadsWatcher::DelayBuildTimestampMap, |
| 197 weak_ptr_factory_.GetWeakPtr()), |
| 198 kBuildTimestampMapDelay); |
| 199 } else { |
| 200 last_notify_time_ = base::TimeTicks::Now(); |
| 201 } |
| 202 } |
141 | 203 |
142 TimestampMap current_timestamp_map = BuildTimestampMap(); | 204 void ArcDownloadsWatcherService::DownloadsWatcher::DelayBuildTimestampMap() { |
| 205 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 206 DCHECK(outstanding_task_); |
| 207 base::PostTaskAndReplyWithResult( |
| 208 BrowserThread::GetBlockingPool(), FROM_HERE, |
| 209 base::Bind(&BuildTimestampMapCallback, downloads_dir_), |
| 210 base::Bind(&DownloadsWatcher::OnBuildTimestampMap, |
| 211 weak_ptr_factory_.GetWeakPtr())); |
| 212 } |
143 | 213 |
| 214 void ArcDownloadsWatcherService::DownloadsWatcher::OnBuildTimestampMap( |
| 215 std::pair<base::TimeTicks, TimestampMap> timestamp_and_map) { |
| 216 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 217 DCHECK(outstanding_task_); |
| 218 base::TimeTicks snapshot_time = timestamp_and_map.first; |
| 219 TimestampMap current_timestamp_map = std::move(timestamp_and_map.second); |
144 std::vector<base::FilePath> changed_paths = | 220 std::vector<base::FilePath> changed_paths = |
145 CollectChangedPaths(last_timestamp_map_, current_timestamp_map); | 221 CollectChangedPaths(last_timestamp_map_, current_timestamp_map); |
146 | 222 |
147 last_timestamp_map_ = std::move(current_timestamp_map); | 223 last_timestamp_map_ = std::move(current_timestamp_map); |
148 | 224 |
149 mojo::Array<mojo::String> mojo_paths(changed_paths.size()); | 225 mojo::Array<mojo::String> mojo_paths(changed_paths.size()); |
150 for (size_t i = 0; i < changed_paths.size(); ++i) { | 226 for (size_t i = 0; i < changed_paths.size(); ++i) { |
151 mojo_paths[i] = changed_paths[i].value(); | 227 mojo_paths[i] = changed_paths[i].value(); |
152 } | 228 } |
153 BrowserThread::PostTask( | 229 BrowserThread::PostTask( |
154 BrowserThread::UI, FROM_HERE, | 230 BrowserThread::UI, FROM_HERE, |
155 base::Bind(callback_, base::Passed(std::move(mojo_paths)))); | 231 base::Bind(callback_, base::Passed(std::move(mojo_paths)))); |
156 } | 232 if (last_notify_time_ > snapshot_time) { |
157 | 233 base::PostTaskAndReplyWithResult( |
158 TimestampMap ArcDownloadsWatcherService::DownloadsWatcher::BuildTimestampMap() | 234 BrowserThread::GetBlockingPool(), FROM_HERE, |
159 const { | 235 base::Bind(&BuildTimestampMapCallback, downloads_dir_), |
160 DCHECK(!downloads_dir_.EndsWithSeparator()); | 236 base::Bind(&DownloadsWatcher::OnBuildTimestampMap, |
161 TimestampMap timestamp_map; | 237 weak_ptr_factory_.GetWeakPtr())); |
162 | 238 } else { |
163 // Enumerate normal files only; directories and symlinks are skipped. | 239 outstanding_task_ = false; |
164 base::FileEnumerator enumerator(downloads_dir_, true, | |
165 base::FileEnumerator::FILES); | |
166 for (base::FilePath cros_path = enumerator.Next(); !cros_path.empty(); | |
167 cros_path = enumerator.Next()) { | |
168 // Android file path can be obtained by replacing |downloads_dir_| prefix | |
169 // with |kAndroidDownloadDir|. | |
170 const base::FilePath& android_path = | |
171 base::FilePath(kAndroidDownloadDir) | |
172 .Append( | |
173 cros_path.value().substr(downloads_dir_.value().length() + 1)); | |
174 const base::FileEnumerator::FileInfo& info = enumerator.GetInfo(); | |
175 timestamp_map[android_path] = info.GetLastModifiedTime(); | |
176 } | 240 } |
177 return timestamp_map; | |
178 } | 241 } |
179 | 242 |
180 ArcDownloadsWatcherService::ArcDownloadsWatcherService( | 243 ArcDownloadsWatcherService::ArcDownloadsWatcherService( |
181 ArcBridgeService* bridge_service) | 244 ArcBridgeService* bridge_service) |
182 : ArcService(bridge_service), weak_ptr_factory_(this) { | 245 : ArcService(bridge_service), weak_ptr_factory_(this) { |
183 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 246 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
184 arc_bridge_service()->file_system()->AddObserver(this); | 247 arc_bridge_service()->file_system()->AddObserver(this); |
185 } | 248 } |
186 | 249 |
187 ArcDownloadsWatcherService::~ArcDownloadsWatcherService() { | 250 ArcDownloadsWatcherService::~ArcDownloadsWatcherService() { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
225 mojo::Array<mojo::String> paths) { | 288 mojo::Array<mojo::String> paths) { |
226 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 289 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
227 | 290 |
228 auto* instance = arc_bridge_service()->file_system()->instance(); | 291 auto* instance = arc_bridge_service()->file_system()->instance(); |
229 if (!instance) | 292 if (!instance) |
230 return; | 293 return; |
231 instance->RequestMediaScan(std::move(paths)); | 294 instance->RequestMediaScan(std::move(paths)); |
232 } | 295 } |
233 | 296 |
234 } // namespace arc | 297 } // namespace arc |
OLD | NEW |