// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Implements the Chrome Extensions Media Galleries API.

#include "chrome/browser/extensions/api/media_galleries/media_galleries_api.h"

#include <set>
#include <string>
#include <vector>

#include "apps/app_window.h"
#include "apps/app_window_registry.h"
#include "base/callback.h"
#include "base/lazy_instance.h"
#include "base/platform_file.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/file_system/file_system_api.h"
#include "chrome/browser/extensions/blob_reader.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/media_galleries/fileapi/safe_media_metadata_parser.h"
#include "chrome/browser/media_galleries/media_file_system_registry.h"
#include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
#include "chrome/browser/media_galleries/media_galleries_histograms.h"
#include "chrome/browser/media_galleries/media_galleries_preferences.h"
#include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
#include "chrome/browser/media_galleries/media_scan_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/common/extensions/api/media_galleries.h"
#include "chrome/common/pref_names.h"
#include "components/storage_monitor/storage_info.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/media_galleries_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "grit/generated_resources.h"
#include "net/base/mime_sniffer.h"
#include "ui/base/l10n/l10n_util.h"

using content::WebContents;
using storage_monitor::MediaStorageUtil;
using storage_monitor::StorageInfo;
using web_modal::WebContentsModalDialogManager;

namespace extensions {

namespace MediaGalleries = api::media_galleries;
namespace GetMediaFileSystems = MediaGalleries::GetMediaFileSystems;

namespace {

const char kDisallowedByPolicy[] =
    "Media Galleries API is disallowed by policy: ";
const char kMissingEventListener[] =
    "Missing event listener registration.";
const char kNoScanPermission[] =
    "No permission to scan.";

const char kDeviceIdKey[] = "deviceId";
const char kGalleryIdKey[] = "galleryId";
const char kIsAvailableKey[] = "isAvailable";
const char kIsMediaDeviceKey[] = "isMediaDevice";
const char kIsRemovableKey[] = "isRemovable";
const char kNameKey[] = "name";

MediaFileSystemRegistry* media_file_system_registry() {
  return g_browser_process->media_file_system_registry();
}

MediaScanManager* media_scan_manager() {
  return media_file_system_registry()->media_scan_manager();
}

// Checks whether the MediaGalleries API is currently accessible (it may be
// disallowed even if an extension has the requisite permission). Then
// initializes the MediaGalleriesPreferences
bool Setup(Profile* profile, std::string* error, base::Closure callback) {
  if (!ChromeSelectFilePolicy::FileSelectDialogsAllowed()) {
    *error = std::string(kDisallowedByPolicy) +
        prefs::kAllowFileSelectionDialogs;
    return false;
  }

  MediaGalleriesPreferences* preferences =
      media_file_system_registry()->GetPreferences(profile);
  preferences->EnsureInitialized(callback);
  return true;
}

WebContents* GetWebContents(content::RenderViewHost* rvh,
                            Profile* profile,
                            const std::string& app_id) {
  WebContents* contents = WebContents::FromRenderViewHost(rvh);
  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
      WebContentsModalDialogManager::FromWebContents(contents);
  if (!web_contents_modal_dialog_manager) {
    // If there is no WebContentsModalDialogManager, then this contents is
    // probably the background page for an app. Try to find a app window to
    // host the dialog.
    apps::AppWindow* window = apps::AppWindowRegistry::Get(profile)
                                  ->GetCurrentAppWindowForApp(app_id);
    contents = window ? window->web_contents() : NULL;
  }
  return contents;
}

base::ListValue* ConstructFileSystemList(
    content::RenderViewHost* rvh,
    const Extension* extension,
    const std::vector<MediaFileSystemInfo>& filesystems) {
  if (!rvh)
    return NULL;

  MediaGalleriesPermission::CheckParam read_param(
      MediaGalleriesPermission::kReadPermission);
  bool has_read_permission = PermissionsData::CheckAPIPermissionWithParam(
      extension, APIPermission::kMediaGalleries, &read_param);
  MediaGalleriesPermission::CheckParam copy_to_param(
      MediaGalleriesPermission::kCopyToPermission);
  bool has_copy_to_permission = PermissionsData::CheckAPIPermissionWithParam(
      extension, APIPermission::kMediaGalleries, &copy_to_param);
  MediaGalleriesPermission::CheckParam delete_param(
      MediaGalleriesPermission::kDeletePermission);
  bool has_delete_permission = PermissionsData::CheckAPIPermissionWithParam(
      extension, APIPermission::kMediaGalleries, &delete_param);

  const int child_id = rvh->GetProcess()->GetID();
  scoped_ptr<base::ListValue> list(new base::ListValue());
  for (size_t i = 0; i < filesystems.size(); ++i) {
    scoped_ptr<base::DictionaryValue> file_system_dict_value(
        new base::DictionaryValue());

    // Send the file system id so the renderer can create a valid FileSystem
    // object.
    file_system_dict_value->SetStringWithoutPathExpansion(
        "fsid", filesystems[i].fsid);

    file_system_dict_value->SetStringWithoutPathExpansion(
        kNameKey, filesystems[i].name);
    file_system_dict_value->SetStringWithoutPathExpansion(
        kGalleryIdKey,
        base::Uint64ToString(filesystems[i].pref_id));
    if (!filesystems[i].transient_device_id.empty()) {
      file_system_dict_value->SetStringWithoutPathExpansion(
          kDeviceIdKey, filesystems[i].transient_device_id);
    }
    file_system_dict_value->SetBooleanWithoutPathExpansion(
        kIsRemovableKey, filesystems[i].removable);
    file_system_dict_value->SetBooleanWithoutPathExpansion(
        kIsMediaDeviceKey, filesystems[i].media_device);
    file_system_dict_value->SetBooleanWithoutPathExpansion(
        kIsAvailableKey, true);

    list->Append(file_system_dict_value.release());

    if (filesystems[i].path.empty())
      continue;

    if (has_read_permission) {
      content::ChildProcessSecurityPolicy* policy =
          content::ChildProcessSecurityPolicy::GetInstance();
      policy->GrantReadFileSystem(child_id, filesystems[i].fsid);
      if (has_delete_permission) {
        policy->GrantDeleteFromFileSystem(child_id, filesystems[i].fsid);
        if (has_copy_to_permission) {
          policy->GrantCopyIntoFileSystem(child_id, filesystems[i].fsid);
        }
      }
    }
  }

  return list.release();
}

bool CheckScanPermission(const extensions::Extension* extension,
                         std::string* error) {
  DCHECK(extension);
  DCHECK(error);
  MediaGalleriesPermission::CheckParam scan_param(
      MediaGalleriesPermission::kScanPermission);
  bool has_scan_permission = PermissionsData::CheckAPIPermissionWithParam(
      extension, APIPermission::kMediaGalleries, &scan_param);
  if (!has_scan_permission)
    *error = kNoScanPermission;
  return has_scan_permission;
}

class SelectDirectoryDialog : public ui::SelectFileDialog::Listener,
                              public base::RefCounted<SelectDirectoryDialog> {
 public:
  // Selected file path, or an empty path if the user canceled.
  typedef base::Callback<void(const base::FilePath&)> Callback;

  SelectDirectoryDialog(WebContents* web_contents, const Callback& callback)
      : web_contents_(web_contents),
        callback_(callback) {
    select_file_dialog_ = ui::SelectFileDialog::Create(
        this, new ChromeSelectFilePolicy(web_contents));
  }

  void Show(const base::FilePath& default_path) {
    AddRef();  // Balanced in the two reachable listener outcomes.
    select_file_dialog_->SelectFile(
      ui::SelectFileDialog::SELECT_FOLDER,
      l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
      default_path,
      NULL,
      0,
      base::FilePath::StringType(),
      platform_util::GetTopLevel(web_contents_->GetView()->GetNativeView()),
      NULL);
  }

  // ui::SelectFileDialog::Listener implementation.
  virtual void FileSelected(const base::FilePath& path,
                            int index,
                            void* params) OVERRIDE {
    callback_.Run(path);
    Release();  // Balanced in Show().
  }

  virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
                                  void* params) OVERRIDE {
    NOTREACHED() << "Should not be able to select multiple files";
  }

  virtual void FileSelectionCanceled(void* params) OVERRIDE {
    callback_.Run(base::FilePath());
    Release();  // Balanced in Show().
  }

 private:
  friend class base::RefCounted<SelectDirectoryDialog>;
  virtual ~SelectDirectoryDialog() {}

  scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
  WebContents* web_contents_;
  Callback callback_;

  DISALLOW_COPY_AND_ASSIGN(SelectDirectoryDialog);
};

}  // namespace

MediaGalleriesEventRouter::MediaGalleriesEventRouter(
    content::BrowserContext* context)
    : profile_(Profile::FromBrowserContext(context)), weak_ptr_factory_(this) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  DCHECK(profile_);
  media_scan_manager()->AddObserver(profile_, this);
}

MediaGalleriesEventRouter::~MediaGalleriesEventRouter() {
}

void MediaGalleriesEventRouter::Shutdown() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  weak_ptr_factory_.InvalidateWeakPtrs();
  media_scan_manager()->RemoveObserver(profile_);
  media_scan_manager()->CancelScansForProfile(profile_);
}

static base::LazyInstance<
    BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter> > g_factory =
    LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter>*
MediaGalleriesEventRouter::GetFactoryInstance() {
  return g_factory.Pointer();
}

// static
MediaGalleriesEventRouter* MediaGalleriesEventRouter::Get(
    content::BrowserContext* context) {
  DCHECK(media_file_system_registry()
             ->GetPreferences(Profile::FromBrowserContext(context))
             ->IsInitialized());
  return BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter>::Get(context);
}

bool MediaGalleriesEventRouter::ExtensionHasScanProgressListener(
    const std::string& extension_id) const {
  EventRouter* router = ExtensionSystem::Get(profile_)->event_router();
  return router->ExtensionHasEventListener(
      extension_id,
      MediaGalleries::OnScanProgress::kEventName);
}

void MediaGalleriesEventRouter::OnScanStarted(const std::string& extension_id) {
  MediaGalleries::ScanProgressDetails details;
  details.type = MediaGalleries::SCAN_PROGRESS_TYPE_START;
  DispatchEventToExtension(
      extension_id,
      MediaGalleries::OnScanProgress::kEventName,
      MediaGalleries::OnScanProgress::Create(details).Pass());
}

void MediaGalleriesEventRouter::OnScanCancelled(
    const std::string& extension_id) {
  MediaGalleries::ScanProgressDetails details;
  details.type = MediaGalleries::SCAN_PROGRESS_TYPE_CANCEL;
  DispatchEventToExtension(
      extension_id,
      MediaGalleries::OnScanProgress::kEventName,
      MediaGalleries::OnScanProgress::Create(details).Pass());
}

void MediaGalleriesEventRouter::OnScanFinished(
    const std::string& extension_id, int gallery_count,
    const MediaGalleryScanResult& file_counts) {
  media_galleries::UsageCount(media_galleries::SCAN_FINISHED);
  MediaGalleries::ScanProgressDetails details;
  details.type = MediaGalleries::SCAN_PROGRESS_TYPE_FINISH;
  details.gallery_count.reset(new int(gallery_count));
  details.audio_count.reset(new int(file_counts.audio_count));
  details.image_count.reset(new int(file_counts.image_count));
  details.video_count.reset(new int(file_counts.video_count));
  DispatchEventToExtension(
      extension_id,
      MediaGalleries::OnScanProgress::kEventName,
      MediaGalleries::OnScanProgress::Create(details).Pass());
}

void MediaGalleriesEventRouter::OnScanError(
    const std::string& extension_id) {
  MediaGalleries::ScanProgressDetails details;
  details.type = MediaGalleries::SCAN_PROGRESS_TYPE_ERROR;
  DispatchEventToExtension(
      extension_id,
      MediaGalleries::OnScanProgress::kEventName,
      MediaGalleries::OnScanProgress::Create(details).Pass());
}

void MediaGalleriesEventRouter::DispatchEventToExtension(
    const std::string& extension_id,
    const std::string& event_name,
    scoped_ptr<base::ListValue> event_args) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  EventRouter* router =
      extensions::ExtensionSystem::Get(profile_)->event_router();
  if (!router->ExtensionHasEventListener(extension_id, event_name))
    return;

  scoped_ptr<extensions::Event> event(
      new extensions::Event(event_name, event_args.Pass()));
  router->DispatchEventToExtension(extension_id, event.Pass());
}

MediaGalleriesGetMediaFileSystemsFunction::
    ~MediaGalleriesGetMediaFileSystemsFunction() {}

bool MediaGalleriesGetMediaFileSystemsFunction::RunImpl() {
  media_galleries::UsageCount(media_galleries::GET_MEDIA_FILE_SYSTEMS);
  scoped_ptr<GetMediaFileSystems::Params> params(
      GetMediaFileSystems::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  MediaGalleries::GetMediaFileSystemsInteractivity interactive =
      MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO;
  if (params->details.get() && params->details->interactive != MediaGalleries::
         GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE) {
    interactive = params->details->interactive;
  }

  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit, this,
      interactive));
}

void MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit(
    MediaGalleries::GetMediaFileSystemsInteractivity interactive) {
  switch (interactive) {
    case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_YES: {
      // The MediaFileSystemRegistry only updates preferences for extensions
      // that it knows are in use. Since this may be the first call to
      // chrome.getMediaFileSystems for this extension, call
      // GetMediaFileSystemsForExtension() here solely so that
      // MediaFileSystemRegistry will send preference changes.
      GetMediaFileSystemsForExtension(base::Bind(
          &MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog, this));
      return;
    }
    case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_IF_NEEDED: {
      GetMediaFileSystemsForExtension(base::Bind(
          &MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries,
          this));
      return;
    }
    case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO:
      GetAndReturnGalleries();
      return;
    case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE:
      NOTREACHED();
  }
  SendResponse(false);
}

void MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog(
    const std::vector<MediaFileSystemInfo>& /*filesystems*/) {
  ShowDialog();
}

void MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries(
    const std::vector<MediaFileSystemInfo>& filesystems) {
  if (filesystems.empty())
    ShowDialog();
  else
    ReturnGalleries(filesystems);
}

void MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries() {
  GetMediaFileSystemsForExtension(base::Bind(
      &MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries, this));
}

void MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries(
    const std::vector<MediaFileSystemInfo>& filesystems) {
  scoped_ptr<base::ListValue> list(
      ConstructFileSystemList(render_view_host(), GetExtension(), filesystems));
  if (!list.get()) {
    SendResponse(false);
    return;
  }

  // The custom JS binding will use this list to create DOMFileSystem objects.
  SetResult(list.release());
  SendResponse(true);
}

void MediaGalleriesGetMediaFileSystemsFunction::ShowDialog() {
  media_galleries::UsageCount(media_galleries::SHOW_DIALOG);
  const Extension* extension = GetExtension();
  WebContents* contents =
      GetWebContents(render_view_host(), GetProfile(), extension->id());
  if (!contents) {
    SendResponse(false);
    return;
  }

  // Controller will delete itself.
  base::Closure cb = base::Bind(
      &MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries, this);
  new MediaGalleriesDialogController(contents, *extension, cb);
}

void MediaGalleriesGetMediaFileSystemsFunction::GetMediaFileSystemsForExtension(
    const MediaFileSystemsCallback& cb) {
  if (!render_view_host()) {
    cb.Run(std::vector<MediaFileSystemInfo>());
    return;
  }
  MediaFileSystemRegistry* registry = media_file_system_registry();
  DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
  registry->GetMediaFileSystemsForExtension(
      render_view_host(), GetExtension(), cb);
}

MediaGalleriesGetAllMediaFileSystemMetadataFunction::
    ~MediaGalleriesGetAllMediaFileSystemMetadataFunction() {}

bool MediaGalleriesGetAllMediaFileSystemMetadataFunction::RunImpl() {
  media_galleries::UsageCount(
      media_galleries::GET_ALL_MEDIA_FILE_SYSTEM_METADATA);
  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnPreferencesInit,
      this));
}

void MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnPreferencesInit() {
  MediaFileSystemRegistry* registry = media_file_system_registry();
  MediaGalleriesPreferences* prefs = registry->GetPreferences(GetProfile());
  DCHECK(prefs->IsInitialized());
  MediaGalleryPrefIdSet permitted_gallery_ids =
      prefs->GalleriesForExtension(*GetExtension());

  MediaStorageUtil::DeviceIdSet* device_ids = new MediaStorageUtil::DeviceIdSet;
  const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
  for (MediaGalleryPrefIdSet::const_iterator it = permitted_gallery_ids.begin();
       it != permitted_gallery_ids.end(); ++it) {
    MediaGalleriesPrefInfoMap::const_iterator gallery_it = galleries.find(*it);
    DCHECK(gallery_it != galleries.end());
    device_ids->insert(gallery_it->second.device_id);
  }

  MediaStorageUtil::FilterAttachedDevices(
      device_ids,
      base::Bind(
          &MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnGetGalleries,
          this,
          permitted_gallery_ids,
          base::Owned(device_ids)));
}

void MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnGetGalleries(
    const MediaGalleryPrefIdSet& permitted_gallery_ids,
    const MediaStorageUtil::DeviceIdSet* available_devices) {
  MediaFileSystemRegistry* registry = media_file_system_registry();
  MediaGalleriesPreferences* prefs = registry->GetPreferences(GetProfile());

  base::ListValue* list = new base::ListValue();
  const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
  for (MediaGalleryPrefIdSet::const_iterator it = permitted_gallery_ids.begin();
       it != permitted_gallery_ids.end(); ++it) {
    MediaGalleriesPrefInfoMap::const_iterator gallery_it = galleries.find(*it);
    DCHECK(gallery_it != galleries.end());
    const MediaGalleryPrefInfo& gallery = gallery_it->second;
    MediaGalleries::MediaFileSystemMetadata metadata;
    metadata.name = base::UTF16ToUTF8(gallery.GetGalleryDisplayName());
    metadata.gallery_id = base::Uint64ToString(gallery.pref_id);
    metadata.is_removable = StorageInfo::IsRemovableDevice(gallery.device_id);
    metadata.is_media_device = StorageInfo::IsMediaDevice(gallery.device_id);
    metadata.is_available = ContainsKey(*available_devices, gallery.device_id);
    list->Append(metadata.ToValue().release());
  }

  SetResult(list);
  SendResponse(true);
}

MediaGalleriesAddUserSelectedFolderFunction::
    ~MediaGalleriesAddUserSelectedFolderFunction() {}

bool MediaGalleriesAddUserSelectedFolderFunction::RunImpl() {
  media_galleries::UsageCount(media_galleries::ADD_USER_SELECTED_FOLDER);
  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit, this));
}

void MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit() {
  Profile* profile = GetProfile();
  const std::string& app_id = GetExtension()->id();
  WebContents* contents = GetWebContents(render_view_host(), profile, app_id);
  if (!contents) {
    // When the request originated from a background page, but there is no app
    // window open, check to see if it originated from a tab and display the
    // dialog in that tab.
    bool found_tab = extensions::ExtensionTabUtil::GetTabById(
        source_tab_id(), profile, profile->IsOffTheRecord(),
        NULL, NULL, &contents, NULL);
    if (!found_tab || !contents) {
      SendResponse(false);
      return;
    }
  }

  if (!user_gesture()) {
    OnDirectorySelected(base::FilePath());
    return;
  }

  base::FilePath last_used_path =
      extensions::file_system_api::GetLastChooseEntryDirectory(
          extensions::ExtensionPrefs::Get(profile), app_id);
  SelectDirectoryDialog::Callback callback = base::Bind(
      &MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected, this);
  scoped_refptr<SelectDirectoryDialog> select_directory_dialog =
      new SelectDirectoryDialog(contents, callback);
  select_directory_dialog->Show(last_used_path);
}

void MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected(
    const base::FilePath& selected_directory) {
  if (selected_directory.empty()) {
    // User cancelled case.
    GetMediaFileSystemsForExtension(base::Bind(
        &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId,
        this,
        kInvalidMediaGalleryPrefId));
    return;
  }

  extensions::file_system_api::SetLastChooseEntryDirectory(
      extensions::ExtensionPrefs::Get(GetProfile()),
      GetExtension()->id(),
      selected_directory);

  MediaGalleriesPreferences* preferences =
      media_file_system_registry()->GetPreferences(GetProfile());
  MediaGalleryPrefId pref_id =
      preferences->AddGalleryByPath(selected_directory,
                                    MediaGalleryPrefInfo::kUserAdded);
  preferences->SetGalleryPermissionForExtension(*GetExtension(), pref_id, true);

  GetMediaFileSystemsForExtension(base::Bind(
      &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId,
      this,
      pref_id));
}

void MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId(
    MediaGalleryPrefId pref_id,
    const std::vector<MediaFileSystemInfo>& filesystems) {
  scoped_ptr<base::ListValue> list(
      ConstructFileSystemList(render_view_host(), GetExtension(), filesystems));
  if (!list.get()) {
    SendResponse(false);
    return;
  }

  int index = -1;
  if (pref_id != kInvalidMediaGalleryPrefId) {
    for (size_t i = 0; i < filesystems.size(); ++i) {
      if (filesystems[i].pref_id == pref_id) {
        index = i;
        break;
      }
    }
  }
  base::DictionaryValue* results = new base::DictionaryValue;
  results->SetWithoutPathExpansion("mediaFileSystems", list.release());
  results->SetIntegerWithoutPathExpansion("selectedFileSystemIndex", index);
  SetResult(results);
  SendResponse(true);
}

void
MediaGalleriesAddUserSelectedFolderFunction::GetMediaFileSystemsForExtension(
    const MediaFileSystemsCallback& cb) {
  if (!render_view_host()) {
    cb.Run(std::vector<MediaFileSystemInfo>());
    return;
  }
  MediaFileSystemRegistry* registry = media_file_system_registry();
  DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
  registry->GetMediaFileSystemsForExtension(
      render_view_host(), GetExtension(), cb);
}

MediaGalleriesStartMediaScanFunction::~MediaGalleriesStartMediaScanFunction() {}

bool MediaGalleriesStartMediaScanFunction::RunImpl() {
  media_galleries::UsageCount(media_galleries::START_MEDIA_SCAN);
  if (!CheckScanPermission(GetExtension(), &error_)) {
    MediaGalleriesEventRouter::Get(GetProfile())->OnScanError(
        GetExtension()->id());
    return false;
  }
  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesStartMediaScanFunction::OnPreferencesInit, this));
}

void MediaGalleriesStartMediaScanFunction::OnPreferencesInit() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  MediaGalleriesEventRouter* api = MediaGalleriesEventRouter::Get(GetProfile());
  if (!api->ExtensionHasScanProgressListener(GetExtension()->id())) {
    error_ = kMissingEventListener;
    SendResponse(false);
    return;
  }

  media_scan_manager()->StartScan(GetProfile(), GetExtension(), user_gesture());
  SendResponse(true);
}

MediaGalleriesCancelMediaScanFunction::
    ~MediaGalleriesCancelMediaScanFunction() {
}

bool MediaGalleriesCancelMediaScanFunction::RunImpl() {
  media_galleries::UsageCount(media_galleries::CANCEL_MEDIA_SCAN);
  if (!CheckScanPermission(GetExtension(), &error_)) {
    MediaGalleriesEventRouter::Get(GetProfile())->OnScanError(
        GetExtension()->id());
    return false;
  }
  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesCancelMediaScanFunction::OnPreferencesInit, this));
}

void MediaGalleriesCancelMediaScanFunction::OnPreferencesInit() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  media_scan_manager()->CancelScan(GetProfile(), GetExtension());
  SendResponse(true);
}

MediaGalleriesAddScanResultsFunction::~MediaGalleriesAddScanResultsFunction() {}

bool MediaGalleriesAddScanResultsFunction::RunImpl() {
  media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS);
  if (!CheckScanPermission(GetExtension(), &error_)) {
    // We don't fire a scan progress error here, as it would be unintuitive.
    return false;
  }
  if (!user_gesture())
    return false;

  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesAddScanResultsFunction::OnPreferencesInit, this));
}

MediaGalleriesScanResultDialogController*
MediaGalleriesAddScanResultsFunction::MakeDialog(
    content::WebContents* web_contents,
    const extensions::Extension& extension,
    const base::Closure& on_finish) {
  // Controller will delete itself.
  return new MediaGalleriesScanResultDialogController(web_contents, extension,
                                                      on_finish);
}

void MediaGalleriesAddScanResultsFunction::OnPreferencesInit() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  const Extension* extension = GetExtension();
  MediaGalleriesPreferences* preferences =
      media_file_system_registry()->GetPreferences(GetProfile());
  if (MediaGalleriesScanResultDialogController::ScanResultCountForExtension(
          preferences, extension) == 0) {
    GetAndReturnGalleries();
    return;
  }

  WebContents* contents =
      GetWebContents(render_view_host(), GetProfile(), extension->id());
  if (!contents) {
    SendResponse(false);
    return;
  }

  base::Closure cb = base::Bind(
      &MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries, this);
  MakeDialog(contents, *extension, cb);
}

void MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries() {
  if (!render_view_host()) {
    ReturnGalleries(std::vector<MediaFileSystemInfo>());
    return;
  }
  MediaFileSystemRegistry* registry = media_file_system_registry();
  DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
  registry->GetMediaFileSystemsForExtension(
      render_view_host(), GetExtension(),
      base::Bind(&MediaGalleriesAddScanResultsFunction::ReturnGalleries,
                 this));
}

void MediaGalleriesAddScanResultsFunction::ReturnGalleries(
    const std::vector<MediaFileSystemInfo>& filesystems) {
  scoped_ptr<base::ListValue> list(
      ConstructFileSystemList(render_view_host(), GetExtension(), filesystems));
  if (!list.get()) {
    SendResponse(false);
    return;
  }

  // The custom JS binding will use this list to create DOMFileSystem objects.
  SetResult(list.release());
  SendResponse(true);
}

MediaGalleriesGetMetadataFunction::~MediaGalleriesGetMetadataFunction() {}

bool MediaGalleriesGetMetadataFunction::RunImpl() {
  std::string blob_uuid;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &blob_uuid));

  const base::Value* options_value = NULL;
  if (!args_->Get(1, &options_value))
    return false;
  scoped_ptr<MediaGalleries::MediaMetadataOptions> options =
      MediaGalleries::MediaMetadataOptions::FromValue(*options_value);
  if (!options)
    return false;

  bool mime_type_only = options->metadata_type ==
      MediaGalleries::GET_METADATA_TYPE_MIMETYPEONLY;

  return Setup(GetProfile(), &error_, base::Bind(
      &MediaGalleriesGetMetadataFunction::OnPreferencesInit, this,
      mime_type_only, blob_uuid));
}

void MediaGalleriesGetMetadataFunction::OnPreferencesInit(
    bool mime_type_only, const std::string& blob_uuid) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));

  // BlobReader is self-deleting.
  BlobReader* reader = new BlobReader(
      GetProfile(),
      blob_uuid,
      base::Bind(&MediaGalleriesGetMetadataFunction::SniffMimeType, this,
                 mime_type_only, blob_uuid));
  reader->SetByteRange(0, net::kMaxBytesToSniff);
  reader->Start();
}

void MediaGalleriesGetMetadataFunction::SniffMimeType(
    bool mime_type_only, const std::string& blob_uuid,
    scoped_ptr<std::string> blob_header, int64 total_blob_length) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));

  std::string mime_type;
  bool mime_type_sniffed = net::SniffMimeTypeFromLocalData(
      blob_header->c_str(), blob_header->size(), &mime_type);

  if (!mime_type_sniffed) {
    SendResponse(false);
    return;
  }

  if (mime_type_only) {
    MediaGalleries::MediaMetadata metadata;
    metadata.mime_type = mime_type;
    SetResult(metadata.ToValue().release());
    SendResponse(true);
    return;
  }

  scoped_refptr<metadata::SafeMediaMetadataParser> parser(
      new metadata::SafeMediaMetadataParser(GetProfile(), blob_uuid,
                                            total_blob_length, mime_type));
  parser->Start(base::Bind(
      &MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone, this));
}

void MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone(
    bool parse_success, base::DictionaryValue* metadata_dictionary) {
  if (!parse_success) {
    SendResponse(false);
    return;
  }

  SetResult(metadata_dictionary->DeepCopy());
  SendResponse(true);
}

}  // namespace extensions