Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / media_galleries / gallery_watch_manager.cc
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 }