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#include "chrome/browser/media_galleries/media_file_system_registry.h"
6
7#include <set>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/callback.h"
12#include "base/files/file_path.h"
13#include "base/prefs/pref_service.h"
14#include "base/stl_util.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
17#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
18#include "chrome/browser/media_galleries/gallery_watch_manager.h"
19#include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
20#include "chrome/browser/media_galleries/media_file_system_context.h"
21#include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
22#include "chrome/browser/media_galleries/media_galleries_histograms.h"
23#include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
24#include "chrome/browser/media_galleries/media_scan_manager.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/common/chrome_paths.h"
27#include "chrome/common/extensions/extension_constants.h"
28#include "chrome/common/pref_names.h"
29#include "components/storage_monitor/media_storage_util.h"
30#include "components/storage_monitor/storage_monitor.h"
31#include "content/public/browser/browser_thread.h"
32#include "content/public/browser/navigation_details.h"
33#include "content/public/browser/render_process_host.h"
34#include "content/public/browser/render_process_host_observer.h"
35#include "content/public/browser/render_view_host.h"
36#include "content/public/browser/web_contents.h"
37#include "content/public/browser/web_contents_observer.h"
38#include "extensions/browser/extension_system.h"
39#include "extensions/common/extension.h"
40#include "extensions/common/extension_set.h"
41#include "storage/browser/fileapi/external_mount_points.h"
42#include "storage/common/fileapi/file_system_mount_option.h"
43#include "storage/common/fileapi/file_system_types.h"
44
45using content::BrowserThread;
46using content::NavigationController;
47using content::RenderProcessHost;
48using content::WebContents;
49using storage::ExternalMountPoints;
50using storage_monitor::MediaStorageUtil;
51using storage_monitor::StorageInfo;
52using storage_monitor::StorageMonitor;
53
54namespace {
55
56struct InvalidatedGalleriesInfo {
57  std::set<ExtensionGalleriesHost*> extension_hosts;
58  std::set<MediaGalleryPrefId> pref_ids;
59};
60
61// Tracks the liveness of multiple RenderProcessHosts that the caller is
62// interested in. Once all of the RPHs have closed or been destroyed a call
63// back informs the caller.
64class RPHReferenceManager {
65 public:
66  // |no_references_callback| is called when the last RenderViewHost reference
67  // goes away. RenderViewHost references are added through ReferenceFromRVH().
68  explicit RPHReferenceManager(const base::Closure& no_references_callback);
69  virtual ~RPHReferenceManager();
70
71  // Remove all references, but don't call |no_references_callback|.
72  void Reset() { STLDeleteValues(&observer_map_); }
73
74  // Returns true if there are no references;
75  bool empty() const { return observer_map_.empty(); }
76
77  // Adds a reference to the passed |rvh|. Calling this multiple times with
78  // the same |rvh| is a no-op.
79  void ReferenceFromRVH(const content::RenderViewHost* rvh);
80
81 private:
82  class RPHWebContentsObserver : public content::WebContentsObserver {
83   public:
84    RPHWebContentsObserver(RPHReferenceManager* manager,
85                           WebContents* web_contents);
86
87   private:
88    // content::WebContentsObserver
89    virtual void WebContentsDestroyed() OVERRIDE;
90    virtual void NavigationEntryCommitted(
91        const content::LoadCommittedDetails& load_details) OVERRIDE;
92
93    RPHReferenceManager* manager_;
94  };
95
96  class RPHObserver : public content::RenderProcessHostObserver {
97   public:
98    RPHObserver(RPHReferenceManager* manager, RenderProcessHost* host);
99    virtual ~RPHObserver();
100
101    void AddWebContentsObserver(WebContents* web_contents);
102    void RemoveWebContentsObserver(WebContents* web_contents);
103    bool HasWebContentsObservers() {
104      return observed_web_contentses_.size() > 0;
105    }
106
107   private:
108    virtual void RenderProcessHostDestroyed(RenderProcessHost* host) OVERRIDE;
109
110    RPHReferenceManager* manager_;
111    RenderProcessHost* host_;
112    typedef std::map<WebContents*, RPHWebContentsObserver*> WCObserverMap;
113    WCObserverMap observed_web_contentses_;
114  };
115  typedef std::map<const RenderProcessHost*, RPHObserver*> RPHObserverMap;
116
117  // Handlers for observed events.
118  void OnRenderProcessHostDestroyed(RenderProcessHost* rph);
119  void OnWebContentsDestroyedOrNavigated(WebContents* contents);
120
121  // A callback to call when the last RVH reference goes away.
122  base::Closure no_references_callback_;
123
124  // The set of render processes and web contents that may have references to
125  // the file system ids this instance manages.
126  RPHObserverMap observer_map_;
127};
128
129RPHReferenceManager::RPHReferenceManager(
130    const base::Closure& no_references_callback)
131    : no_references_callback_(no_references_callback) {
132}
133
134RPHReferenceManager::~RPHReferenceManager() {
135  Reset();
136}
137
138void RPHReferenceManager::ReferenceFromRVH(const content::RenderViewHost* rvh) {
139  WebContents* contents = WebContents::FromRenderViewHost(rvh);
140  RenderProcessHost* rph = contents->GetRenderProcessHost();
141  RPHObserver* state = NULL;
142  if (!ContainsKey(observer_map_, rph)) {
143    state = new RPHObserver(this, rph);
144    observer_map_[rph] = state;
145  } else {
146    state = observer_map_[rph];
147  }
148
149  state->AddWebContentsObserver(contents);
150}
151
152RPHReferenceManager::RPHWebContentsObserver::RPHWebContentsObserver(
153    RPHReferenceManager* manager,
154    WebContents* web_contents)
155    : content::WebContentsObserver(web_contents),
156      manager_(manager) {
157}
158
159void RPHReferenceManager::RPHWebContentsObserver::WebContentsDestroyed() {
160  manager_->OnWebContentsDestroyedOrNavigated(web_contents());
161}
162
163void RPHReferenceManager::RPHWebContentsObserver::NavigationEntryCommitted(
164    const content::LoadCommittedDetails& load_details) {
165  if (load_details.is_in_page)
166    return;
167
168  manager_->OnWebContentsDestroyedOrNavigated(web_contents());
169}
170
171RPHReferenceManager::RPHObserver::RPHObserver(
172    RPHReferenceManager* manager, RenderProcessHost* host)
173    : manager_(manager),
174      host_(host) {
175  host->AddObserver(this);
176}
177
178RPHReferenceManager::RPHObserver::~RPHObserver() {
179  STLDeleteValues(&observed_web_contentses_);
180  if (host_)
181    host_->RemoveObserver(this);
182}
183
184void RPHReferenceManager::RPHObserver::AddWebContentsObserver(
185    WebContents* web_contents) {
186  if (ContainsKey(observed_web_contentses_, web_contents))
187    return;
188
189  RPHWebContentsObserver* observer =
190    new RPHWebContentsObserver(manager_, web_contents);
191  observed_web_contentses_[web_contents] = observer;
192}
193
194void RPHReferenceManager::RPHObserver::RemoveWebContentsObserver(
195    WebContents* web_contents) {
196  WCObserverMap::iterator wco_iter =
197      observed_web_contentses_.find(web_contents);
198  DCHECK(wco_iter != observed_web_contentses_.end());
199  delete wco_iter->second;
200  observed_web_contentses_.erase(wco_iter);
201}
202
203void RPHReferenceManager::RPHObserver::RenderProcessHostDestroyed(
204    RenderProcessHost* host) {
205  host_ = NULL;
206  manager_->OnRenderProcessHostDestroyed(host);
207}
208
209void RPHReferenceManager::OnRenderProcessHostDestroyed(
210    RenderProcessHost* rph) {
211  RPHObserverMap::iterator rph_info = observer_map_.find(rph);
212  // This could be a potential problem if the RPH is navigated to a page on the
213  // same renderer (triggering OnWebContentsDestroyedOrNavigated()) and then the
214  // renderer crashes.
215  if (rph_info == observer_map_.end()) {
216    NOTREACHED();
217    return;
218  }
219  delete rph_info->second;
220  observer_map_.erase(rph_info);
221  if (observer_map_.empty())
222    no_references_callback_.Run();
223}
224
225void RPHReferenceManager::OnWebContentsDestroyedOrNavigated(
226    WebContents* contents) {
227  RenderProcessHost* rph = contents->GetRenderProcessHost();
228  RPHObserverMap::iterator rph_info = observer_map_.find(rph);
229  DCHECK(rph_info != observer_map_.end());
230
231  rph_info->second->RemoveWebContentsObserver(contents);
232  if (!rph_info->second->HasWebContentsObservers())
233    OnRenderProcessHostDestroyed(rph);
234}
235
236}  // namespace
237
238MediaFileSystemInfo::MediaFileSystemInfo(const base::string16& fs_name,
239                                         const base::FilePath& fs_path,
240                                         const std::string& filesystem_id,
241                                         MediaGalleryPrefId pref_id,
242                                         const std::string& transient_device_id,
243                                         bool removable,
244                                         bool media_device)
245    : name(fs_name),
246      path(fs_path),
247      fsid(filesystem_id),
248      pref_id(pref_id),
249      transient_device_id(transient_device_id),
250      removable(removable),
251      media_device(media_device) {
252}
253
254MediaFileSystemInfo::MediaFileSystemInfo() {}
255MediaFileSystemInfo::~MediaFileSystemInfo() {}
256
257// The main owner of this class is
258// |MediaFileSystemRegistry::extension_hosts_map_|, but a callback may
259// temporarily hold a reference.
260class ExtensionGalleriesHost
261    : public base::RefCountedThreadSafe<ExtensionGalleriesHost> {
262 public:
263  // |no_references_callback| is called when the last RenderViewHost reference
264  // goes away. RenderViewHost references are added through ReferenceFromRVH().
265  ExtensionGalleriesHost(MediaFileSystemContext* file_system_context,
266                         const base::FilePath& profile_path,
267                         const std::string& extension_id,
268                         const base::Closure& no_references_callback)
269      : file_system_context_(file_system_context),
270        profile_path_(profile_path),
271        extension_id_(extension_id),
272        no_references_callback_(no_references_callback),
273        rph_refs_(base::Bind(&ExtensionGalleriesHost::CleanUp,
274                             base::Unretained(this))) {
275  }
276
277  // For each gallery in the list of permitted |galleries|, checks if the
278  // device is attached and if so looks up or creates a file system name and
279  // passes the information needed for the renderer to create those file
280  // system objects to the |callback|.
281  void GetMediaFileSystems(const MediaGalleryPrefIdSet& galleries,
282                           const MediaGalleriesPrefInfoMap& galleries_info,
283                           const MediaFileSystemsCallback& callback) {
284    DCHECK_CURRENTLY_ON(BrowserThread::UI);
285
286    // Extract all the device ids so we can make sure they are attached.
287    MediaStorageUtil::DeviceIdSet* device_ids =
288        new MediaStorageUtil::DeviceIdSet;
289    for (std::set<MediaGalleryPrefId>::const_iterator id = galleries.begin();
290         id != galleries.end();
291         ++id) {
292      device_ids->insert(galleries_info.find(*id)->second.device_id);
293    }
294    MediaStorageUtil::FilterAttachedDevices(device_ids, base::Bind(
295        &ExtensionGalleriesHost::GetMediaFileSystemsForAttachedDevices, this,
296        base::Owned(device_ids), galleries, galleries_info, callback));
297  }
298
299  // Checks if |gallery| is attached and if so, registers the file system and
300  // then calls |callback| with the result.
301  void RegisterMediaFileSystem(
302      const MediaGalleryPrefInfo& gallery,
303      const base::Callback<void(base::File::Error result)>& callback) {
304    // Extract all the device ids so we can make sure they are attached.
305    MediaStorageUtil::DeviceIdSet* device_ids =
306        new MediaStorageUtil::DeviceIdSet;
307    device_ids->insert(gallery.device_id);
308    MediaStorageUtil::FilterAttachedDevices(device_ids, base::Bind(
309        &ExtensionGalleriesHost::RegisterAttachedMediaFileSystem, this,
310        base::Owned(device_ids), gallery, callback));
311  }
312
313  // Revoke the file system for |id| if this extension has created one for |id|.
314  void RevokeGalleryByPrefId(MediaGalleryPrefId id) {
315    PrefIdFsInfoMap::iterator gallery = pref_id_map_.find(id);
316    if (gallery == pref_id_map_.end())
317      return;
318
319    file_system_context_->RevokeFileSystem(gallery->second.fsid);
320    pref_id_map_.erase(gallery);
321
322    if (pref_id_map_.empty()) {
323      rph_refs_.Reset();
324      CleanUp();
325    }
326  }
327
328  // Indicate that the passed |rvh| will reference the file system ids created
329  // by this class.
330  void ReferenceFromRVH(const content::RenderViewHost* rvh) {
331    rph_refs_.ReferenceFromRVH(rvh);
332  }
333
334 private:
335  typedef std::map<MediaGalleryPrefId, MediaFileSystemInfo> PrefIdFsInfoMap;
336
337  // Private destructor and friend declaration for ref counted implementation.
338  friend class base::RefCountedThreadSafe<ExtensionGalleriesHost>;
339
340  virtual ~ExtensionGalleriesHost() {
341    DCHECK(rph_refs_.empty());
342    DCHECK(pref_id_map_.empty());
343  }
344
345  void GetMediaFileSystemsForAttachedDevices(
346      const MediaStorageUtil::DeviceIdSet* attached_devices,
347      const MediaGalleryPrefIdSet& galleries,
348      const MediaGalleriesPrefInfoMap& galleries_info,
349      const MediaFileSystemsCallback& callback) {
350    std::vector<MediaFileSystemInfo> result;
351
352    if (rph_refs_.empty()) {
353      // We're actually in the middle of shutdown, and Filter...() lagging
354      // which can invoke this method interleaved in the destruction callback
355      // sequence and re-populate pref_id_map_.
356      callback.Run(result);
357      return;
358    }
359
360    for (std::set<MediaGalleryPrefId>::const_iterator pref_id_it =
361             galleries.begin();
362         pref_id_it != galleries.end();
363         ++pref_id_it) {
364      const MediaGalleryPrefId& pref_id = *pref_id_it;
365      const MediaGalleryPrefInfo& gallery_info =
366          galleries_info.find(pref_id)->second;
367      const std::string& device_id = gallery_info.device_id;
368      if (!ContainsKey(*attached_devices, device_id))
369        continue;
370
371      PrefIdFsInfoMap::const_iterator existing_info =
372          pref_id_map_.find(pref_id);
373      if (existing_info != pref_id_map_.end()) {
374        result.push_back(existing_info->second);
375        continue;
376      }
377
378      base::FilePath path = gallery_info.AbsolutePath();
379      if (!MediaStorageUtil::CanCreateFileSystem(device_id, path))
380        continue;
381
382      std::string fs_name = MediaFileSystemBackend::ConstructMountName(
383          profile_path_, extension_id_, pref_id);
384      if (!file_system_context_->RegisterFileSystem(device_id, fs_name, path))
385        continue;
386
387      MediaFileSystemInfo new_entry(
388          gallery_info.GetGalleryDisplayName(),
389          file_system_context_->GetRegisteredPath(fs_name),
390          fs_name,
391          pref_id,
392          GetTransientIdForRemovableDeviceId(device_id),
393          StorageInfo::IsRemovableDevice(device_id),
394          StorageInfo::IsMediaDevice(device_id));
395      result.push_back(new_entry);
396      pref_id_map_[pref_id] = new_entry;
397    }
398
399    if (result.size() == 0) {
400      rph_refs_.Reset();
401      CleanUp();
402    }
403
404    DCHECK_EQ(pref_id_map_.size(), result.size());
405    callback.Run(result);
406  }
407
408  void RegisterAttachedMediaFileSystem(
409      const MediaStorageUtil::DeviceIdSet* attached_device,
410      const MediaGalleryPrefInfo& gallery,
411      const base::Callback<void(base::File::Error result)>& callback) {
412    base::File::Error result = base::File::FILE_ERROR_NOT_FOUND;
413
414    // If rph_refs is empty then we're actually in the middle of shutdown, and
415    // Filter...() lagging which can invoke this method interleaved in the
416    // destruction callback sequence and re-populate pref_id_map_.
417    if (!attached_device->empty() && !rph_refs_.empty()) {
418      std::string fs_name = MediaFileSystemBackend::ConstructMountName(
419          profile_path_, extension_id_, gallery.pref_id);
420      base::FilePath path = gallery.AbsolutePath();
421      const std::string& device_id = gallery.device_id;
422
423      if (ContainsKey(pref_id_map_, gallery.pref_id)) {
424        result = base::File::FILE_OK;
425      } else if (MediaStorageUtil::CanCreateFileSystem(device_id, path) &&
426                 file_system_context_->RegisterFileSystem(device_id, fs_name,
427                                                          path)) {
428        result = base::File::FILE_OK;
429        pref_id_map_[gallery.pref_id] = MediaFileSystemInfo(
430            gallery.GetGalleryDisplayName(),
431            file_system_context_->GetRegisteredPath(fs_name),
432            fs_name,
433            gallery.pref_id,
434            GetTransientIdForRemovableDeviceId(device_id),
435            StorageInfo::IsRemovableDevice(device_id),
436            StorageInfo::IsMediaDevice(device_id));
437      }
438    }
439
440    if (pref_id_map_.empty()) {
441      rph_refs_.Reset();
442      CleanUp();
443    }
444    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
445                            base::Bind(callback, result));
446  }
447
448  std::string GetTransientIdForRemovableDeviceId(const std::string& device_id) {
449    if (!StorageInfo::IsRemovableDevice(device_id))
450      return std::string();
451
452    return StorageMonitor::GetInstance()->GetTransientIdForDeviceId(device_id);
453  }
454
455  void CleanUp() {
456    DCHECK(rph_refs_.empty());
457    for (PrefIdFsInfoMap::const_iterator it = pref_id_map_.begin();
458         it != pref_id_map_.end();
459         ++it) {
460      file_system_context_->RevokeFileSystem(it->second.fsid);
461    }
462    pref_id_map_.clear();
463
464    no_references_callback_.Run();
465  }
466
467  // MediaFileSystemRegistry owns |this| and |file_system_context_|, so it's
468  // safe to store a raw pointer.
469  MediaFileSystemContext* file_system_context_;
470
471  // Path for the active profile.
472  const base::FilePath profile_path_;
473
474  // Id of the extension this host belongs to.
475  const std::string extension_id_;
476
477  // A callback to call when the last RVH reference goes away.
478  base::Closure no_references_callback_;
479
480  // A map from the gallery preferences id to the file system information.
481  PrefIdFsInfoMap pref_id_map_;
482
483  // The set of render processes and web contents that may have references to
484  // the file system ids this instance manages.
485  RPHReferenceManager rph_refs_;
486
487  DISALLOW_COPY_AND_ASSIGN(ExtensionGalleriesHost);
488};
489
490/******************
491 * Public methods
492 ******************/
493
494void MediaFileSystemRegistry::GetMediaFileSystemsForExtension(
495    const content::RenderViewHost* rvh,
496    const extensions::Extension* extension,
497    const MediaFileSystemsCallback& callback) {
498  // TODO(tommycli): Change to DCHECK after fixing http://crbug.com/374330.
499  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
500
501  Profile* profile =
502      Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext());
503  MediaGalleriesPreferences* preferences = GetPreferences(profile);
504  MediaGalleryPrefIdSet galleries =
505      preferences->GalleriesForExtension(*extension);
506
507  if (galleries.empty()) {
508    callback.Run(std::vector<MediaFileSystemInfo>());
509    return;
510  }
511
512  ExtensionGalleriesHost* extension_host =
513      GetExtensionGalleryHost(profile, preferences, extension->id());
514
515  // This must come before the GetMediaFileSystems call to make sure the
516  // RVH of the context is referenced before the filesystems are retrieved.
517  extension_host->ReferenceFromRVH(rvh);
518
519  extension_host->GetMediaFileSystems(galleries, preferences->known_galleries(),
520                                      callback);
521}
522
523void MediaFileSystemRegistry::RegisterMediaFileSystemForExtension(
524    const content::RenderViewHost* rvh,
525    const extensions::Extension* extension,
526    MediaGalleryPrefId pref_id,
527    const base::Callback<void(base::File::Error result)>& callback) {
528  DCHECK_CURRENTLY_ON(BrowserThread::UI);
529  DCHECK_NE(kInvalidMediaGalleryPrefId, pref_id);
530
531  Profile* profile =
532      Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext());
533  MediaGalleriesPreferences* preferences = GetPreferences(profile);
534  MediaGalleriesPrefInfoMap::const_iterator gallery =
535      preferences->known_galleries().find(pref_id);
536  MediaGalleryPrefIdSet permitted_galleries =
537      preferences->GalleriesForExtension(*extension);
538
539  if (gallery == preferences->known_galleries().end() ||
540      !ContainsKey(permitted_galleries, pref_id)) {
541    BrowserThread::PostTask(
542        BrowserThread::IO, FROM_HERE,
543        base::Bind(callback, base::File::FILE_ERROR_NOT_FOUND));
544    return;
545  }
546
547  ExtensionGalleriesHost* extension_host =
548      GetExtensionGalleryHost(profile, preferences, extension->id());
549
550  // This must come before the GetMediaFileSystems call to make sure the
551  // RVH of the context is referenced before the filesystems are retrieved.
552  extension_host->ReferenceFromRVH(rvh);
553
554  extension_host->RegisterMediaFileSystem(gallery->second, callback);
555}
556
557MediaGalleriesPreferences* MediaFileSystemRegistry::GetPreferences(
558    Profile* profile) {
559  // Create an empty ExtensionHostMap for this profile on first initialization.
560  if (!ContainsKey(extension_hosts_map_, profile)) {
561    extension_hosts_map_[profile] = ExtensionHostMap();
562    media_galleries::UsageCount(media_galleries::PROFILES_WITH_USAGE);
563  }
564
565  return MediaGalleriesPreferencesFactory::GetForProfile(profile);
566}
567
568MediaScanManager* MediaFileSystemRegistry::media_scan_manager() {
569  if (!media_scan_manager_)
570    media_scan_manager_.reset(new MediaScanManager);
571  return media_scan_manager_.get();
572}
573
574GalleryWatchManager* MediaFileSystemRegistry::gallery_watch_manager() {
575  if (!gallery_watch_manager_)
576    gallery_watch_manager_.reset(new GalleryWatchManager);
577  return gallery_watch_manager_.get();
578}
579
580void MediaFileSystemRegistry::OnRemovableStorageDetached(
581    const StorageInfo& info) {
582  DCHECK_CURRENTLY_ON(BrowserThread::UI);
583
584  // Since revoking a gallery in the ExtensionGalleriesHost may cause it
585  // to be removed from the map and therefore invalidate any iterator pointing
586  // to it, this code first copies all the invalid gallery ids and the
587  // extension hosts in which they may appear (per profile) and revoked it in
588  // a second step.
589  std::vector<InvalidatedGalleriesInfo> invalid_galleries_info;
590
591  for (ExtensionGalleriesHostMap::iterator profile_it =
592           extension_hosts_map_.begin();
593       profile_it != extension_hosts_map_.end();
594       ++profile_it) {
595    MediaGalleriesPreferences* preferences = GetPreferences(profile_it->first);
596    // If |preferences| is not yet initialized, it won't contain any galleries.
597    if (!preferences->IsInitialized())
598      continue;
599
600    InvalidatedGalleriesInfo invalid_galleries_in_profile;
601    invalid_galleries_in_profile.pref_ids =
602        preferences->LookUpGalleriesByDeviceId(info.device_id());
603
604    for (ExtensionHostMap::const_iterator extension_host_it =
605             profile_it->second.begin();
606         extension_host_it != profile_it->second.end();
607         ++extension_host_it) {
608      invalid_galleries_in_profile.extension_hosts.insert(
609          extension_host_it->second.get());
610    }
611
612    invalid_galleries_info.push_back(invalid_galleries_in_profile);
613  }
614
615  for (size_t i = 0; i < invalid_galleries_info.size(); i++) {
616    for (std::set<ExtensionGalleriesHost*>::const_iterator extension_host_it =
617             invalid_galleries_info[i].extension_hosts.begin();
618         extension_host_it != invalid_galleries_info[i].extension_hosts.end();
619         ++extension_host_it) {
620      for (std::set<MediaGalleryPrefId>::const_iterator pref_id_it =
621               invalid_galleries_info[i].pref_ids.begin();
622           pref_id_it != invalid_galleries_info[i].pref_ids.end();
623           ++pref_id_it) {
624        (*extension_host_it)->RevokeGalleryByPrefId(*pref_id_it);
625      }
626    }
627  }
628}
629
630/******************
631 * Private methods
632 ******************/
633
634class MediaFileSystemRegistry::MediaFileSystemContextImpl
635    : public MediaFileSystemContext {
636 public:
637  MediaFileSystemContextImpl() {}
638  virtual ~MediaFileSystemContextImpl() {}
639
640  virtual bool RegisterFileSystem(const std::string& device_id,
641                                  const std::string& fs_name,
642                                  const base::FilePath& path) OVERRIDE {
643    if (StorageInfo::IsMassStorageDevice(device_id)) {
644      return RegisterFileSystemForMassStorage(device_id, fs_name, path);
645    } else {
646      return RegisterFileSystemForMTPDevice(device_id, fs_name, path);
647    }
648  }
649
650  virtual void RevokeFileSystem(const std::string& fs_name) OVERRIDE {
651    ImportedMediaGalleryRegistry* imported_registry =
652        ImportedMediaGalleryRegistry::GetInstance();
653    if (imported_registry->RevokeImportedFilesystemOnUIThread(fs_name))
654      return;
655
656    ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(fs_name);
657
658    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
659        &MTPDeviceMapService::RevokeMTPFileSystem,
660        base::Unretained(MTPDeviceMapService::GetInstance()),
661        fs_name));
662  }
663
664  virtual base::FilePath GetRegisteredPath(
665      const std::string& fs_name) const OVERRIDE {
666    base::FilePath result;
667    if (!ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(fs_name,
668                                                                     &result)) {
669      return base::FilePath();
670    }
671    return result;
672  }
673
674 private:
675  // Registers and returns the file system id for the mass storage device
676  // specified by |device_id| and |path|.
677  bool RegisterFileSystemForMassStorage(const std::string& device_id,
678                                        const std::string& fs_name,
679                                        const base::FilePath& path) {
680    DCHECK_CURRENTLY_ON(BrowserThread::UI);
681    DCHECK(StorageInfo::IsMassStorageDevice(device_id));
682
683    // Sanity checks for |path|.
684    CHECK(path.IsAbsolute());
685    CHECK(!path.ReferencesParent());
686
687    // TODO(gbillock): refactor ImportedMediaGalleryRegistry to delegate this
688    // call tree, probably by having it figure out by device id what
689    // registration is needed, or having per-device-type handlers at the
690    // next higher level.
691    bool result = false;
692    if (StorageInfo::IsITunesDevice(device_id)) {
693      ImportedMediaGalleryRegistry* registry =
694          ImportedMediaGalleryRegistry::GetInstance();
695      result = registry->RegisterITunesFilesystemOnUIThread(fs_name, path);
696    } else if (StorageInfo::IsPicasaDevice(device_id)) {
697      ImportedMediaGalleryRegistry* registry =
698          ImportedMediaGalleryRegistry::GetInstance();
699      result = registry->RegisterPicasaFilesystemOnUIThread(fs_name, path);
700    } else if (StorageInfo::IsIPhotoDevice(device_id)) {
701      ImportedMediaGalleryRegistry* registry =
702          ImportedMediaGalleryRegistry::GetInstance();
703      result = registry->RegisterIPhotoFilesystemOnUIThread(fs_name, path);
704    } else {
705      result = ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
706          fs_name,
707          storage::kFileSystemTypeNativeMedia,
708          storage::FileSystemMountOption(),
709          path);
710    }
711    return result;
712  }
713
714  bool RegisterFileSystemForMTPDevice(const std::string& device_id,
715                                      const std::string fs_name,
716                                      const base::FilePath& path) {
717    DCHECK_CURRENTLY_ON(BrowserThread::UI);
718    DCHECK(!StorageInfo::IsMassStorageDevice(device_id));
719
720    // Sanity checks for |path|.
721    CHECK(MediaStorageUtil::CanCreateFileSystem(device_id, path));
722    bool result = ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
723        fs_name,
724        storage::kFileSystemTypeDeviceMedia,
725        storage::FileSystemMountOption(),
726        path);
727    CHECK(result);
728    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
729        &MTPDeviceMapService::RegisterMTPFileSystem,
730        base::Unretained(MTPDeviceMapService::GetInstance()),
731        path.value(), fs_name));
732    return result;
733  }
734
735  DISALLOW_COPY_AND_ASSIGN(MediaFileSystemContextImpl);
736};
737
738// Constructor in 'private' section because depends on private class definition.
739MediaFileSystemRegistry::MediaFileSystemRegistry()
740    : file_system_context_(new MediaFileSystemContextImpl) {
741  StorageMonitor::GetInstance()->AddObserver(this);
742}
743
744MediaFileSystemRegistry::~MediaFileSystemRegistry() {
745  // TODO(gbillock): This is needed because the unit test uses the
746  // g_browser_process registry. We should create one in the unit test,
747  // and then can remove this.
748  if (StorageMonitor::GetInstance())
749    StorageMonitor::GetInstance()->RemoveObserver(this);
750}
751
752void MediaFileSystemRegistry::OnPermissionRemoved(
753    MediaGalleriesPreferences* prefs,
754    const std::string& extension_id,
755    MediaGalleryPrefId pref_id) {
756  Profile* profile = prefs->profile();
757  ExtensionGalleriesHostMap::const_iterator host_map_it =
758      extension_hosts_map_.find(profile);
759  DCHECK(host_map_it != extension_hosts_map_.end());
760  const ExtensionHostMap& extension_host_map = host_map_it->second;
761  ExtensionHostMap::const_iterator gallery_host_it =
762      extension_host_map.find(extension_id);
763  if (gallery_host_it == extension_host_map.end())
764    return;
765  gallery_host_it->second->RevokeGalleryByPrefId(pref_id);
766}
767
768void MediaFileSystemRegistry::OnGalleryRemoved(
769    MediaGalleriesPreferences* prefs,
770    MediaGalleryPrefId pref_id) {
771  Profile* profile = prefs->profile();
772  // Get the Extensions, MediaGalleriesPreferences and ExtensionHostMap for
773  // |profile|.
774  const ExtensionService* extension_service =
775      extensions::ExtensionSystem::Get(profile)->extension_service();
776  const extensions::ExtensionSet* extensions_set =
777      extension_service->extensions();
778  ExtensionGalleriesHostMap::const_iterator host_map_it =
779      extension_hosts_map_.find(profile);
780  DCHECK(host_map_it != extension_hosts_map_.end());
781  const ExtensionHostMap& extension_host_map = host_map_it->second;
782
783  // Go through ExtensionHosts, and remove indicated gallery, if any.
784  // RevokeGalleryByPrefId() may end up deleting from |extension_host_map| and
785  // even delete |extension_host_map| altogether. So do this in two loops to
786  // avoid using an invalidated iterator or deleted map.
787  std::vector<const extensions::Extension*> extensions;
788  for (ExtensionHostMap::const_iterator it = extension_host_map.begin();
789       it != extension_host_map.end();
790       ++it) {
791    extensions.push_back(extensions_set->GetByID(it->first));
792  }
793  for (size_t i = 0; i < extensions.size(); ++i) {
794    if (!ContainsKey(extension_hosts_map_, profile))
795      break;
796    ExtensionHostMap::const_iterator gallery_host_it =
797        extension_host_map.find(extensions[i]->id());
798    if (gallery_host_it == extension_host_map.end())
799      continue;
800    gallery_host_it->second->RevokeGalleryByPrefId(pref_id);
801  }
802}
803
804ExtensionGalleriesHost* MediaFileSystemRegistry::GetExtensionGalleryHost(
805    Profile* profile,
806    MediaGalleriesPreferences* preferences,
807    const std::string& extension_id) {
808  ExtensionGalleriesHostMap::iterator extension_hosts =
809      extension_hosts_map_.find(profile);
810  // GetPreferences(), which had to be called because preferences is an
811  // argument, ensures that profile is in the map.
812  DCHECK(extension_hosts != extension_hosts_map_.end());
813  if (extension_hosts->second.empty())
814    preferences->AddGalleryChangeObserver(this);
815
816  ExtensionGalleriesHost* result = extension_hosts->second[extension_id].get();
817  if (!result) {
818    result = new ExtensionGalleriesHost(
819        file_system_context_.get(),
820        profile->GetPath(),
821        extension_id,
822        base::Bind(&MediaFileSystemRegistry::OnExtensionGalleriesHostEmpty,
823                   base::Unretained(this),
824                   profile,
825                   extension_id));
826    extension_hosts_map_[profile][extension_id] = result;
827  }
828  return result;
829}
830
831void MediaFileSystemRegistry::OnExtensionGalleriesHostEmpty(
832    Profile* profile, const std::string& extension_id) {
833  DCHECK_CURRENTLY_ON(BrowserThread::UI);
834
835  ExtensionGalleriesHostMap::iterator extension_hosts =
836      extension_hosts_map_.find(profile);
837  DCHECK(extension_hosts != extension_hosts_map_.end());
838  ExtensionHostMap::size_type erase_count =
839      extension_hosts->second.erase(extension_id);
840  DCHECK_EQ(1U, erase_count);
841  if (extension_hosts->second.empty()) {
842    // When a profile has no ExtensionGalleriesHosts left, remove the
843    // matching gallery-change-watcher since it is no longer needed. Leave the
844    // |extension_hosts| entry alone, since it indicates the profile has been
845    // previously used.
846    MediaGalleriesPreferences* preferences = GetPreferences(profile);
847    preferences->RemoveGalleryChangeObserver(this);
848  }
849}
850