1 // Copyright (c) 2012 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 // GalleryWatchManager implementation.
7 #include "chrome/browser/extensions/api/media_galleries_private/gallery_watch_manager.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/compiler_specific.h"
15 #include "base/files/file_path_watcher.h"
16 #include "base/location.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/stl_util.h"
19 #include "base/time/time.h"
20 #include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_event_router.h"
21 #include "content/public/browser/browser_thread.h"
23 namespace extensions {
27 using content::BrowserThread;
29 // Map to keep track of profile specific GalleryWatchManager objects.
30 // Key: Profile identifier.
31 // Value: GalleryWatchManager*.
32 // This map owns the GalleryWatchManager object.
33 typedef std::map<void*, extensions::GalleryWatchManager*> WatchManagerMap;
34 WatchManagerMap* g_gallery_watch_managers = NULL;
36 // Dispatches the gallery changed event on the UI thread.
37 void SendGalleryChangedEventOnUIThread(
38 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router,
39 MediaGalleryPrefId gallery_id,
40 const std::set<std::string>& extension_ids) {
41 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
42 if (event_router.get())
43 event_router->OnGalleryChanged(gallery_id, extension_ids);
48 ///////////////////////////////////////////////////////////////////////////////
49 // GalleryWatchManager::GalleryFilePathWatcher //
50 ///////////////////////////////////////////////////////////////////////////////
52 // This class does a recursive watch on the gallery file path and holds a list
53 // of extensions that are watching the gallery. When there is a file system
54 // activity within the gallery, GalleryFilePathWatcher notifies the interested
55 // extensions. This class lives on the file thread.
56 class GalleryWatchManager::GalleryFilePathWatcher
57 : public base::RefCounted<GalleryFilePathWatcher> {
59 // |on_destroyed_callback| is called when the last GalleryFilePathWatcher
60 // reference goes away.
61 GalleryFilePathWatcher(
62 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router,
63 MediaGalleryPrefId gallery_id,
64 const base::FilePath& path,
65 const std::string& extension_id,
66 const base::Closure& on_destroyed_callback);
68 // Adds the extension reference to the watched gallery.
69 void AddExtension(const std::string& extension_id);
71 // Removes the extension reference to the watched gallery.
72 void RemoveExtension(const std::string& extension_id);
74 // Handles the extension unloaded/uninstalled event.
75 void OnExtensionUnloaded(const std::string& extension_id);
77 // Sets up the watch operation for the specified |gallery_path_|. On
78 // success, returns true.
81 // Removes all the extension references when the browser profile is in
83 void RemoveAllWatchReferences();
86 friend class base::RefCounted<GalleryFilePathWatcher>;
88 // Key: Extension identifier, e.g "qoueruoweuroiwueroiwujkshdf".
89 // Value: Time at which the last gallery changed event is dispatched.
90 // Initialized to null Time value.
91 typedef std::map<std::string, base::Time> ExtensionWatchInfoMap;
93 // Private because GalleryFilePathWatcher is ref-counted.
94 virtual ~GalleryFilePathWatcher();
96 // FilePathWatcher callback.
97 void OnFilePathChanged(const base::FilePath& path, bool error);
99 // Remove the watch references for the extension specified by the
101 void RemoveExtensionReferences(const std::string& extension_id);
103 // Used to notify the interested extensions about the gallery changed event.
104 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router_;
106 // The gallery identifier, e.g "1".
107 MediaGalleryPrefId gallery_id_;
109 // The gallery file path watcher.
110 base::FilePathWatcher file_watcher_;
112 // The gallery file path, e.g "C:\My Pictures".
113 base::FilePath gallery_path_;
115 // A callback to call when |this| object is destroyed.
116 base::Closure on_destroyed_callback_;
118 // Map to keep track of the extension and its corresponding watch count.
119 ExtensionWatchInfoMap extension_watch_info_map_;
121 // Used to provide a weak pointer to FilePathWatcher callback.
122 base::WeakPtrFactory<GalleryFilePathWatcher> weak_ptr_factory_;
124 DISALLOW_COPY_AND_ASSIGN(GalleryFilePathWatcher);
127 GalleryWatchManager::GalleryFilePathWatcher::GalleryFilePathWatcher(
128 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router,
129 MediaGalleryPrefId gallery_id,
130 const base::FilePath& path,
131 const std::string& extension_id,
132 const base::Closure& on_destroyed_callback)
133 : event_router_(event_router),
134 gallery_id_(gallery_id),
135 on_destroyed_callback_(on_destroyed_callback),
136 weak_ptr_factory_(this) {
137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
138 gallery_path_ = path;
139 AddExtension(extension_id);
142 void GalleryWatchManager::GalleryFilePathWatcher::AddExtension(
143 const std::string& extension_id) {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
145 if (ContainsKey(extension_watch_info_map_, extension_id))
147 extension_watch_info_map_[extension_id] = base::Time();
151 void GalleryWatchManager::GalleryFilePathWatcher::RemoveExtension(
152 const std::string& extension_id) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
154 if (extension_watch_info_map_.erase(extension_id) == 1)
158 void GalleryWatchManager::GalleryFilePathWatcher::OnExtensionUnloaded(
159 const std::string& extension_id) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
161 RemoveExtensionReferences(extension_id);
164 bool GalleryWatchManager::GalleryFilePathWatcher::SetupWatch() {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
166 return file_watcher_.Watch(
168 base::Bind(&GalleryFilePathWatcher::OnFilePathChanged,
169 weak_ptr_factory_.GetWeakPtr()));
172 void GalleryWatchManager::GalleryFilePathWatcher::RemoveAllWatchReferences() {
173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
174 std::set<std::string> extension_ids;
175 for (ExtensionWatchInfoMap::iterator iter = extension_watch_info_map_.begin();
176 iter != extension_watch_info_map_.end(); ++iter)
177 extension_ids.insert(iter->first);
179 for (std::set<std::string>::const_iterator it = extension_ids.begin();
180 it != extension_ids.end(); ++it)
181 RemoveExtensionReferences(*it);
184 GalleryWatchManager::GalleryFilePathWatcher::~GalleryFilePathWatcher() {
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
186 on_destroyed_callback_.Run();
189 void GalleryWatchManager::GalleryFilePathWatcher::OnFilePathChanged(
190 const base::FilePath& path,
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
193 if (error || (path != gallery_path_))
196 std::set<std::string> extension_ids;
197 for (ExtensionWatchInfoMap::iterator iter = extension_watch_info_map_.begin();
198 iter != extension_watch_info_map_.end(); ++iter) {
199 if (!iter->second.is_null()) {
200 // Ignore gallery change event if it is received too frequently.
201 // For example, when an user copies/deletes 1000 media files from a
202 // gallery, this callback is called 1000 times within a span of 10ms.
203 // GalleryWatchManager should not send 1000 gallery changed events to
204 // the watching extension.
205 const int kMinSecondsToIgnoreGalleryChangedEvent = 3;
206 base::TimeDelta diff = base::Time::Now() - iter->second;
207 if (diff.InSeconds() < kMinSecondsToIgnoreGalleryChangedEvent)
210 iter->second = base::Time::Now();
211 extension_ids.insert(iter->first);
213 if (!extension_ids.empty()) {
214 content::BrowserThread::PostTask(
215 content::BrowserThread::UI, FROM_HERE,
216 base::Bind(SendGalleryChangedEventOnUIThread, event_router_,
217 gallery_id_, extension_ids));
221 void GalleryWatchManager::GalleryFilePathWatcher::RemoveExtensionReferences(
222 const std::string& extension_id) {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
224 ExtensionWatchInfoMap::iterator it =
225 extension_watch_info_map_.find(extension_id);
226 if (it == extension_watch_info_map_.end())
228 extension_watch_info_map_.erase(it);
232 ///////////////////////////////////////////////////////////////////////////////
233 // GalleryWatchManager //
234 ///////////////////////////////////////////////////////////////////////////////
237 GalleryWatchManager* GalleryWatchManager::GetForProfile(
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
241 bool has_watch_manager = (g_gallery_watch_managers &&
242 GalleryWatchManager::HasForProfile(profile_id));
243 if (!g_gallery_watch_managers)
244 g_gallery_watch_managers = new WatchManagerMap;
245 if (!has_watch_manager)
246 (*g_gallery_watch_managers)[profile_id] = new GalleryWatchManager;
247 return (*g_gallery_watch_managers)[profile_id];
251 bool GalleryWatchManager::HasForProfile(void* profile_id) {
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
254 if (!g_gallery_watch_managers)
256 WatchManagerMap::const_iterator it =
257 g_gallery_watch_managers->find(profile_id);
258 return (it != g_gallery_watch_managers->end());
262 void GalleryWatchManager::OnProfileShutdown(void* profile_id) {
263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
265 if (!g_gallery_watch_managers || g_gallery_watch_managers->empty())
267 WatchManagerMap::iterator it = g_gallery_watch_managers->find(profile_id);
268 if (it == g_gallery_watch_managers->end())
271 g_gallery_watch_managers->erase(it);
272 if (g_gallery_watch_managers->empty())
273 delete g_gallery_watch_managers;
277 bool GalleryWatchManager::SetupGalleryWatch(
279 MediaGalleryPrefId gallery_id,
280 const base::FilePath& watch_path,
281 const std::string& extension_id,
282 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router) {
283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
284 return GalleryWatchManager::GetForProfile(profile_id)->StartGalleryWatch(
285 gallery_id, watch_path, extension_id, event_router);
289 void GalleryWatchManager::RemoveGalleryWatch(void* profile_id,
290 const base::FilePath& watch_path,
291 const std::string& extension_id) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
293 if (!GalleryWatchManager::HasForProfile(profile_id))
295 GalleryWatchManager::GetForProfile(profile_id)->StopGalleryWatch(
296 watch_path, extension_id);
299 void GalleryWatchManager::OnExtensionUnloaded(void* profile_id,
300 const std::string& extension_id) {
301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
302 if (!GalleryWatchManager::HasForProfile(profile_id))
304 GalleryWatchManager::GetForProfile(profile_id)->HandleExtensionUnloadedEvent(
308 GalleryWatchManager::GalleryWatchManager() {
309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
312 GalleryWatchManager::~GalleryWatchManager() {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
317 bool GalleryWatchManager::StartGalleryWatch(
318 MediaGalleryPrefId gallery_id,
319 const base::FilePath& watch_path,
320 const std::string& extension_id,
321 base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router) {
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
323 WatcherMap::const_iterator iter = gallery_watchers_.find(watch_path);
324 if (iter != gallery_watchers_.end()) {
326 iter->second->AddExtension(extension_id);
330 // Need to add a new watcher.
331 scoped_refptr<GalleryFilePathWatcher> watch(
332 new GalleryFilePathWatcher(
333 event_router, gallery_id, watch_path, extension_id,
334 base::Bind(&GalleryWatchManager::RemoveGalleryFilePathWatcherEntry,
335 base::Unretained(this),
337 if (!watch->SetupWatch())
339 gallery_watchers_[watch_path] = watch.get();
343 void GalleryWatchManager::StopGalleryWatch(
344 const base::FilePath& watch_path,
345 const std::string& extension_id) {
346 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
347 WatcherMap::iterator iter = gallery_watchers_.find(watch_path);
348 if (iter == gallery_watchers_.end())
350 // Remove the renderer process for this watch.
351 iter->second->RemoveExtension(extension_id);
354 void GalleryWatchManager::HandleExtensionUnloadedEvent(
355 const std::string& extension_id) {
356 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
357 std::list<base::FilePath> watchers_to_notify;
358 for (WatcherMap::iterator iter = gallery_watchers_.begin();
359 iter != gallery_watchers_.end(); ++iter)
360 watchers_to_notify.push_back(iter->first);
362 for (std::list<base::FilePath>::const_iterator path =
363 watchers_to_notify.begin();
364 path != watchers_to_notify.end(); ++path) {
365 WatcherMap::iterator iter = gallery_watchers_.find(*path);
366 if (iter == gallery_watchers_.end())
368 iter->second->OnExtensionUnloaded(extension_id);
372 void GalleryWatchManager::DeleteAllWatchers() {
373 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
374 if (gallery_watchers_.empty())
377 // Create a copy of |gallery_watchers_| to delete because
378 // GalleryFilePathWatcher::RemoveAllWatchReferences will
379 // eventually call GalleryWatchManager::RemoveGalleryFilePathWatcherEntry()
380 // and modify |gallery_watchers_|.
381 WatcherMap watchers_to_delete(gallery_watchers_);
382 for (WatcherMap::const_iterator iter = watchers_to_delete.begin();
383 iter != watchers_to_delete.end(); ++iter)
384 iter->second->RemoveAllWatchReferences();
387 void GalleryWatchManager::RemoveGalleryFilePathWatcherEntry(
388 const base::FilePath& watch_path) {
389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
390 gallery_watchers_.erase(watch_path);
393 } // namespace extensions