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/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
;
24 // Don't send a notification more than once per 3 seconds (chosen arbitrarily).
25 const int kMinNotificiationDelayInSeconds
= 3;
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
40 class GalleryWatchManager::FileWatchManager
{
42 explicit FileWatchManager(const base::FilePathWatcher::Callback
& callback
);
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();
54 typedef std::map
<base::FilePath
, linked_ptr
<base::FilePathWatcher
> >
57 void OnFilePathChanged(const base::FilePath
& path
, bool error
);
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));
91 linked_ptr
<base::FilePathWatcher
> watcher(new base::FilePathWatcher
);
92 bool success
= watcher
->Watch(path
,
94 base::Bind(&FileWatchManager::OnFilePathChanged
,
95 weak_factory_
.GetWeakPtr()));
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
,
119 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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
);
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
);
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
);
200 WatchOwner
owner(browser_context
, extension
->id(), gallery_id
);
201 if (ContainsKey(watches_
, owner
)) {
202 callback
.Run(std::string());
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
);
215 MediaGalleryPrefIdSet permitted
=
216 preferences
->GalleriesForExtension(*extension
);
217 if (!ContainsKey(permitted
, gallery_id
)) {
218 callback
.Run(kNoPermissionError
);
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);
242 base::Callback
<void(bool)> on_watch_added
=
243 base::Bind(&GalleryWatchManager::OnFileWatchActivated
,
244 weak_factory_
.GetWeakPtr(),
248 BrowserThread::PostTask(BrowserThread::FILE,
250 base::Bind(&FileWatchManager::AddFileWatch
,
251 watch_manager_
->GetWeakPtr(),
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
);
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
++);
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();
297 if (it
->first
.browser_context
== browser_context
&&
298 it
->first
.extension_id
== extension_id
) {
299 result
.insert(it
->first
.gallery_id
);
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())
312 it
->second
.owners
.erase(owner
);
313 if (it
->second
.owners
.empty()) {
314 watched_paths_
.erase(it
);
315 BrowserThread::PostTask(BrowserThread::FILE,
317 base::Bind(&FileWatchManager::RemoveFileWatch
,
318 watch_manager_
->GetWeakPtr(),
323 void GalleryWatchManager::OnFileWatchActivated(const WatchOwner
& owner
,
324 const base::FilePath
& path
,
325 const ResultCallback
& callback
,
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());
333 callback
.Run(kCouldNotWatchGalleryError
);
337 void GalleryWatchManager::OnFilePathChanged(const base::FilePath
& path
,
339 WatchedPaths::iterator notification_info
= watched_paths_
.find(path
);
340 if (notification_info
== watched_paths_
.end())
343 // On error, all watches on that path are dropped, so update records and
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();
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
,
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
) -
370 BrowserThread::PostDelayedTask(
373 base::Bind(&GalleryWatchManager::OnFilePathChanged
,
374 weak_factory_
.GetWeakPtr(),
377 delay_to_next_valid_time
);
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();
388 DCHECK(ContainsKey(watches_
, *it
));
389 if (ContainsKey(observers_
, it
->browser_context
)) {
390 observers_
[it
->browser_context
]->OnGalleryChanged(it
->extension_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();
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();
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
);