// 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 #include #include #include #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 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 GetStorages() const OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); std::vector 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(), 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(), 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 StorageInfoMap; // Callback queues - DBus communication is in-order, thus callbacks are // received in the same order as the requests. typedef std::queue OpenStorageCallbackQueue; // (callback, handle) typedef std::queue > CloseStorageCallbackQueue; typedef std::queue ReadDirectoryCallbackQueue; typedef std::queue ReadFileCallbackQueue; typedef std::queue 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& 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& 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(), 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 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 mtp_client_; #if !defined(OS_CHROMEOS) // And a D-Bus session for talking to mtpd. scoped_refptr session_bus_; #endif // Device attachment / detachment observers. ObserverList observers_; // Map to keep track of attached storages by name. StorageInfoMap storage_info_map_; // Set of open storage handles. std::set 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 weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(MediaTransferProtocolManagerImpl); }; } // namespace // static MediaTransferProtocolManager* MediaTransferProtocolManager::Initialize( scoped_refptr 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