Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / media_galleries / gallery_watch_manager.cc
blob8ca263f2e7c2c56a85da55c7435d3429417bf3b6
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/media_galleries/media_galleries_preferences_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
16 #include "components/storage_monitor/storage_monitor.h"
17 #include "content/public/browser/browser_context.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "extensions/common/extension.h"
21 using content::BrowserContext;
22 using content::BrowserThread;
24 namespace {
26 // Don't send a notification more than once per 3 seconds (chosen arbitrarily).
27 const int kMinNotificiationDelayInSeconds = 3;
29 class ShutdownNotifierFactory
30 : public BrowserContextKeyedServiceShutdownNotifierFactory {
31 public:
32 static ShutdownNotifierFactory* GetInstance() {
33 return Singleton<ShutdownNotifierFactory>::get();
36 private:
37 friend struct DefaultSingletonTraits<ShutdownNotifierFactory>;
39 ShutdownNotifierFactory()
40 : BrowserContextKeyedServiceShutdownNotifierFactory(
41 "GalleryWatchManager") {
42 DependsOn(MediaGalleriesPreferencesFactory::GetInstance());
44 ~ShutdownNotifierFactory() override {}
46 DISALLOW_COPY_AND_ASSIGN(ShutdownNotifierFactory);
49 } // namespace.
51 const char GalleryWatchManager::kInvalidGalleryIDError[] = "Invalid gallery ID";
52 const char GalleryWatchManager::kNoPermissionError[] =
53 "No permission for gallery ID.";
54 const char GalleryWatchManager::kCouldNotWatchGalleryError[] =
55 "Could not watch gallery path.";
57 // Manages a collection of file path watchers on the FILE thread and relays
58 // the change events to |callback| on the UI thread. This file is constructed
59 // on the UI thread, but operates and is destroyed on the FILE thread.
60 // If |callback| is called with an error, all watches on that path have been
61 // dropped.
62 class GalleryWatchManager::FileWatchManager {
63 public:
64 explicit FileWatchManager(const base::FilePathWatcher::Callback& callback);
65 ~FileWatchManager();
67 // Posts success or failure via |callback| to the UI thread.
68 void AddFileWatch(const base::FilePath& path,
69 const base::Callback<void(bool)>& callback);
71 void RemoveFileWatch(const base::FilePath& path);
73 base::WeakPtr<FileWatchManager> GetWeakPtr();
75 private:
76 typedef std::map<base::FilePath, linked_ptr<base::FilePathWatcher> >
77 WatcherMap;
79 void OnFilePathChanged(const base::FilePath& path, bool error);
81 WatcherMap watchers_;
83 base::FilePathWatcher::Callback callback_;
85 base::WeakPtrFactory<FileWatchManager> weak_factory_;
87 DISALLOW_COPY_AND_ASSIGN(FileWatchManager);
90 GalleryWatchManager::FileWatchManager::FileWatchManager(
91 const base::FilePathWatcher::Callback& callback)
92 : callback_(callback), weak_factory_(this) {
93 DCHECK_CURRENTLY_ON(BrowserThread::UI);
96 GalleryWatchManager::FileWatchManager::~FileWatchManager() {
97 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
100 void GalleryWatchManager::FileWatchManager::AddFileWatch(
101 const base::FilePath& path,
102 const base::Callback<void(bool)>& callback) {
103 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
105 // This can occur if the GalleryWatchManager attempts to watch the same path
106 // again before recieving the callback. It's benign.
107 if (ContainsKey(watchers_, path)) {
108 BrowserThread::PostTask(
109 BrowserThread::UI, FROM_HERE, base::Bind(callback, false));
110 return;
113 linked_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
114 bool success = watcher->Watch(path,
115 true /*recursive*/,
116 base::Bind(&FileWatchManager::OnFilePathChanged,
117 weak_factory_.GetWeakPtr()));
119 if (success)
120 watchers_[path] = watcher;
122 BrowserThread::PostTask(
123 BrowserThread::UI, FROM_HERE, base::Bind(callback, success));
126 void GalleryWatchManager::FileWatchManager::RemoveFileWatch(
127 const base::FilePath& path) {
128 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
129 size_t erased = watchers_.erase(path);
130 DCHECK_EQ(erased, 1u);
133 base::WeakPtr<GalleryWatchManager::FileWatchManager>
134 GalleryWatchManager::FileWatchManager::GetWeakPtr() {
135 return weak_factory_.GetWeakPtr();
138 void GalleryWatchManager::FileWatchManager::OnFilePathChanged(
139 const base::FilePath& path,
140 bool error) {
141 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
142 if (error)
143 RemoveFileWatch(path);
144 BrowserThread::PostTask(
145 BrowserThread::UI, FROM_HERE, base::Bind(callback_, path, error));
148 GalleryWatchManager::WatchOwner::WatchOwner(BrowserContext* browser_context,
149 const std::string& extension_id,
150 MediaGalleryPrefId gallery_id)
151 : browser_context(browser_context),
152 extension_id(extension_id),
153 gallery_id(gallery_id) {
156 bool GalleryWatchManager::WatchOwner::operator<(const WatchOwner& other) const {
157 return browser_context < other.browser_context ||
158 extension_id < other.extension_id || gallery_id < other.gallery_id;
161 GalleryWatchManager::NotificationInfo::NotificationInfo()
162 : delayed_notification_pending(false) {
165 GalleryWatchManager::NotificationInfo::~NotificationInfo() {
168 GalleryWatchManager::GalleryWatchManager()
169 : storage_monitor_observed_(false), weak_factory_(this) {
170 DCHECK_CURRENTLY_ON(BrowserThread::UI);
171 watch_manager_.reset(new FileWatchManager(base::Bind(
172 &GalleryWatchManager::OnFilePathChanged, weak_factory_.GetWeakPtr())));
175 GalleryWatchManager::~GalleryWatchManager() {
176 weak_factory_.InvalidateWeakPtrs();
178 if (storage_monitor_observed_ &&
179 storage_monitor::StorageMonitor::GetInstance()) {
180 storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this);
183 BrowserThread::DeleteSoon(
184 BrowserThread::FILE, FROM_HERE, watch_manager_.release());
187 void GalleryWatchManager::AddObserver(BrowserContext* browser_context,
188 GalleryWatchManagerObserver* observer) {
189 DCHECK(browser_context);
190 DCHECK(observer);
191 DCHECK(!ContainsKey(observers_, browser_context));
192 observers_[browser_context] = observer;
195 void GalleryWatchManager::RemoveObserver(BrowserContext* browser_context) {
196 DCHECK(browser_context);
197 size_t erased = observers_.erase(browser_context);
198 DCHECK_EQ(erased, 1u);
201 void GalleryWatchManager::ShutdownBrowserContext(
202 BrowserContext* browser_context) {
203 DCHECK_CURRENTLY_ON(BrowserThread::UI);
204 DCHECK(browser_context);
206 MediaGalleriesPreferences* preferences =
207 g_browser_process->media_file_system_registry()->GetPreferences(
208 Profile::FromBrowserContext(browser_context));
209 size_t observed = observed_preferences_.erase(preferences);
210 if (observed > 0)
211 preferences->RemoveGalleryChangeObserver(this);
213 WatchesMap::iterator it = watches_.begin();
214 while (it != watches_.end()) {
215 if (it->first.browser_context == browser_context) {
216 DeactivateFileWatch(it->first, it->second);
217 // Post increment moves iterator to next element while deleting current.
218 watches_.erase(it++);
219 } else {
220 ++it;
224 browser_context_subscription_map_.erase(browser_context);
227 void GalleryWatchManager::AddWatch(BrowserContext* browser_context,
228 const extensions::Extension* extension,
229 MediaGalleryPrefId gallery_id,
230 const ResultCallback& callback) {
231 DCHECK_CURRENTLY_ON(BrowserThread::UI);
232 DCHECK(browser_context);
233 DCHECK(extension);
235 WatchOwner owner(browser_context, extension->id(), gallery_id);
236 if (ContainsKey(watches_, owner)) {
237 callback.Run(std::string());
238 return;
241 MediaGalleriesPreferences* preferences =
242 g_browser_process->media_file_system_registry()->GetPreferences(
243 Profile::FromBrowserContext(browser_context));
245 if (!ContainsKey(preferences->known_galleries(), gallery_id)) {
246 callback.Run(kInvalidGalleryIDError);
247 return;
250 MediaGalleryPrefIdSet permitted =
251 preferences->GalleriesForExtension(*extension);
252 if (!ContainsKey(permitted, gallery_id)) {
253 callback.Run(kNoPermissionError);
254 return;
257 base::FilePath path =
258 preferences->known_galleries().find(gallery_id)->second.AbsolutePath();
260 if (!storage_monitor_observed_) {
261 storage_monitor_observed_ = true;
262 storage_monitor::StorageMonitor::GetInstance()->AddObserver(this);
265 // Observe the preferences if we haven't already.
266 if (!ContainsKey(observed_preferences_, preferences)) {
267 observed_preferences_.insert(preferences);
268 preferences->AddGalleryChangeObserver(this);
271 watches_[owner] = path;
272 EnsureBrowserContextSubscription(owner.browser_context);
274 // Start the FilePathWatcher on |gallery_path| if necessary.
275 if (ContainsKey(watched_paths_, path)) {
276 OnFileWatchActivated(owner, path, callback, true);
277 } else {
278 base::Callback<void(bool)> on_watch_added =
279 base::Bind(&GalleryWatchManager::OnFileWatchActivated,
280 weak_factory_.GetWeakPtr(),
281 owner,
282 path,
283 callback);
284 BrowserThread::PostTask(BrowserThread::FILE,
285 FROM_HERE,
286 base::Bind(&FileWatchManager::AddFileWatch,
287 watch_manager_->GetWeakPtr(),
288 path,
289 on_watch_added));
293 void GalleryWatchManager::RemoveWatch(BrowserContext* browser_context,
294 const std::string& extension_id,
295 MediaGalleryPrefId gallery_id) {
296 DCHECK_CURRENTLY_ON(BrowserThread::UI);
297 DCHECK(browser_context);
299 WatchOwner owner(browser_context, extension_id, gallery_id);
300 WatchesMap::iterator it = watches_.find(owner);
301 if (it != watches_.end()) {
302 DeactivateFileWatch(owner, it->second);
303 watches_.erase(it);
307 void GalleryWatchManager::RemoveAllWatches(BrowserContext* browser_context,
308 const std::string& extension_id) {
309 DCHECK_CURRENTLY_ON(BrowserThread::UI);
310 DCHECK(browser_context);
312 WatchesMap::iterator it = watches_.begin();
313 while (it != watches_.end()) {
314 if (it->first.extension_id == extension_id) {
315 DeactivateFileWatch(it->first, it->second);
316 // Post increment moves iterator to next element while deleting current.
317 watches_.erase(it++);
318 } else {
319 ++it;
324 MediaGalleryPrefIdSet GalleryWatchManager::GetWatchSet(
325 BrowserContext* browser_context,
326 const std::string& extension_id) {
327 DCHECK_CURRENTLY_ON(BrowserThread::UI);
328 DCHECK(browser_context);
330 MediaGalleryPrefIdSet result;
331 for (WatchesMap::const_iterator it = watches_.begin(); it != watches_.end();
332 ++it) {
333 if (it->first.browser_context == browser_context &&
334 it->first.extension_id == extension_id) {
335 result.insert(it->first.gallery_id);
338 return result;
341 void GalleryWatchManager::EnsureBrowserContextSubscription(
342 BrowserContext* browser_context) {
343 auto it = browser_context_subscription_map_.find(browser_context);
344 if (it == browser_context_subscription_map_.end()) {
345 browser_context_subscription_map_.set(
346 browser_context,
347 ShutdownNotifierFactory::GetInstance()
348 ->Get(browser_context)
349 ->Subscribe(base::Bind(&GalleryWatchManager::ShutdownBrowserContext,
350 base::Unretained(this), browser_context)));
354 void GalleryWatchManager::DeactivateFileWatch(const WatchOwner& owner,
355 const base::FilePath& path) {
356 DCHECK_CURRENTLY_ON(BrowserThread::UI);
357 WatchedPaths::iterator it = watched_paths_.find(path);
358 if (it == watched_paths_.end())
359 return;
361 it->second.owners.erase(owner);
362 if (it->second.owners.empty()) {
363 watched_paths_.erase(it);
364 BrowserThread::PostTask(BrowserThread::FILE,
365 FROM_HERE,
366 base::Bind(&FileWatchManager::RemoveFileWatch,
367 watch_manager_->GetWeakPtr(),
368 path));
372 void GalleryWatchManager::OnFileWatchActivated(const WatchOwner& owner,
373 const base::FilePath& path,
374 const ResultCallback& callback,
375 bool success) {
376 if (success) {
377 // |watched_paths_| doesn't necessarily to contain |path| yet.
378 // In that case, it calls the default constructor for NotificationInfo.
379 watched_paths_[path].owners.insert(owner);
380 callback.Run(std::string());
381 } else {
382 callback.Run(kCouldNotWatchGalleryError);
386 void GalleryWatchManager::OnFilePathChanged(const base::FilePath& path,
387 bool error) {
388 WatchedPaths::iterator notification_info = watched_paths_.find(path);
389 if (notification_info == watched_paths_.end())
390 return;
392 // On error, all watches on that path are dropped, so update records and
393 // notify observers.
394 if (error) {
395 // Make a copy, as |watched_paths_| is modified as we erase watches.
396 std::set<WatchOwner> owners = notification_info->second.owners;
397 for (std::set<WatchOwner>::iterator it = owners.begin(); it != owners.end();
398 ++it) {
399 Profile* profile = Profile::FromBrowserContext(it->browser_context);
400 RemoveWatch(it->browser_context, it->extension_id, it->gallery_id);
401 if (ContainsKey(observers_, profile))
402 observers_[profile]->OnGalleryWatchDropped(it->extension_id,
403 it->gallery_id);
406 return;
409 base::TimeDelta time_since_last_notify =
410 base::Time::Now() - notification_info->second.last_notify_time;
411 if (time_since_last_notify <
412 base::TimeDelta::FromSeconds(kMinNotificiationDelayInSeconds)) {
413 if (!notification_info->second.delayed_notification_pending) {
414 notification_info->second.delayed_notification_pending = true;
415 base::TimeDelta delay_to_next_valid_time =
416 notification_info->second.last_notify_time +
417 base::TimeDelta::FromSeconds(kMinNotificiationDelayInSeconds) -
418 base::Time::Now();
419 BrowserThread::PostDelayedTask(
420 BrowserThread::UI,
421 FROM_HERE,
422 base::Bind(&GalleryWatchManager::OnFilePathChanged,
423 weak_factory_.GetWeakPtr(),
424 path,
425 error),
426 delay_to_next_valid_time);
428 return;
430 notification_info->second.delayed_notification_pending = false;
431 notification_info->second.last_notify_time = base::Time::Now();
433 std::set<WatchOwner>::const_iterator it;
434 for (it = notification_info->second.owners.begin();
435 it != notification_info->second.owners.end();
436 ++it) {
437 DCHECK(ContainsKey(watches_, *it));
438 if (ContainsKey(observers_, it->browser_context)) {
439 observers_[it->browser_context]->OnGalleryChanged(it->extension_id,
440 it->gallery_id);
445 void GalleryWatchManager::OnPermissionRemoved(MediaGalleriesPreferences* pref,
446 const std::string& extension_id,
447 MediaGalleryPrefId pref_id) {
448 RemoveWatch(pref->profile(), extension_id, pref_id);
449 if (ContainsKey(observers_, pref->profile()))
450 observers_[pref->profile()]->OnGalleryWatchDropped(extension_id, pref_id);
453 void GalleryWatchManager::OnGalleryRemoved(MediaGalleriesPreferences* pref,
454 MediaGalleryPrefId pref_id) {
455 // Removing a watch may update |watches_|, so extract the extension ids first.
456 std::set<std::string> extension_ids;
457 for (WatchesMap::const_iterator it = watches_.begin(); it != watches_.end();
458 ++it) {
459 if (it->first.browser_context == pref->profile() &&
460 it->first.gallery_id == pref_id) {
461 extension_ids.insert(it->first.extension_id);
465 for (std::set<std::string>::const_iterator it = extension_ids.begin();
466 it != extension_ids.end();
467 ++it) {
468 RemoveWatch(pref->profile(), *it, pref_id);
469 if (ContainsKey(observers_, pref->profile()))
470 observers_[pref->profile()]->OnGalleryWatchDropped(*it, pref_id);
474 void GalleryWatchManager::OnRemovableStorageDetached(
475 const storage_monitor::StorageInfo& info) {
476 WatchesMap::iterator it = watches_.begin();
477 while (it != watches_.end()) {
478 MediaGalleriesPreferences* preferences =
479 g_browser_process->media_file_system_registry()->GetPreferences(
480 Profile::FromBrowserContext(it->first.browser_context));
481 MediaGalleryPrefIdSet detached_ids =
482 preferences->LookUpGalleriesByDeviceId(info.device_id());
484 if (ContainsKey(detached_ids, it->first.gallery_id)) {
485 WatchOwner owner = it->first;
486 DeactivateFileWatch(owner, it->second);
487 // Post increment moves iterator to next element while deleting current.
488 watches_.erase(it++);
489 observers_[preferences->profile()]->OnGalleryWatchDropped(
490 owner.extension_id, owner.gallery_id);
491 } else {
492 ++it;