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.
4//
5// GalleryWatchManager implementation.
6
7#include "chrome/browser/extensions/api/media_galleries_private/gallery_watch_manager.h"
8
9#include <list>
10#include <set>
11
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"
22
23namespace extensions {
24
25namespace {
26
27using content::BrowserThread;
28
29// Map to keep track of profile specific GalleryWatchManager objects.
30// Key: Profile identifier.
31// Value: GalleryWatchManager*.
32// This map owns the GalleryWatchManager object.
33typedef std::map<void*, extensions::GalleryWatchManager*> WatchManagerMap;
34WatchManagerMap* g_gallery_watch_managers = NULL;
35
36// Dispatches the gallery changed event on the UI thread.
37void SendGalleryChangedEventOnUIThread(
38    base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router,
39    MediaGalleryPrefId gallery_id,
40    const std::set<std::string>& extension_ids) {
41  DCHECK_CURRENTLY_ON(BrowserThread::UI);
42  if (event_router.get())
43    event_router->OnGalleryChanged(gallery_id, extension_ids);
44}
45
46}  // namespace
47
48///////////////////////////////////////////////////////////////////////////////
49//            GalleryWatchManager::GalleryFilePathWatcher                    //
50///////////////////////////////////////////////////////////////////////////////
51
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.
56class GalleryWatchManager::GalleryFilePathWatcher
57    : public base::RefCounted<GalleryFilePathWatcher> {
58 public:
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);
67
68  // Adds the extension reference to the watched gallery.
69  void AddExtension(const std::string& extension_id);
70
71  // Removes the extension reference to the watched gallery.
72  void RemoveExtension(const std::string& extension_id);
73
74  // Handles the extension unloaded/uninstalled event.
75  void OnExtensionUnloaded(const std::string& extension_id);
76
77  // Sets up the watch operation for the specified |gallery_path_|. On
78  // success, returns true.
79  bool SetupWatch();
80
81  // Removes all the extension references when the browser profile is in
82  // shutdown mode.
83  void RemoveAllWatchReferences();
84
85 private:
86  friend class base::RefCounted<GalleryFilePathWatcher>;
87
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;
92
93  // Private because GalleryFilePathWatcher is ref-counted.
94  virtual ~GalleryFilePathWatcher();
95
96  // FilePathWatcher callback.
97  void OnFilePathChanged(const base::FilePath& path, bool error);
98
99  // Remove the watch references for the extension specified by the
100  // |extension_id|.
101  void RemoveExtensionReferences(const std::string& extension_id);
102
103  // Used to notify the interested extensions about the gallery changed event.
104  base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router_;
105
106  // The gallery identifier, e.g "1".
107  MediaGalleryPrefId gallery_id_;
108
109  // The gallery file path watcher.
110  base::FilePathWatcher file_watcher_;
111
112  // The gallery file path, e.g "C:\My Pictures".
113  base::FilePath gallery_path_;
114
115  // A callback to call when |this| object is destroyed.
116  base::Closure on_destroyed_callback_;
117
118  // Map to keep track of the extension and its corresponding watch count.
119  ExtensionWatchInfoMap extension_watch_info_map_;
120
121  // Used to provide a weak pointer to FilePathWatcher callback.
122  base::WeakPtrFactory<GalleryFilePathWatcher> weak_ptr_factory_;
123
124  DISALLOW_COPY_AND_ASSIGN(GalleryFilePathWatcher);
125};
126
127GalleryWatchManager::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_CURRENTLY_ON(BrowserThread::FILE);
138  gallery_path_ = path;
139  AddExtension(extension_id);
140}
141
142void GalleryWatchManager::GalleryFilePathWatcher::AddExtension(
143    const std::string& extension_id) {
144  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
145  if (ContainsKey(extension_watch_info_map_, extension_id))
146    return;
147  extension_watch_info_map_[extension_id] = base::Time();
148  AddRef();
149}
150
151void GalleryWatchManager::GalleryFilePathWatcher::RemoveExtension(
152    const std::string& extension_id) {
153  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
154  if (extension_watch_info_map_.erase(extension_id) == 1)
155    Release();
156}
157
158void GalleryWatchManager::GalleryFilePathWatcher::OnExtensionUnloaded(
159    const std::string& extension_id) {
160  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
161  RemoveExtensionReferences(extension_id);
162}
163
164bool GalleryWatchManager::GalleryFilePathWatcher::SetupWatch() {
165  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
166  return file_watcher_.Watch(
167      gallery_path_, true,
168      base::Bind(&GalleryFilePathWatcher::OnFilePathChanged,
169                 weak_ptr_factory_.GetWeakPtr()));
170}
171
172void GalleryWatchManager::GalleryFilePathWatcher::RemoveAllWatchReferences() {
173  DCHECK_CURRENTLY_ON(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);
178
179  for (std::set<std::string>::const_iterator it = extension_ids.begin();
180       it != extension_ids.end(); ++it)
181    RemoveExtensionReferences(*it);
182}
183
184GalleryWatchManager::GalleryFilePathWatcher::~GalleryFilePathWatcher() {
185  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
186  on_destroyed_callback_.Run();
187}
188
189void GalleryWatchManager::GalleryFilePathWatcher::OnFilePathChanged(
190    const base::FilePath& path,
191    bool error) {
192  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
193  if (error || (path != gallery_path_))
194    return;
195
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)
208        continue;
209    }
210    iter->second = base::Time::Now();
211    extension_ids.insert(iter->first);
212  }
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));
218  }
219}
220
221void GalleryWatchManager::GalleryFilePathWatcher::RemoveExtensionReferences(
222    const std::string& extension_id) {
223  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
224  ExtensionWatchInfoMap::iterator it =
225      extension_watch_info_map_.find(extension_id);
226  if (it == extension_watch_info_map_.end())
227    return;
228  extension_watch_info_map_.erase(it);
229  Release();
230}
231
232///////////////////////////////////////////////////////////////////////////////
233//                       GalleryWatchManager                                 //
234///////////////////////////////////////////////////////////////////////////////
235
236// static
237GalleryWatchManager* GalleryWatchManager::GetForProfile(
238    void* profile_id) {
239  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
240  DCHECK(profile_id);
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];
248}
249
250// static
251bool GalleryWatchManager::HasForProfile(void* profile_id) {
252  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
253  DCHECK(profile_id);
254  if (!g_gallery_watch_managers)
255    return false;
256  WatchManagerMap::const_iterator it =
257      g_gallery_watch_managers->find(profile_id);
258  return (it != g_gallery_watch_managers->end());
259}
260
261// static
262void GalleryWatchManager::OnProfileShutdown(void* profile_id) {
263  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
264  DCHECK(profile_id);
265  if (!g_gallery_watch_managers || g_gallery_watch_managers->empty())
266    return;
267  WatchManagerMap::iterator it = g_gallery_watch_managers->find(profile_id);
268  if (it == g_gallery_watch_managers->end())
269    return;
270  delete it->second;
271  g_gallery_watch_managers->erase(it);
272  if (g_gallery_watch_managers->empty()) {
273    delete g_gallery_watch_managers;
274    g_gallery_watch_managers = NULL;
275  }
276}
277
278// static
279bool GalleryWatchManager::SetupGalleryWatch(
280    void* profile_id,
281    MediaGalleryPrefId gallery_id,
282    const base::FilePath& watch_path,
283    const std::string& extension_id,
284    base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router) {
285  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
286  return GalleryWatchManager::GetForProfile(profile_id)->StartGalleryWatch(
287      gallery_id, watch_path, extension_id, event_router);
288}
289
290// static
291void GalleryWatchManager::RemoveGalleryWatch(void* profile_id,
292                                             const base::FilePath& watch_path,
293                                             const std::string& extension_id) {
294  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
295  if (!GalleryWatchManager::HasForProfile(profile_id))
296    return;
297  GalleryWatchManager::GetForProfile(profile_id)->StopGalleryWatch(
298      watch_path, extension_id);
299}
300
301void GalleryWatchManager::OnExtensionUnloaded(void* profile_id,
302                                              const std::string& extension_id) {
303  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
304  if (!GalleryWatchManager::HasForProfile(profile_id))
305    return;
306  GalleryWatchManager::GetForProfile(profile_id)->HandleExtensionUnloadedEvent(
307      extension_id);
308}
309
310GalleryWatchManager::GalleryWatchManager() {
311  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
312}
313
314GalleryWatchManager::~GalleryWatchManager() {
315  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
316  DeleteAllWatchers();
317}
318
319bool GalleryWatchManager::StartGalleryWatch(
320    MediaGalleryPrefId gallery_id,
321    const base::FilePath& watch_path,
322    const std::string& extension_id,
323    base::WeakPtr<MediaGalleriesPrivateEventRouter> event_router) {
324  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
325  WatcherMap::const_iterator iter = gallery_watchers_.find(watch_path);
326  if (iter != gallery_watchers_.end()) {
327    // Already watched.
328    iter->second->AddExtension(extension_id);
329    return true;
330  }
331
332  // Need to add a new watcher.
333  scoped_refptr<GalleryFilePathWatcher> watch(
334      new GalleryFilePathWatcher(
335          event_router, gallery_id, watch_path, extension_id,
336          base::Bind(&GalleryWatchManager::RemoveGalleryFilePathWatcherEntry,
337                     base::Unretained(this),
338                     watch_path)));
339  if (!watch->SetupWatch())
340    return false;
341  gallery_watchers_[watch_path] = watch.get();
342  return true;
343}
344
345void GalleryWatchManager::StopGalleryWatch(
346    const base::FilePath& watch_path,
347    const std::string& extension_id) {
348  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
349  WatcherMap::iterator iter = gallery_watchers_.find(watch_path);
350  if (iter == gallery_watchers_.end())
351    return;
352  // Remove the renderer process for this watch.
353  iter->second->RemoveExtension(extension_id);
354}
355
356void GalleryWatchManager::HandleExtensionUnloadedEvent(
357    const std::string& extension_id) {
358  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
359  std::list<base::FilePath> watchers_to_notify;
360  for (WatcherMap::iterator iter = gallery_watchers_.begin();
361       iter != gallery_watchers_.end(); ++iter)
362    watchers_to_notify.push_back(iter->first);
363
364  for (std::list<base::FilePath>::const_iterator path =
365           watchers_to_notify.begin();
366       path != watchers_to_notify.end(); ++path) {
367     WatcherMap::iterator iter = gallery_watchers_.find(*path);
368     if (iter == gallery_watchers_.end())
369       continue;
370     iter->second->OnExtensionUnloaded(extension_id);
371  }
372}
373
374void GalleryWatchManager::DeleteAllWatchers() {
375  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
376  if (gallery_watchers_.empty())
377    return;
378
379  // Create a copy of |gallery_watchers_| to delete because
380  // GalleryFilePathWatcher::RemoveAllWatchReferences will
381  // eventually call GalleryWatchManager::RemoveGalleryFilePathWatcherEntry()
382  // and modify |gallery_watchers_|.
383  WatcherMap watchers_to_delete(gallery_watchers_);
384  for (WatcherMap::const_iterator iter = watchers_to_delete.begin();
385       iter != watchers_to_delete.end(); ++iter)
386    iter->second->RemoveAllWatchReferences();
387}
388
389void GalleryWatchManager::RemoveGalleryFilePathWatcherEntry(
390    const base::FilePath& watch_path) {
391  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
392  gallery_watchers_.erase(watch_path);
393}
394
395}  // namespace extensions
396