blob: 3ba793479c1447564cb9dd1fe86d989737fe28ee [file] [log] [blame]
// 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.
#include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
#include <map>
#include <queue>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/threading/thread_checker.h"
#include "dbus/bus.h"
#include "device/media_transfer_protocol/media_transfer_protocol_daemon_client.h"
#include "device/media_transfer_protocol/mtp_file_entry.pb.h"
#include "device/media_transfer_protocol/mtp_storage_info.pb.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#if defined(OS_CHROMEOS)
#include "chromeos/dbus/dbus_thread_manager.h"
#endif
namespace device {
namespace {
MediaTransferProtocolManager* g_media_transfer_protocol_manager = NULL;
// The MediaTransferProtocolManager implementation.
class MediaTransferProtocolManagerImpl : public MediaTransferProtocolManager {
public:
explicit MediaTransferProtocolManagerImpl(
scoped_refptr<base::SequencedTaskRunner> task_runner)
: weak_ptr_factory_(this) {
#if defined(OS_CHROMEOS)
DCHECK(!task_runner.get());
#else
DCHECK(task_runner.get());
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
options.connection_type = dbus::Bus::PRIVATE;
options.dbus_task_runner = task_runner;
session_bus_ = new dbus::Bus(options);
#endif
if (GetBus()) {
// Listen for future mtpd service owner changes, in case it is not
// available right now. There is no guarantee on Linux or ChromeOS that
// mtpd is running already.
mtpd_owner_changed_callback_ = base::Bind(
&MediaTransferProtocolManagerImpl::FinishSetupOnOriginThread,
weak_ptr_factory_.GetWeakPtr());
GetBus()->ListenForServiceOwnerChange(mtpd::kMtpdServiceName,
mtpd_owner_changed_callback_);
GetBus()->GetServiceOwner(mtpd::kMtpdServiceName,
mtpd_owner_changed_callback_);
}
}
virtual ~MediaTransferProtocolManagerImpl() {
DCHECK(g_media_transfer_protocol_manager);
g_media_transfer_protocol_manager = NULL;
if (GetBus()) {
GetBus()->UnlistenForServiceOwnerChange(mtpd::kMtpdServiceName,
mtpd_owner_changed_callback_);
}
#if !defined(OS_CHROMEOS)
session_bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE, base::Bind(&dbus::Bus::ShutdownAndBlock, session_bus_));
#endif
VLOG(1) << "MediaTransferProtocolManager Shutdown completed";
}
// MediaTransferProtocolManager override.
virtual void AddObserver(Observer* observer) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.AddObserver(observer);
}
// MediaTransferProtocolManager override.
virtual void RemoveObserver(Observer* observer) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.RemoveObserver(observer);
}
// MediaTransferProtocolManager override.
virtual const std::vector<std::string> GetStorages() const OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<std::string> storages;
for (StorageInfoMap::const_iterator it = storage_info_map_.begin();
it != storage_info_map_.end();
++it) {
storages.push_back(it->first);
}
return storages;
}
// MediaTransferProtocolManager override.
virtual const MtpStorageInfo* GetStorageInfo(
const std::string& storage_name) const OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
StorageInfoMap::const_iterator it = storage_info_map_.find(storage_name);
return it != storage_info_map_.end() ? &it->second : NULL;
}
// MediaTransferProtocolManager override.
virtual void OpenStorage(const std::string& storage_name,
const std::string& mode,
const OpenStorageCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(storage_info_map_, storage_name) || !mtp_client_) {
callback.Run(std::string(), true);
return;
}
open_storage_callbacks_.push(callback);
mtp_client_->OpenStorage(
storage_name,
mode,
base::Bind(&MediaTransferProtocolManagerImpl::OnOpenStorage,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnOpenStorageError,
weak_ptr_factory_.GetWeakPtr()));
}
// MediaTransferProtocolManager override.
virtual void CloseStorage(const std::string& storage_handle,
const CloseStorageCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(true);
return;
}
close_storage_callbacks_.push(std::make_pair(callback, storage_handle));
mtp_client_->CloseStorage(
storage_handle,
base::Bind(&MediaTransferProtocolManagerImpl::OnCloseStorage,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnCloseStorageError,
weak_ptr_factory_.GetWeakPtr()));
}
// MediaTransferProtocolManager override.
virtual void ReadDirectoryByPath(
const std::string& storage_handle,
const std::string& path,
const ReadDirectoryCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(std::vector<MtpFileEntry>(), true);
return;
}
read_directory_callbacks_.push(callback);
mtp_client_->ReadDirectoryByPath(
storage_handle,
path,
base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectory,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError,
weak_ptr_factory_.GetWeakPtr()));
}
// MediaTransferProtocolManager override.
virtual void ReadDirectoryById(
const std::string& storage_handle,
uint32 file_id,
const ReadDirectoryCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(std::vector<MtpFileEntry>(), true);
return;
}
read_directory_callbacks_.push(callback);
mtp_client_->ReadDirectoryById(
storage_handle,
file_id,
base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectory,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError,
weak_ptr_factory_.GetWeakPtr()));
}
// MediaTransferProtocolManager override.
virtual void ReadFileChunkByPath(const std::string& storage_handle,
const std::string& path,
uint32 offset,
uint32 count,
const ReadFileCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(std::string(), true);
return;
}
read_file_callbacks_.push(callback);
mtp_client_->ReadFileChunkByPath(
storage_handle, path, offset, count,
base::Bind(&MediaTransferProtocolManagerImpl::OnReadFile,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnReadFileError,
weak_ptr_factory_.GetWeakPtr()));
}
// MediaTransferProtocolManager override.
virtual void ReadFileChunkById(const std::string& storage_handle,
uint32 file_id,
uint32 offset,
uint32 count,
const ReadFileCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(std::string(), true);
return;
}
read_file_callbacks_.push(callback);
mtp_client_->ReadFileChunkById(
storage_handle, file_id, offset, count,
base::Bind(&MediaTransferProtocolManagerImpl::OnReadFile,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnReadFileError,
weak_ptr_factory_.GetWeakPtr()));
}
virtual void GetFileInfoByPath(const std::string& storage_handle,
const std::string& path,
const GetFileInfoCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(MtpFileEntry(), true);
return;
}
get_file_info_callbacks_.push(callback);
mtp_client_->GetFileInfoByPath(
storage_handle,
path,
base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfo,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfoError,
weak_ptr_factory_.GetWeakPtr()));
}
virtual void GetFileInfoById(const std::string& storage_handle,
uint32 file_id,
const GetFileInfoCallback& callback) OVERRIDE {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
callback.Run(MtpFileEntry(), true);
return;
}
get_file_info_callbacks_.push(callback);
mtp_client_->GetFileInfoById(
storage_handle,
file_id,
base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfo,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfoError,
weak_ptr_factory_.GetWeakPtr()));
}
private:
// Map of storage names to storage info.
typedef std::map<std::string, MtpStorageInfo> StorageInfoMap;
// Callback queues - DBus communication is in-order, thus callbacks are
// received in the same order as the requests.
typedef std::queue<OpenStorageCallback> OpenStorageCallbackQueue;
// (callback, handle)
typedef std::queue<std::pair<CloseStorageCallback, std::string>
> CloseStorageCallbackQueue;
typedef std::queue<ReadDirectoryCallback> ReadDirectoryCallbackQueue;
typedef std::queue<ReadFileCallback> ReadFileCallbackQueue;
typedef std::queue<GetFileInfoCallback> GetFileInfoCallbackQueue;
void OnStorageAttached(const std::string& storage_name) {
DCHECK(thread_checker_.CalledOnValidThread());
mtp_client_->GetStorageInfo(
storage_name,
base::Bind(&MediaTransferProtocolManagerImpl::OnGetStorageInfo,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&base::DoNothing));
}
void OnStorageDetached(const std::string& storage_name) {
DCHECK(thread_checker_.CalledOnValidThread());
if (storage_info_map_.erase(storage_name) == 0) {
// This can happen for a storage where
// MediaTransferProtocolDaemonClient::GetStorageInfo() failed.
// Return to avoid giving observers phantom detach events.
return;
}
FOR_EACH_OBSERVER(Observer,
observers_,
StorageChanged(false /* detach */, storage_name));
}
void OnStorageChanged(bool is_attach, const std::string& storage_name) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(mtp_client_);
if (is_attach)
OnStorageAttached(storage_name);
else
OnStorageDetached(storage_name);
}
void OnEnumerateStorages(const std::vector<std::string>& storage_names) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(mtp_client_);
for (size_t i = 0; i < storage_names.size(); ++i) {
if (ContainsKey(storage_info_map_, storage_names[i])) {
// OnStorageChanged() might have gotten called first.
continue;
}
OnStorageAttached(storage_names[i]);
}
}
void OnGetStorageInfo(const MtpStorageInfo& storage_info) {
DCHECK(thread_checker_.CalledOnValidThread());
const std::string& storage_name = storage_info.storage_name();
if (ContainsKey(storage_info_map_, storage_name)) {
// This should not happen, since MediaTransferProtocolManagerImpl should
// only call EnumerateStorages() once, which populates |storage_info_map_|
// with the already-attached devices.
// After that, all incoming signals are either for new storage
// attachments, which should not be in |storage_info_map_|, or for
// storage detachments, which do not add to |storage_info_map_|.
// Return to avoid giving observers phantom detach events.
NOTREACHED();
return;
}
// New storage. Add it and let the observers know.
storage_info_map_.insert(std::make_pair(storage_name, storage_info));
FOR_EACH_OBSERVER(Observer,
observers_,
StorageChanged(true /* is attach */, storage_name));
}
void OnOpenStorage(const std::string& handle) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!ContainsKey(handles_, handle)) {
handles_.insert(handle);
open_storage_callbacks_.front().Run(handle, false);
} else {
NOTREACHED();
open_storage_callbacks_.front().Run(std::string(), true);
}
open_storage_callbacks_.pop();
}
void OnOpenStorageError() {
open_storage_callbacks_.front().Run(std::string(), true);
open_storage_callbacks_.pop();
}
void OnCloseStorage() {
DCHECK(thread_checker_.CalledOnValidThread());
const std::string& handle = close_storage_callbacks_.front().second;
if (ContainsKey(handles_, handle)) {
handles_.erase(handle);
close_storage_callbacks_.front().first.Run(false);
} else {
NOTREACHED();
close_storage_callbacks_.front().first.Run(true);
}
close_storage_callbacks_.pop();
}
void OnCloseStorageError() {
DCHECK(thread_checker_.CalledOnValidThread());
close_storage_callbacks_.front().first.Run(true);
close_storage_callbacks_.pop();
}
void OnReadDirectory(const std::vector<MtpFileEntry>& file_entries) {
DCHECK(thread_checker_.CalledOnValidThread());
read_directory_callbacks_.front().Run(file_entries, false);
read_directory_callbacks_.pop();
}
void OnReadDirectoryError() {
DCHECK(thread_checker_.CalledOnValidThread());
read_directory_callbacks_.front().Run(std::vector<MtpFileEntry>(), true);
read_directory_callbacks_.pop();
}
void OnReadFile(const std::string& data) {
DCHECK(thread_checker_.CalledOnValidThread());
read_file_callbacks_.front().Run(data, false);
read_file_callbacks_.pop();
}
void OnReadFileError() {
DCHECK(thread_checker_.CalledOnValidThread());
read_file_callbacks_.front().Run(std::string(), true);
read_file_callbacks_.pop();
}
void OnGetFileInfo(const MtpFileEntry& entry) {
DCHECK(thread_checker_.CalledOnValidThread());
get_file_info_callbacks_.front().Run(entry, false);
get_file_info_callbacks_.pop();
}
void OnGetFileInfoError() {
DCHECK(thread_checker_.CalledOnValidThread());
get_file_info_callbacks_.front().Run(MtpFileEntry(), true);
get_file_info_callbacks_.pop();
}
// Get the Bus object used to communicate with mtpd.
dbus::Bus* GetBus() {
DCHECK(thread_checker_.CalledOnValidThread());
#if defined(OS_CHROMEOS)
return chromeos::DBusThreadManager::Get()->GetSystemBus();
#else
return session_bus_.get();
#endif
}
// Callback to finish initialization after figuring out if the mtpd service
// has an owner, or if the service owner has changed.
// |mtpd_service_owner| contains the name of the current owner, if any.
void FinishSetupOnOriginThread(const std::string& mtpd_service_owner) {
DCHECK(thread_checker_.CalledOnValidThread());
if (mtpd_service_owner == current_mtpd_owner_)
return;
// In the case of a new service owner, clear |storage_info_map_|.
// Assume all storages have been disconnected. If there is a new service
// owner, reconnecting to it will reconnect all the storages as well.
// Save a copy of |storage_info_map_| keys as |storage_info_map_| can
// change in OnStorageDetached().
std::vector<std::string> storage_names;
for (StorageInfoMap::const_iterator it = storage_info_map_.begin();
it != storage_info_map_.end();
++it) {
storage_names.push_back(it->first);
}
for (size_t i = 0; i != storage_names.size(); ++i)
OnStorageDetached(storage_names[i]);
if (mtpd_service_owner.empty()) {
current_mtpd_owner_.clear();
mtp_client_.reset();
return;
}
current_mtpd_owner_ = mtpd_service_owner;
mtp_client_.reset(MediaTransferProtocolDaemonClient::Create(GetBus()));
// Set up signals and start initializing |storage_info_map_|.
mtp_client_->ListenForChanges(
base::Bind(&MediaTransferProtocolManagerImpl::OnStorageChanged,
weak_ptr_factory_.GetWeakPtr()));
mtp_client_->EnumerateStorages(
base::Bind(&MediaTransferProtocolManagerImpl::OnEnumerateStorages,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&base::DoNothing));
}
// Mtpd DBus client.
scoped_ptr<MediaTransferProtocolDaemonClient> mtp_client_;
#if !defined(OS_CHROMEOS)
// And a D-Bus session for talking to mtpd.
scoped_refptr<dbus::Bus> session_bus_;
#endif
// Device attachment / detachment observers.
ObserverList<Observer> observers_;
// Map to keep track of attached storages by name.
StorageInfoMap storage_info_map_;
// Set of open storage handles.
std::set<std::string> handles_;
dbus::Bus::GetServiceOwnerCallback mtpd_owner_changed_callback_;
std::string current_mtpd_owner_;
// Queued callbacks.
OpenStorageCallbackQueue open_storage_callbacks_;
CloseStorageCallbackQueue close_storage_callbacks_;
ReadDirectoryCallbackQueue read_directory_callbacks_;
ReadFileCallbackQueue read_file_callbacks_;
GetFileInfoCallbackQueue get_file_info_callbacks_;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<MediaTransferProtocolManagerImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaTransferProtocolManagerImpl);
};
} // namespace
// static
MediaTransferProtocolManager* MediaTransferProtocolManager::Initialize(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
DCHECK(!g_media_transfer_protocol_manager);
g_media_transfer_protocol_manager =
new MediaTransferProtocolManagerImpl(task_runner);
VLOG(1) << "MediaTransferProtocolManager initialized";
return g_media_transfer_protocol_manager;
}
} // namespace device