blob: 09bf2b03f6dcce398a1ee01da1dc0e6f17facd74 [file] [log] [blame]
// Copyright (c) 2013 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.
//
// MTPDeviceDelegateImplWin implementation.
#include "chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h"
#include <portabledevice.h>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
#include "chrome/browser/media_galleries/win/mtp_device_object_entry.h"
#include "chrome/browser/media_galleries/win/mtp_device_object_enumerator.h"
#include "chrome/browser/media_galleries/win/mtp_device_operations_util.h"
#include "chrome/browser/media_galleries/win/portable_device_map_service.h"
#include "chrome/browser/media_galleries/win/snapshot_file_details.h"
#include "chrome/browser/storage_monitor/storage_monitor.h"
#include "content/public/browser/browser_thread.h"
#include "webkit/common/fileapi/file_system_util.h"
namespace {
// Gets the details of the MTP partition storage specified by the
// |storage_path| on the UI thread. Returns true if the storage details are
// valid and returns false otherwise.
bool GetStorageInfoOnUIThread(const string16& storage_path,
string16* pnp_device_id,
string16* storage_object_id) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK(!storage_path.empty());
DCHECK(pnp_device_id);
DCHECK(storage_object_id);
string16 storage_device_id;
RemoveChars(storage_path, L"\\\\", &storage_device_id);
DCHECK(!storage_device_id.empty());
// TODO(gbillock): Take the StorageMonitor as an argument.
StorageMonitor* monitor = StorageMonitor::GetInstance();
DCHECK(monitor);
return monitor->GetMTPStorageInfoFromDeviceId(
UTF16ToUTF8(storage_device_id), pnp_device_id, storage_object_id);
}
// Returns the object id of the file object specified by the |file_path|,
// e.g. if the |file_path| is "\\MTP:StorageSerial:SID-{1001,,192}:125\DCIM"
// and |device_info.registered_device_path_| is
// "\\MTP:StorageSerial:SID-{1001,,192}:125", this function returns the
// identifier of the "DCIM" folder object.
//
// Returns an empty string if the device is detached while the request is in
// progress or when the |file_path| is invalid.
string16 GetFileObjectIdFromPathOnBlockingPoolThread(
const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
const base::FilePath& file_path) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!file_path.empty());
IPortableDevice* device =
PortableDeviceMapService::GetInstance()->GetPortableDevice(
device_info.registered_device_path);
if (!device)
return string16();
if (device_info.registered_device_path == file_path.value())
return device_info.storage_object_id;
base::FilePath relative_path;
if (!base::FilePath(device_info.registered_device_path).AppendRelativePath(
file_path, &relative_path))
return string16();
std::vector<string16> path_components;
relative_path.GetComponents(&path_components);
DCHECK(!path_components.empty());
string16 parent_id(device_info.storage_object_id);
string16 file_object_id;
for (size_t i = 0; i < path_components.size(); ++i) {
file_object_id =
media_transfer_protocol::GetObjectIdFromName(device, parent_id,
path_components[i]);
if (file_object_id.empty())
break;
parent_id = file_object_id;
}
return file_object_id;
}
// Returns a pointer to a new instance of AbstractFileEnumerator for the given
// |root| directory. Called on a blocking pool thread.
scoped_ptr<MTPDeviceObjectEnumerator>
CreateFileEnumeratorOnBlockingPoolThread(
const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
const base::FilePath& root) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!device_info.registered_device_path.empty());
DCHECK(!root.empty());
IPortableDevice* device =
PortableDeviceMapService::GetInstance()->GetPortableDevice(
device_info.registered_device_path);
if (!device)
return scoped_ptr<MTPDeviceObjectEnumerator>();
string16 object_id = GetFileObjectIdFromPathOnBlockingPoolThread(device_info,
root);
if (object_id.empty())
return scoped_ptr<MTPDeviceObjectEnumerator>();
MTPDeviceObjectEntries entries;
if (!media_transfer_protocol::GetDirectoryEntries(device, object_id,
&entries) ||
entries.empty())
return scoped_ptr<MTPDeviceObjectEnumerator>();
return scoped_ptr<MTPDeviceObjectEnumerator>(
new MTPDeviceObjectEnumerator(entries));
}
// Opens the device for communication on a blocking pool thread.
// |pnp_device_id| specifies the PnP device id.
// |registered_device_path| specifies the registered file system root path for
// the given device.
bool OpenDeviceOnBlockingPoolThread(const string16& pnp_device_id,
const string16& registered_device_path) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!pnp_device_id.empty());
DCHECK(!registered_device_path.empty());
base::win::ScopedComPtr<IPortableDevice> device =
media_transfer_protocol::OpenDevice(pnp_device_id);
bool init_succeeded = device.get() != NULL;
if (init_succeeded) {
PortableDeviceMapService::GetInstance()->AddPortableDevice(
registered_device_path, device.get());
}
return init_succeeded;
}
// Gets the |file_path| details from the MTP device specified by the
// |device_info.registered_device_path|. On success, |error| is set to
// base::PLATFORM_FILE_OK and fills in |file_info|. On failure, |error| is set
// to corresponding platform file error and |file_info| is not set.
base::PlatformFileError GetFileInfoOnBlockingPoolThread(
const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
const base::FilePath& file_path,
base::PlatformFileInfo* file_info) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!device_info.registered_device_path.empty());
DCHECK(!file_path.empty());
DCHECK(file_info);
IPortableDevice* device =
PortableDeviceMapService::GetInstance()->GetPortableDevice(
device_info.registered_device_path);
if (!device)
return base::PLATFORM_FILE_ERROR_FAILED;
string16 object_id = GetFileObjectIdFromPathOnBlockingPoolThread(device_info,
file_path);
if (object_id.empty())
return base::PLATFORM_FILE_ERROR_FAILED;
return media_transfer_protocol::GetFileEntryInfo(device, object_id,
file_info);
}
// Reads the |root| directory file entries on a blocking pool thread. On
// success, |error| is set to base::PLATFORM_FILE_OK and |entries| contains the
// directory file entries. On failure, |error| is set to platform file error
// and |entries| is not set.
base::PlatformFileError ReadDirectoryOnBlockingPoolThread(
const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
const base::FilePath& root,
fileapi::AsyncFileUtil::EntryList* entries) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!root.empty());
DCHECK(entries);
base::PlatformFileInfo file_info;
base::PlatformFileError error = GetFileInfoOnBlockingPoolThread(device_info,
root,
&file_info);
if (error != base::PLATFORM_FILE_OK)
return error;
if (!file_info.is_directory)
return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
base::FilePath current;
scoped_ptr<MTPDeviceObjectEnumerator> file_enum =
CreateFileEnumeratorOnBlockingPoolThread(device_info, root);
if (!file_enum)
return error;
while (!(current = file_enum->Next()).empty()) {
fileapi::DirectoryEntry entry;
entry.is_directory = file_enum->IsDirectory();
entry.name = fileapi::VirtualPath::BaseName(current).value();
entry.size = file_enum->Size();
entry.last_modified_time = file_enum->LastModifiedTime();
entries->push_back(entry);
}
return error;
}
// Gets the device file stream object on a blocking pool thread.
// |device_info| contains the device storage partition details.
// On success, returns base::PLATFORM_FILE_OK and file stream details are set in
// |file_details|. On failure, returns a platform file error and file stream
// details are not set in |file_details|.
base::PlatformFileError GetFileStreamOnBlockingPoolThread(
const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
SnapshotFileDetails* file_details) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(file_details);
DCHECK(!file_details->request_info().device_file_path.empty());
DCHECK(!file_details->request_info().snapshot_file_path.empty());
IPortableDevice* device =
PortableDeviceMapService::GetInstance()->GetPortableDevice(
device_info.registered_device_path);
if (!device)
return base::PLATFORM_FILE_ERROR_FAILED;
string16 file_object_id =
GetFileObjectIdFromPathOnBlockingPoolThread(
device_info, file_details->request_info().device_file_path);
if (file_object_id.empty())
return base::PLATFORM_FILE_ERROR_FAILED;
base::PlatformFileInfo file_info;
base::PlatformFileError error =
GetFileInfoOnBlockingPoolThread(
device_info,
file_details->request_info().device_file_path,
&file_info);
if (error != base::PLATFORM_FILE_OK)
return error;
DWORD optimal_transfer_size = 0;
base::win::ScopedComPtr<IStream> file_stream;
if (file_info.size > 0) {
HRESULT hr = media_transfer_protocol::GetFileStreamForObject(
device,
file_object_id,
file_stream.Receive(),
&optimal_transfer_size);
if (hr != S_OK)
return base::PLATFORM_FILE_ERROR_FAILED;
}
// LocalFileStreamReader is used to read the contents of the snapshot file.
// Snapshot file modification time does not match the last modified time
// of the original media file. Therefore, set the last modified time to null
// in order to avoid the verification in LocalFileStreamReader.
//
// Users will use HTML5 FileSystem Entry getMetadata() interface to get the
// actual last modified time of the media file.
file_info.last_modified = base::Time();
DCHECK(file_info.size == 0 || optimal_transfer_size > 0U);
file_details->set_file_info(file_info);
file_details->set_device_file_stream(file_stream);
file_details->set_optimal_transfer_size(optimal_transfer_size);
return error;
}
// Copies the data chunk from device file to the snapshot file based on the
// parameters specified by |file_details|.
// Returns the total number of bytes written to the snapshot file for non-empty
// files, or 0 on failure. For empty files, just return 0.
DWORD WriteDataChunkIntoSnapshotFileOnBlockingPoolThread(
const SnapshotFileDetails& file_details) {
base::ThreadRestrictions::AssertIOAllowed();
if (file_details.file_info().size == 0)
return 0;
return media_transfer_protocol::CopyDataChunkToLocalFile(
file_details.device_file_stream(),
file_details.request_info().snapshot_file_path,
file_details.optimal_transfer_size());
}
void DeletePortableDeviceOnBlockingPoolThread(
const string16& registered_device_path) {
base::ThreadRestrictions::AssertIOAllowed();
PortableDeviceMapService::GetInstance()->RemovePortableDevice(
registered_device_path);
}
} // namespace
// Used by CreateMTPDeviceAsyncDelegate() to create the MTP device
// delegate on the IO thread.
void OnGetStorageInfoCreateDelegate(
const string16& device_location,
const CreateMTPDeviceAsyncDelegateCallback& callback,
string16* pnp_device_id,
string16* storage_object_id,
bool succeeded) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(pnp_device_id);
DCHECK(storage_object_id);
if (!succeeded)
return;
callback.Run(new MTPDeviceDelegateImplWin(device_location,
*pnp_device_id,
*storage_object_id));
}
void CreateMTPDeviceAsyncDelegate(
const string16& device_location,
const CreateMTPDeviceAsyncDelegateCallback& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!device_location.empty());
string16* pnp_device_id = new string16;
string16* storage_object_id = new string16;
content::BrowserThread::PostTaskAndReplyWithResult<bool>(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&GetStorageInfoOnUIThread,
device_location,
base::Unretained(pnp_device_id),
base::Unretained(storage_object_id)),
base::Bind(&OnGetStorageInfoCreateDelegate,
device_location,
callback,
base::Owned(pnp_device_id),
base::Owned(storage_object_id)));
}
// MTPDeviceDelegateImplWin ---------------------------------------------------
MTPDeviceDelegateImplWin::StorageDeviceInfo::StorageDeviceInfo(
const string16& pnp_device_id,
const string16& registered_device_path,
const string16& storage_object_id)
: pnp_device_id(pnp_device_id),
registered_device_path(registered_device_path),
storage_object_id(storage_object_id) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
}
MTPDeviceDelegateImplWin::PendingTaskInfo::PendingTaskInfo(
const tracked_objects::Location& location,
const base::Callback<base::PlatformFileError(void)>& task,
const base::Callback<void(base::PlatformFileError)>& reply)
: location(location),
task(task),
reply(reply) {
}
MTPDeviceDelegateImplWin::MTPDeviceDelegateImplWin(
const string16& registered_device_path,
const string16& pnp_device_id,
const string16& storage_object_id)
: storage_device_info_(pnp_device_id, registered_device_path,
storage_object_id),
init_state_(UNINITIALIZED),
media_task_runner_(MediaFileSystemBackend::MediaTaskRunner()),
task_in_progress_(false),
weak_ptr_factory_(this) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!registered_device_path.empty());
DCHECK(!pnp_device_id.empty());
DCHECK(!storage_object_id.empty());
DCHECK(media_task_runner_.get());
}
MTPDeviceDelegateImplWin::~MTPDeviceDelegateImplWin() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
}
void MTPDeviceDelegateImplWin::GetFileInfo(
const base::FilePath& file_path,
const GetFileInfoSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!file_path.empty());
base::PlatformFileInfo* file_info = new base::PlatformFileInfo;
EnsureInitAndRunTask(
PendingTaskInfo(FROM_HERE,
base::Bind(&GetFileInfoOnBlockingPoolThread,
storage_device_info_,
file_path,
base::Unretained(file_info)),
base::Bind(&MTPDeviceDelegateImplWin::OnGetFileInfo,
weak_ptr_factory_.GetWeakPtr(),
success_callback,
error_callback,
base::Owned(file_info))));
}
void MTPDeviceDelegateImplWin::ReadDirectory(
const base::FilePath& root,
const ReadDirectorySuccessCallback& success_callback,
const ErrorCallback& error_callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!root.empty());
fileapi::AsyncFileUtil::EntryList* entries =
new fileapi::AsyncFileUtil::EntryList;
EnsureInitAndRunTask(
PendingTaskInfo(FROM_HERE,
base::Bind(&ReadDirectoryOnBlockingPoolThread,
storage_device_info_,
root,
base::Unretained(entries)),
base::Bind(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
weak_ptr_factory_.GetWeakPtr(),
success_callback,
error_callback,
base::Owned(entries))));
}
void MTPDeviceDelegateImplWin::CreateSnapshotFile(
const base::FilePath& device_file_path,
const base::FilePath& snapshot_file_path,
const CreateSnapshotFileSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!device_file_path.empty());
DCHECK(!snapshot_file_path.empty());
scoped_ptr<SnapshotFileDetails> file_details(
new SnapshotFileDetails(SnapshotRequestInfo(device_file_path,
snapshot_file_path,
success_callback,
error_callback)));
// Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
// it is owned by |file_details| in the reply callback.
EnsureInitAndRunTask(
PendingTaskInfo(FROM_HERE,
base::Bind(&GetFileStreamOnBlockingPoolThread,
storage_device_info_,
file_details.get()),
base::Bind(&MTPDeviceDelegateImplWin::OnGetFileStream,
weak_ptr_factory_.GetWeakPtr(),
base::Passed(&file_details))));
}
void MTPDeviceDelegateImplWin::CancelPendingTasksAndDeleteDelegate() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
PortableDeviceMapService::GetInstance()->MarkPortableDeviceForDeletion(
storage_device_info_.registered_device_path);
media_task_runner_->PostTask(
FROM_HERE,
base::Bind(&DeletePortableDeviceOnBlockingPoolThread,
storage_device_info_.registered_device_path));
while (!pending_tasks_.empty())
pending_tasks_.pop();
delete this;
}
void MTPDeviceDelegateImplWin::EnsureInitAndRunTask(
const PendingTaskInfo& task_info) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if ((init_state_ == INITIALIZED) && !task_in_progress_) {
DCHECK(pending_tasks_.empty());
DCHECK(!current_snapshot_details_.get());
base::PostTaskAndReplyWithResult(media_task_runner_,
task_info.location,
task_info.task,
task_info.reply);
task_in_progress_ = true;
return;
}
pending_tasks_.push(task_info);
if (init_state_ == UNINITIALIZED) {
init_state_ = PENDING_INIT;
base::PostTaskAndReplyWithResult(
media_task_runner_,
FROM_HERE,
base::Bind(&OpenDeviceOnBlockingPoolThread,
storage_device_info_.pnp_device_id,
storage_device_info_.registered_device_path),
base::Bind(&MTPDeviceDelegateImplWin::OnInitCompleted,
weak_ptr_factory_.GetWeakPtr()));
task_in_progress_ = true;
}
}
void MTPDeviceDelegateImplWin::WriteDataChunkIntoSnapshotFile() {
DCHECK(current_snapshot_details_.get());
base::PostTaskAndReplyWithResult(
media_task_runner_,
FROM_HERE,
base::Bind(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
*current_snapshot_details_),
base::Bind(&MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
weak_ptr_factory_.GetWeakPtr(),
current_snapshot_details_->request_info().snapshot_file_path));
}
void MTPDeviceDelegateImplWin::ProcessNextPendingRequest() {
DCHECK(!task_in_progress_);
if (pending_tasks_.empty())
return;
const PendingTaskInfo& task_info = pending_tasks_.front();
task_in_progress_ = true;
base::PostTaskAndReplyWithResult(media_task_runner_,
task_info.location,
task_info.task,
task_info.reply);
pending_tasks_.pop();
}
void MTPDeviceDelegateImplWin::OnInitCompleted(bool succeeded) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
task_in_progress_ = false;
ProcessNextPendingRequest();
}
void MTPDeviceDelegateImplWin::OnGetFileInfo(
const GetFileInfoSuccessCallback& success_callback,
const ErrorCallback& error_callback,
base::PlatformFileInfo* file_info,
base::PlatformFileError error) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(file_info);
if (error == base::PLATFORM_FILE_OK)
success_callback.Run(*file_info);
else
error_callback.Run(error);
task_in_progress_ = false;
ProcessNextPendingRequest();
}
void MTPDeviceDelegateImplWin::OnDidReadDirectory(
const ReadDirectorySuccessCallback& success_callback,
const ErrorCallback& error_callback,
fileapi::AsyncFileUtil::EntryList* file_list,
base::PlatformFileError error) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(file_list);
if (error == base::PLATFORM_FILE_OK)
success_callback.Run(*file_list, false /*no more entries*/);
else
error_callback.Run(error);
task_in_progress_ = false;
ProcessNextPendingRequest();
}
void MTPDeviceDelegateImplWin::OnGetFileStream(
scoped_ptr<SnapshotFileDetails> file_details,
base::PlatformFileError error) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(file_details);
DCHECK(!file_details->request_info().device_file_path.empty());
DCHECK(!file_details->request_info().snapshot_file_path.empty());
DCHECK(!current_snapshot_details_.get());
if (error != base::PLATFORM_FILE_OK) {
file_details->request_info().error_callback.Run(error);
task_in_progress_ = false;
ProcessNextPendingRequest();
return;
}
DCHECK(file_details->file_info().size == 0 ||
file_details->device_file_stream());
current_snapshot_details_.reset(file_details.release());
WriteDataChunkIntoSnapshotFile();
}
void MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile(
const base::FilePath& snapshot_file_path,
DWORD bytes_written) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(!snapshot_file_path.empty());
if (!current_snapshot_details_.get())
return;
DCHECK_EQ(
current_snapshot_details_->request_info().snapshot_file_path.value(),
snapshot_file_path.value());
bool succeeded = false;
bool should_continue = false;
if (current_snapshot_details_->file_info().size > 0) {
if (current_snapshot_details_->AddBytesWritten(bytes_written)) {
if (current_snapshot_details_->IsSnapshotFileWriteComplete()) {
succeeded = true;
} else {
should_continue = true;
}
}
} else {
// Handle empty files.
DCHECK_EQ(0U, bytes_written);
succeeded = true;
}
if (should_continue) {
WriteDataChunkIntoSnapshotFile();
return;
}
if (succeeded) {
current_snapshot_details_->request_info().success_callback.Run(
current_snapshot_details_->file_info(),
current_snapshot_details_->request_info().snapshot_file_path);
} else {
current_snapshot_details_->request_info().error_callback.Run(
base::PLATFORM_FILE_ERROR_FAILED);
}
task_in_progress_ = false;
current_snapshot_details_.reset();
ProcessNextPendingRequest();
}