Home | History | Annotate | Download | only in media_galleries_private
      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 
     23 namespace extensions {
     24 
     25 namespace {
     26 
     27 using 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.
     33 typedef std::map<void*, extensions::GalleryWatchManager*> WatchManagerMap;
     34 WatchManagerMap* g_gallery_watch_managers = NULL;
     35 
     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_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.
     56 class 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 
    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_CURRENTLY_ON(BrowserThread::FILE);
    138   gallery_path_ = path;
    139   AddExtension(extension_id);
    140 }
    141 
    142 void 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 
    151 void 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 
    158 void GalleryWatchManager::GalleryFilePathWatcher::OnExtensionUnloaded(
    159     const std::string& extension_id) {
    160   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    161   RemoveExtensionReferences(extension_id);
    162 }
    163 
    164 bool 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 
    172 void 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 
    184 GalleryWatchManager::GalleryFilePathWatcher::~GalleryFilePathWatcher() {
    185   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    186   on_destroyed_callback_.Run();
    187 }
    188 
    189 void 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 
    221 void 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
    237 GalleryWatchManager* 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
    251 bool 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
    262 void 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
    279 bool 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
    291 void 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 
    301 void 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 
    310 GalleryWatchManager::GalleryWatchManager() {
    311   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    312 }
    313 
    314 GalleryWatchManager::~GalleryWatchManager() {
    315   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    316   DeleteAllWatchers();
    317 }
    318 
    319 bool 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 
    345 void 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 
    356 void 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 
    374 void 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 
    389 void 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