Home | History | Annotate | Download | only in media_galleries
      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.
      4 
      5 #include "chrome/browser/media_galleries/gallery_watch_manager.h"
      6 
      7 #include "base/bind.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"
     18 
     19 using content::BrowserContext;
     20 using content::BrowserThread;
     21 
     22 namespace {
     23 
     24 // Don't send a notification more than once per 3 seconds (chosen arbitrarily).
     25 const int kMinNotificiationDelayInSeconds = 3;
     26 
     27 }  // namespace.
     28 
     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.";
     34 
     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
     39 // dropped.
     40 class GalleryWatchManager::FileWatchManager {
     41  public:
     42   explicit FileWatchManager(const base::FilePathWatcher::Callback& callback);
     43   ~FileWatchManager();
     44 
     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);
     48 
     49   void RemoveFileWatch(const base::FilePath& path);
     50 
     51   base::WeakPtr<FileWatchManager> GetWeakPtr();
     52 
     53  private:
     54   typedef std::map<base::FilePath, linked_ptr<base::FilePathWatcher> >
     55       WatcherMap;
     56 
     57   void OnFilePathChanged(const base::FilePath& path, bool error);
     58 
     59   WatcherMap watchers_;
     60 
     61   base::FilePathWatcher::Callback callback_;
     62 
     63   base::WeakPtrFactory<FileWatchManager> weak_factory_;
     64 
     65   DISALLOW_COPY_AND_ASSIGN(FileWatchManager);
     66 };
     67 
     68 GalleryWatchManager::FileWatchManager::FileWatchManager(
     69     const base::FilePathWatcher::Callback& callback)
     70     : callback_(callback), weak_factory_(this) {
     71   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     72 }
     73 
     74 GalleryWatchManager::FileWatchManager::~FileWatchManager() {
     75   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
     76 }
     77 
     78 void GalleryWatchManager::FileWatchManager::AddFileWatch(
     79     const base::FilePath& path,
     80     const base::Callback<void(bool)>& callback) {
     81   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
     82 
     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));
     88     return;
     89   }
     90 
     91   linked_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
     92   bool success = watcher->Watch(path,
     93                                 true /*recursive*/,
     94                                 base::Bind(&FileWatchManager::OnFilePathChanged,
     95                                            weak_factory_.GetWeakPtr()));
     96 
     97   if (success)
     98     watchers_[path] = watcher;
     99 
    100   BrowserThread::PostTask(
    101       BrowserThread::UI, FROM_HERE, base::Bind(callback, success));
    102 }
    103 
    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);
    109 }
    110 
    111 base::WeakPtr<GalleryWatchManager::FileWatchManager>
    112 GalleryWatchManager::FileWatchManager::GetWeakPtr() {
    113   return weak_factory_.GetWeakPtr();
    114 }
    115 
    116 void GalleryWatchManager::FileWatchManager::OnFilePathChanged(
    117     const base::FilePath& path,
    118     bool error) {
    119   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    120   if (error)
    121     RemoveFileWatch(path);
    122   BrowserThread::PostTask(
    123       BrowserThread::UI, FROM_HERE, base::Bind(callback_, path, error));
    124 }
    125 
    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) {
    132 }
    133 
    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;
    137 }
    138 
    139 GalleryWatchManager::NotificationInfo::NotificationInfo()
    140     : delayed_notification_pending(false) {
    141 }
    142 
    143 GalleryWatchManager::NotificationInfo::~NotificationInfo() {
    144 }
    145 
    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())));
    151 }
    152 
    153 GalleryWatchManager::~GalleryWatchManager() {
    154   weak_factory_.InvalidateWeakPtrs();
    155 
    156   if (storage_monitor_observed_ &&
    157       storage_monitor::StorageMonitor::GetInstance()) {
    158     storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this);
    159   }
    160 
    161   BrowserThread::DeleteSoon(
    162       BrowserThread::FILE, FROM_HERE, watch_manager_.release());
    163 }
    164 
    165 void GalleryWatchManager::AddObserver(BrowserContext* browser_context,
    166                                       GalleryWatchManagerObserver* observer) {
    167   DCHECK(browser_context);
    168   DCHECK(observer);
    169   DCHECK(!ContainsKey(observers_, browser_context));
    170   observers_[browser_context] = observer;
    171 }
    172 
    173 void GalleryWatchManager::RemoveObserver(BrowserContext* browser_context) {
    174   DCHECK(browser_context);
    175   size_t erased = observers_.erase(browser_context);
    176   DCHECK_EQ(erased, 1u);
    177 }
    178 
    179 void GalleryWatchManager::ShutdownBrowserContext(
    180     BrowserContext* browser_context) {
    181   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    182   DCHECK(browser_context);
    183 
    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);
    188   if (observed > 0)
    189     preferences->RemoveGalleryChangeObserver(this);
    190 }
    191 
    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);
    198   DCHECK(extension);
    199 
    200   WatchOwner owner(browser_context, extension->id(), gallery_id);
    201   if (ContainsKey(watches_, owner)) {
    202     callback.Run(std::string());
    203     return;
    204   }
    205 
    206   MediaGalleriesPreferences* preferences =
    207       g_browser_process->media_file_system_registry()->GetPreferences(
    208           Profile::FromBrowserContext(browser_context));
    209 
    210   if (!ContainsKey(preferences->known_galleries(), gallery_id)) {
    211     callback.Run(kInvalidGalleryIDError);
    212     return;
    213   }
    214 
    215   MediaGalleryPrefIdSet permitted =
    216       preferences->GalleriesForExtension(*extension);
    217   if (!ContainsKey(permitted, gallery_id)) {
    218     callback.Run(kNoPermissionError);
    219     return;
    220   }
    221 
    222   base::FilePath path =
    223       preferences->known_galleries().find(gallery_id)->second.AbsolutePath();
    224 
    225   if (!storage_monitor_observed_) {
    226     storage_monitor_observed_ = true;
    227     storage_monitor::StorageMonitor::GetInstance()->AddObserver(this);
    228   }
    229 
    230   // Observe the preferences if we haven't already.
    231   if (!ContainsKey(observed_preferences_, preferences)) {
    232     observed_preferences_.insert(preferences);
    233     preferences->AddGalleryChangeObserver(this);
    234   }
    235 
    236   watches_[owner] = path;
    237 
    238   // Start the FilePathWatcher on |gallery_path| if necessary.
    239   if (ContainsKey(watched_paths_, path)) {
    240     OnFileWatchActivated(owner, path, callback, true);
    241   } else {
    242     base::Callback<void(bool)> on_watch_added =
    243         base::Bind(&GalleryWatchManager::OnFileWatchActivated,
    244                    weak_factory_.GetWeakPtr(),
    245                    owner,
    246                    path,
    247                    callback);
    248     BrowserThread::PostTask(BrowserThread::FILE,
    249                             FROM_HERE,
    250                             base::Bind(&FileWatchManager::AddFileWatch,
    251                                        watch_manager_->GetWeakPtr(),
    252                                        path,
    253                                        on_watch_added));
    254   }
    255 }
    256 
    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);
    262 
    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);
    267     watches_.erase(it);
    268   }
    269 }
    270 
    271 void GalleryWatchManager::RemoveAllWatches(BrowserContext* browser_context,
    272                                            const std::string& extension_id) {
    273   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    274   DCHECK(browser_context);
    275 
    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++);
    282     } else {
    283       ++it;
    284     }
    285   }
    286 }
    287 
    288 MediaGalleryPrefIdSet GalleryWatchManager::GetWatchSet(
    289     BrowserContext* browser_context,
    290     const std::string& extension_id) {
    291   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    292   DCHECK(browser_context);
    293 
    294   MediaGalleryPrefIdSet result;
    295   for (WatchesMap::const_iterator it = watches_.begin(); it != watches_.end();
    296        ++it) {
    297     if (it->first.browser_context == browser_context &&
    298         it->first.extension_id == extension_id) {
    299       result.insert(it->first.gallery_id);
    300     }
    301   }
    302   return result;
    303 }
    304 
    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())
    310     return;
    311 
    312   it->second.owners.erase(owner);
    313   if (it->second.owners.empty()) {
    314     watched_paths_.erase(it);
    315     BrowserThread::PostTask(BrowserThread::FILE,
    316                             FROM_HERE,
    317                             base::Bind(&FileWatchManager::RemoveFileWatch,
    318                                        watch_manager_->GetWeakPtr(),
    319                                        path));
    320   }
    321 }
    322 
    323 void GalleryWatchManager::OnFileWatchActivated(const WatchOwner& owner,
    324                                                const base::FilePath& path,
    325                                                const ResultCallback& callback,
    326                                                bool success) {
    327   if (success) {
    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());
    332   } else {
    333     callback.Run(kCouldNotWatchGalleryError);
    334   }
    335 }
    336 
    337 void GalleryWatchManager::OnFilePathChanged(const base::FilePath& path,
    338                                             bool error) {
    339   WatchedPaths::iterator notification_info = watched_paths_.find(path);
    340   if (notification_info == watched_paths_.end())
    341     return;
    342 
    343   // On error, all watches on that path are dropped, so update records and
    344   // notify observers.
    345   if (error) {
    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();
    349          ++it) {
    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,
    354                                                    it->gallery_id);
    355     }
    356 
    357     return;
    358   }
    359 
    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) -
    369           base::Time::Now();
    370       BrowserThread::PostDelayedTask(
    371           BrowserThread::UI,
    372           FROM_HERE,
    373           base::Bind(&GalleryWatchManager::OnFilePathChanged,
    374                      weak_factory_.GetWeakPtr(),
    375                      path,
    376                      error),
    377           delay_to_next_valid_time);
    378     }
    379     return;
    380   }
    381   notification_info->second.delayed_notification_pending = false;
    382   notification_info->second.last_notify_time = base::Time::Now();
    383 
    384   std::set<WatchOwner>::const_iterator it;
    385   for (it = notification_info->second.owners.begin();
    386        it != notification_info->second.owners.end();
    387        ++it) {
    388     DCHECK(ContainsKey(watches_, *it));
    389     if (ContainsKey(observers_, it->browser_context)) {
    390       observers_[it->browser_context]->OnGalleryChanged(it->extension_id,
    391                                                         it->gallery_id);
    392     }
    393   }
    394 }
    395 
    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);
    402 }
    403 
    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();
    409        ++it) {
    410     if (it->first.browser_context == pref->profile() &&
    411         it->first.gallery_id == pref_id) {
    412       extension_ids.insert(it->first.extension_id);
    413     }
    414   }
    415 
    416   for (std::set<std::string>::const_iterator it = extension_ids.begin();
    417        it != extension_ids.end();
    418        ++it) {
    419     RemoveWatch(pref->profile(), *it, pref_id);
    420     if (ContainsKey(observers_, pref->profile()))
    421       observers_[pref->profile()]->OnGalleryWatchDropped(*it, pref_id);
    422   }
    423 }
    424 
    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());
    434 
    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);
    441     } else {
    442       ++it;
    443     }
    444   }
    445 }
    446