Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / media_galleries / gallery_watch_manager.cc
blob9a1f5eabd613ace2e57acaaa0ba804f972dc2e7c
1 // Copyright 2014 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.
5 #include "chrome/browser/media_galleries/gallery_watch_manager.h"
7 #include "base/bind.h"
8 #include "base/stl_util.h"
9 #include "base/time/time.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/media_galleries/gallery_watch_manager_observer.h"
12 #include "chrome/browser/media_galleries/media_file_system_registry.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "components/storage_monitor/storage_monitor.h"
15 #include "content/public/browser/browser_context.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "extensions/common/extension.h"
19 using content::BrowserContext;
20 using content::BrowserThread;
22 namespace {
24 // Don't send a notification more than once per 3 seconds (chosen arbitrarily).
25 const int kMinNotificiationDelayInSeconds = 3;
27 } // namespace.
29 const char GalleryWatchManager::kInvalidGalleryIDError[] = "Invalid gallery ID";
30 const char GalleryWatchManager::kNoPermissionError[] =
31 "No permission for gallery ID.";
32 const char GalleryWatchManager::kCouldNotWatchGalleryError[] =
33 "Could not watch gallery path.";
35 // Manages a collection of file path watchers on the FILE thread and relays
36 // the change events to |callback| on the UI thread. This file is constructed
37 // on the UI thread, but operates and is destroyed on the FILE thread.
38 // If |callback| is called with an error, all watches on that path have been
39 // dropped.
40 class GalleryWatchManager::FileWatchManager {
41 public:
42 explicit FileWatchManager(const base::FilePathWatcher::Callback& callback);
43 ~FileWatchManager();
45 // Posts success or failure via |callback| to the UI thread.
46 void AddFileWatch(const base::FilePath& path,
47 const base::Callback<void(bool)>& callback);
49 void RemoveFileWatch(const base::FilePath& path);
51 base::WeakPtr<FileWatchManager> GetWeakPtr();
53 private:
54 typedef std::map<base::FilePath, linked_ptr<base::FilePathWatcher> >
55 WatcherMap;
57 void OnFilePathChanged(const base::FilePath& path, bool error);
59 WatcherMap watchers_;
61 base::FilePathWatcher::Callback callback_;
63 base::WeakPtrFactory<FileWatchManager> weak_factory_;
65 DISALLOW_COPY_AND_ASSIGN(FileWatchManager);
68 GalleryWatchManager::FileWatchManager::FileWatchManager(
69 const base::FilePathWatcher::Callback& callback)
70 : callback_(callback), weak_factory_(this) {
71 DCHECK_CURRENTLY_ON(BrowserThread::UI);
74 GalleryWatchManager::FileWatchManager::~FileWatchManager() {
75 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
78 void GalleryWatchManager::FileWatchManager::AddFileWatch(
79 const base::FilePath& path,
80 const base::Callback<void(bool)>& callback) {
81 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
83 // This can occur if the GalleryWatchManager attempts to watch the same path
84 // again before recieving the callback. It's benign.
85 if (ContainsKey(watchers_, path)) {
86 BrowserThread::PostTask(
87 BrowserThread::UI, FROM_HERE, base::Bind(callback, false));
88 return;
91 linked_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
92 bool success = watcher->Watch(path,
93 true /*recursive*/,
94 base::Bind(&FileWatchManager::OnFilePathChanged,
95 weak_factory_.GetWeakPtr()));
97 if (success)
98 watchers_[path] = watcher;
100 BrowserThread::PostTask(
101 BrowserThread::UI, FROM_HERE, base::Bind(callback, success));
104 void GalleryWatchManager::FileWatchManager::RemoveFileWatch(
105 const base::FilePath& path) {
106 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
107 size_t erased = watchers_.erase(path);
108 DCHECK_EQ(erased, 1u);
111 base::WeakPtr<GalleryWatchManager::FileWatchManager>
112 GalleryWatchManager::FileWatchManager::GetWeakPtr() {
113 return weak_factory_.GetWeakPtr();
116 void GalleryWatchManager::FileWatchManager::OnFilePathChanged(
117 const base::FilePath& path,
118 bool error) {
119 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
120 if (error)
121 RemoveFileWatch(path);
122 BrowserThread::PostTask(
123 BrowserThread::UI, FROM_HERE, base::Bind(callback_, path, error));
126 GalleryWatchManager::WatchOwner::WatchOwner(BrowserContext* browser_context,
127 const std::string& extension_id,
128 MediaGalleryPrefId gallery_id)
129 : browser_context(browser_context),
130 extension_id(extension_id),
131 gallery_id(gallery_id) {
134 bool GalleryWatchManager::WatchOwner::operator<(const WatchOwner& other) const {
135 return browser_context < other.browser_context ||
136 extension_id < other.extension_id || gallery_id < other.gallery_id;
139 GalleryWatchManager::NotificationInfo::NotificationInfo()
140 : delayed_notification_pending(false) {
143 GalleryWatchManager::NotificationInfo::~NotificationInfo() {
146 GalleryWatchManager::GalleryWatchManager()
147 : storage_monitor_observed_(false), weak_factory_(this) {
148 DCHECK_CURRENTLY_ON(BrowserThread::UI);
149 watch_manager_.reset(new FileWatchManager(base::Bind(
150 &GalleryWatchManager::OnFilePathChanged, weak_factory_.GetWeakPtr())));
153 GalleryWatchManager::~GalleryWatchManager() {
154 weak_factory_.InvalidateWeakPtrs();
156 if (storage_monitor_observed_ &&
157 storage_monitor::StorageMonitor::GetInstance()) {
158 storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this);
161 BrowserThread::DeleteSoon(
162 BrowserThread::FILE, FROM_HERE, watch_manager_.release());
165 void GalleryWatchManager::AddObserver(BrowserContext* browser_context,
166 GalleryWatchManagerObserver* observer) {
167 DCHECK(browser_context);
168 DCHECK(observer);
169 DCHECK(!ContainsKey(observers_, browser_context));
170 observers_[browser_context] = observer;
173 void GalleryWatchManager::RemoveObserver(BrowserContext* browser_context) {
174 DCHECK(browser_context);
175 size_t erased = observers_.erase(browser_context);
176 DCHECK_EQ(erased, 1u);
179 void GalleryWatchManager::ShutdownBrowserContext(
180 BrowserContext* browser_context) {
181 DCHECK_CURRENTLY_ON(BrowserThread::UI);
182 DCHECK(browser_context);
184 MediaGalleriesPreferences* preferences =
185 g_browser_process->media_file_system_registry()->GetPreferences(
186 Profile::FromBrowserContext(browser_context));
187 size_t observed = observed_preferences_.erase(preferences);
188 if (observed > 0)
189 preferences->RemoveGalleryChangeObserver(this);
192 void GalleryWatchManager::AddWatch(BrowserContext* browser_context,
193 const extensions::Extension* extension,
194 MediaGalleryPrefId gallery_id,
195 const ResultCallback& callback) {
196 DCHECK_CURRENTLY_ON(BrowserThread::UI);
197 DCHECK(browser_context);
198 DCHECK(extension);
200 WatchOwner owner(browser_context, extension->id(), gallery_id);
201 if (ContainsKey(watches_, owner)) {
202 callback.Run(std::string());
203 return;
206 MediaGalleriesPreferences* preferences =
207 g_browser_process->media_file_system_registry()->GetPreferences(
208 Profile::FromBrowserContext(browser_context));
210 if (!ContainsKey(preferences->known_galleries(), gallery_id)) {
211 callback.Run(kInvalidGalleryIDError);
212 return;
215 MediaGalleryPrefIdSet permitted =
216 preferences->GalleriesForExtension(*extension);
217 if (!ContainsKey(permitted, gallery_id)) {
218 callback.Run(kNoPermissionError);
219 return;
222 base::FilePath path =
223 preferences->known_galleries().find(gallery_id)->second.AbsolutePath();
225 if (!storage_monitor_observed_) {
226 storage_monitor_observed_ = true;
227 storage_monitor::StorageMonitor::GetInstance()->AddObserver(this);
230 // Observe the preferences if we haven't already.
231 if (!ContainsKey(observed_preferences_, preferences)) {
232 observed_preferences_.insert(preferences);
233 preferences->AddGalleryChangeObserver(this);
236 watches_[owner] = path;
238 // Start the FilePathWatcher on |gallery_path| if necessary.
239 if (ContainsKey(watched_paths_, path)) {
240 OnFileWatchActivated(owner, path, callback, true);
241 } else {
242 base::Callback<void(bool)> on_watch_added =
243 base::Bind(&GalleryWatchManager::OnFileWatchActivated,
244 weak_factory_.GetWeakPtr(),
245 owner,
246 path,
247 callback);
248 BrowserThread::PostTask(BrowserThread::FILE,
249 FROM_HERE,
250 base::Bind(&FileWatchManager::AddFileWatch,
251 watch_manager_->GetWeakPtr(),
252 path,
253 on_watch_added));
257 void GalleryWatchManager::RemoveWatch(BrowserContext* browser_context,
258 const std::string& extension_id,
259 MediaGalleryPrefId gallery_id) {
260 DCHECK_CURRENTLY_ON(BrowserThread::UI);
261 DCHECK(browser_context);
263 WatchOwner owner(browser_context, extension_id, gallery_id);
264 WatchesMap::iterator it = watches_.find(owner);
265 if (it != watches_.end()) {
266 DeactivateFileWatch(owner, it->second);
267 watches_.erase(it);
271 void GalleryWatchManager::RemoveAllWatches(BrowserContext* browser_context,
272 const std::string& extension_id) {
273 DCHECK_CURRENTLY_ON(BrowserThread::UI);
274 DCHECK(browser_context);
276 WatchesMap::iterator it = watches_.begin();
277 while (it != watches_.end()) {
278 if (it->first.extension_id == extension_id) {
279 DeactivateFileWatch(it->first, it->second);
280 // Post increment moves iterator to next element while deleting current.
281 watches_.erase(it++);
282 } else {
283 ++it;
288 MediaGalleryPrefIdSet GalleryWatchManager::GetWatchSet(
289 BrowserContext* browser_context,
290 const std::string& extension_id) {
291 DCHECK_CURRENTLY_ON(BrowserThread::UI);
292 DCHECK(browser_context);
294 MediaGalleryPrefIdSet result;
295 for (WatchesMap::const_iterator it = watches_.begin(); it != watches_.end();
296 ++it) {
297 if (it->first.browser_context == browser_context &&
298 it->first.extension_id == extension_id) {
299 result.insert(it->first.gallery_id);
302 return result;
305 void GalleryWatchManager::DeactivateFileWatch(const WatchOwner& owner,
306 const base::FilePath& path) {
307 DCHECK_CURRENTLY_ON(BrowserThread::UI);
308 WatchedPaths::iterator it = watched_paths_.find(path);
309 if (it == watched_paths_.end())
310 return;
312 it->second.owners.erase(owner);
313 if (it->second.owners.empty()) {
314 watched_paths_.erase(it);
315 BrowserThread::PostTask(BrowserThread::FILE,
316 FROM_HERE,
317 base::Bind(&FileWatchManager::RemoveFileWatch,
318 watch_manager_->GetWeakPtr(),
319 path));
323 void GalleryWatchManager::OnFileWatchActivated(const WatchOwner& owner,
324 const base::FilePath& path,
325 const ResultCallback& callback,
326 bool success) {
327 if (success) {
328 // |watched_paths_| doesn't necessarily to contain |path| yet.
329 // In that case, it calls the default constructor for NotificationInfo.
330 watched_paths_[path].owners.insert(owner);
331 callback.Run(std::string());
332 } else {
333 callback.Run(kCouldNotWatchGalleryError);
337 void GalleryWatchManager::OnFilePathChanged(const base::FilePath& path,
338 bool error) {
339 WatchedPaths::iterator notification_info = watched_paths_.find(path);
340 if (notification_info == watched_paths_.end())
341 return;
343 // On error, all watches on that path are dropped, so update records and
344 // notify observers.
345 if (error) {
346 // Make a copy, as |watched_paths_| is modified as we erase watches.
347 std::set<WatchOwner> owners = notification_info->second.owners;
348 for (std::set<WatchOwner>::iterator it = owners.begin(); it != owners.end();
349 ++it) {
350 Profile* profile = Profile::FromBrowserContext(it->browser_context);
351 RemoveWatch(it->browser_context, it->extension_id, it->gallery_id);
352 if (ContainsKey(observers_, profile))
353 observers_[profile]->OnGalleryWatchDropped(it->extension_id,
354 it->gallery_id);
357 return;
360 base::TimeDelta time_since_last_notify =
361 base::Time::Now() - notification_info->second.last_notify_time;
362 if (time_since_last_notify <
363 base::TimeDelta::FromSeconds(kMinNotificiationDelayInSeconds)) {
364 if (!notification_info->second.delayed_notification_pending) {
365 notification_info->second.delayed_notification_pending = true;
366 base::TimeDelta delay_to_next_valid_time =
367 notification_info->second.last_notify_time +
368 base::TimeDelta::FromSeconds(kMinNotificiationDelayInSeconds) -
369 base::Time::Now();
370 BrowserThread::PostDelayedTask(
371 BrowserThread::UI,
372 FROM_HERE,
373 base::Bind(&GalleryWatchManager::OnFilePathChanged,
374 weak_factory_.GetWeakPtr(),
375 path,
376 error),
377 delay_to_next_valid_time);
379 return;
381 notification_info->second.delayed_notification_pending = false;
382 notification_info->second.last_notify_time = base::Time::Now();
384 std::set<WatchOwner>::const_iterator it;
385 for (it = notification_info->second.owners.begin();
386 it != notification_info->second.owners.end();
387 ++it) {
388 DCHECK(ContainsKey(watches_, *it));
389 if (ContainsKey(observers_, it->browser_context)) {
390 observers_[it->browser_context]->OnGalleryChanged(it->extension_id,
391 it->gallery_id);
396 void GalleryWatchManager::OnPermissionRemoved(MediaGalleriesPreferences* pref,
397 const std::string& extension_id,
398 MediaGalleryPrefId pref_id) {
399 RemoveWatch(pref->profile(), extension_id, pref_id);
400 if (ContainsKey(observers_, pref->profile()))
401 observers_[pref->profile()]->OnGalleryWatchDropped(extension_id, pref_id);
404 void GalleryWatchManager::OnGalleryRemoved(MediaGalleriesPreferences* pref,
405 MediaGalleryPrefId pref_id) {
406 // Removing a watch may update |watches_|, so extract the extension ids first.
407 std::set<std::string> extension_ids;
408 for (WatchesMap::const_iterator it = watches_.begin(); it != watches_.end();
409 ++it) {
410 if (it->first.browser_context == pref->profile() &&
411 it->first.gallery_id == pref_id) {
412 extension_ids.insert(it->first.extension_id);
416 for (std::set<std::string>::const_iterator it = extension_ids.begin();
417 it != extension_ids.end();
418 ++it) {
419 RemoveWatch(pref->profile(), *it, pref_id);
420 if (ContainsKey(observers_, pref->profile()))
421 observers_[pref->profile()]->OnGalleryWatchDropped(*it, pref_id);
425 void GalleryWatchManager::OnRemovableStorageDetached(
426 const storage_monitor::StorageInfo& info) {
427 WatchesMap::iterator it = watches_.begin();
428 while (it != watches_.end()) {
429 MediaGalleriesPreferences* preferences =
430 g_browser_process->media_file_system_registry()->GetPreferences(
431 Profile::FromBrowserContext(it->first.browser_context));
432 MediaGalleryPrefIdSet detached_ids =
433 preferences->LookUpGalleriesByDeviceId(info.device_id());
435 if (ContainsKey(detached_ids, it->first.gallery_id)) {
436 DeactivateFileWatch(it->first, it->second);
437 // Post increment moves iterator to next element while deleting current.
438 watches_.erase(it++);
439 observers_[preferences->profile()]->OnGalleryWatchDropped(
440 it->first.extension_id, it->first.gallery_id);
441 } else {
442 ++it;