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