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"
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
;
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
{
32 static ShutdownNotifierFactory
* GetInstance() {
33 return base::Singleton
<ShutdownNotifierFactory
>::get();
37 friend struct base::DefaultSingletonTraits
<ShutdownNotifierFactory
>;
39 ShutdownNotifierFactory()
40 : BrowserContextKeyedServiceShutdownNotifierFactory(
41 "GalleryWatchManager") {
42 DependsOn(MediaGalleriesPreferencesFactory::GetInstance());
44 ~ShutdownNotifierFactory() override
{}
46 DISALLOW_COPY_AND_ASSIGN(ShutdownNotifierFactory
);
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
62 class GalleryWatchManager::FileWatchManager
{
64 explicit FileWatchManager(const base::FilePathWatcher::Callback
& callback
);
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();
76 typedef std::map
<base::FilePath
, linked_ptr
<base::FilePathWatcher
> >
79 void OnFilePathChanged(const base::FilePath
& path
, bool error
);
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));
113 linked_ptr
<base::FilePathWatcher
> watcher(new base::FilePathWatcher
);
114 bool success
= watcher
->Watch(path
,
116 base::Bind(&FileWatchManager::OnFilePathChanged
,
117 weak_factory_
.GetWeakPtr()));
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
,
141 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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
);
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
);
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
++);
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
);
235 WatchOwner
owner(browser_context
, extension
->id(), gallery_id
);
236 if (ContainsKey(watches_
, owner
)) {
237 callback
.Run(std::string());
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
);
250 MediaGalleryPrefIdSet permitted
=
251 preferences
->GalleriesForExtension(*extension
);
252 if (!ContainsKey(permitted
, gallery_id
)) {
253 callback
.Run(kNoPermissionError
);
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);
278 base::Callback
<void(bool)> on_watch_added
=
279 base::Bind(&GalleryWatchManager::OnFileWatchActivated
,
280 weak_factory_
.GetWeakPtr(),
284 BrowserThread::PostTask(BrowserThread::FILE,
286 base::Bind(&FileWatchManager::AddFileWatch
,
287 watch_manager_
->GetWeakPtr(),
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
);
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
++);
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();
333 if (it
->first
.browser_context
== browser_context
&&
334 it
->first
.extension_id
== extension_id
) {
335 result
.insert(it
->first
.gallery_id
);
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(
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())
361 it
->second
.owners
.erase(owner
);
362 if (it
->second
.owners
.empty()) {
363 watched_paths_
.erase(it
);
364 BrowserThread::PostTask(BrowserThread::FILE,
366 base::Bind(&FileWatchManager::RemoveFileWatch
,
367 watch_manager_
->GetWeakPtr(),
372 void GalleryWatchManager::OnFileWatchActivated(const WatchOwner
& owner
,
373 const base::FilePath
& path
,
374 const ResultCallback
& callback
,
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());
382 callback
.Run(kCouldNotWatchGalleryError
);
386 void GalleryWatchManager::OnFilePathChanged(const base::FilePath
& path
,
388 WatchedPaths::iterator notification_info
= watched_paths_
.find(path
);
389 if (notification_info
== watched_paths_
.end())
392 // On error, all watches on that path are dropped, so update records and
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();
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
,
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
) -
419 BrowserThread::PostDelayedTask(
422 base::Bind(&GalleryWatchManager::OnFilePathChanged
,
423 weak_factory_
.GetWeakPtr(),
426 delay_to_next_valid_time
);
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();
437 DCHECK(ContainsKey(watches_
, *it
));
438 if (ContainsKey(observers_
, it
->browser_context
)) {
439 observers_
[it
->browser_context
]->OnGalleryChanged(it
->extension_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();
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();
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
);