Commit 68df59a3 authored by gbillock@chromium.org's avatar gbillock@chromium.org

[Media Galleries] Switch Mac MTP delegate to async interface.

BUG=None


Review URL: https://chromiumcodereview.appspot.com/12255023

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@192631 0039d316-1c4b-4281-b951-d872f2087c98
parent cb75116a
...@@ -5,21 +5,15 @@ ...@@ -5,21 +5,15 @@
#ifndef CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_MAC_H_ #ifndef CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_MAC_H_
#define CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_MAC_H_ #define CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_MAC_H_
#include <list>
#include <map>
#include <vector> #include <vector>
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/hash_tables.h" #include "base/hash_tables.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/platform_file.h" #include "base/platform_file.h"
#include "base/sequenced_task_runner_helpers.h" #include "webkit/fileapi/media/mtp_device_async_delegate.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "webkit/fileapi/file_system_file_util.h"
#include "webkit/fileapi/media/mtp_device_delegate.h"
namespace base {
class SequencedTaskRunner;
}
namespace chrome { namespace chrome {
...@@ -28,94 +22,89 @@ namespace chrome { ...@@ -28,94 +22,89 @@ namespace chrome {
// and names of all files notified through the ItemAdded call will be // and names of all files notified through the ItemAdded call will be
// all appear as children of that directory. (ItemAdded calls with directories // all appear as children of that directory. (ItemAdded calls with directories
// will be ignored.) // will be ignored.)
class MTPDeviceDelegateImplMac : public fileapi::MTPDeviceDelegate { // Note on thread management: This class is thread-compatible: it can be created
// on any thread, but then mutates all state on the UI thread. The async
// delegate interface can be invoked on any thread, as it simply forwards calls
// to the UI thread.
class MTPDeviceDelegateImplMac : public fileapi::MTPDeviceAsyncDelegate {
public: public:
MTPDeviceDelegateImplMac(const std::string& device_id, MTPDeviceDelegateImplMac(const std::string& device_id,
const base::FilePath::StringType& synthetic_path, const base::FilePath::StringType& synthetic_path);
base::SequencedTaskRunner* media_task_runner);
// MTPDeviceDelegate: // MTPDeviceAsyncDelegate implementation. These functions are called on the
virtual base::PlatformFileError GetFileInfo( // IO thread by the async filesystem file util. They forward to
// similarly-named methods on the UI thread.
virtual void GetFileInfo(
const base::FilePath& file_path, const base::FilePath& file_path,
base::PlatformFileInfo* file_info) OVERRIDE; const GetFileInfoSuccessCallback& success_callback,
virtual scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> const ErrorCallback& error_callback) OVERRIDE;
CreateFileEnumerator(const base::FilePath& root, bool recursive) OVERRIDE; virtual void ReadDirectory(
virtual base::PlatformFileError CreateSnapshotFile( const base::FilePath& root,
const ReadDirectorySuccessCallback& success_callback,
const ErrorCallback& error_callback) OVERRIDE;
virtual void CreateSnapshotFile(
const base::FilePath& device_file_path, const base::FilePath& device_file_path,
const base::FilePath& local_path, const base::FilePath& local_path,
base::PlatformFileInfo* file_info) OVERRIDE; const CreateSnapshotFileSuccessCallback& success_callback,
const ErrorCallback& error_callback) OVERRIDE;
virtual void CancelPendingTasksAndDeleteDelegate() OVERRIDE; virtual void CancelPendingTasksAndDeleteDelegate() OVERRIDE;
// Forward delegates for ImageCaptureDeviceListener // Forward delegates for ImageCaptureDeviceListener. These are
// invoked in callbacks of the ImageCapture library on the UI thread.
virtual void ItemAdded(const std::string& name, virtual void ItemAdded(const std::string& name,
const base::PlatformFileInfo& info); const base::PlatformFileInfo& info);
virtual void NoMoreItems(); virtual void NoMoreItems();
virtual void DownloadedFile(const std::string& name, virtual void DownloadedFile(const std::string& name,
base::PlatformFileError error); base::PlatformFileError error);
// Implementation returned by |CreateFileEnumerator()|. Must be deleted // Scheduled when early directory reads are requested. The
// before CancelPendingTasksAndDeleteDelegate is called. // timeout will signal an ABORT error to the caller if the
class Enumerator : // device metadata cannot be read.
public fileapi::FileSystemFileUtil::AbstractFileEnumerator { void ReadDirectoryTimeout(const base::FilePath& root);
public:
explicit Enumerator(MTPDeviceDelegateImplMac* delegate);
virtual ~Enumerator();
virtual base::FilePath Next() OVERRIDE;
virtual int64 Size() OVERRIDE;
virtual base::Time LastModifiedTime() OVERRIDE;
virtual bool IsDirectory() OVERRIDE;
// Called by the delegate to signal any waiters that the received items
// list has changed state.
void ItemsChanged();
private:
MTPDeviceDelegateImplMac* delegate_;
size_t position_;
base::WaitableEvent wait_for_items_;
};
// GetFile and HasAllFiles called by enumerators.
base::FilePath GetFile(size_t index);
bool ReceivedAllFiles();
void RemoveEnumerator(Enumerator* enumerator);
private: private:
friend class base::DeleteHelper<MTPDeviceDelegateImplMac>;
class DeviceListener; class DeviceListener;
virtual ~MTPDeviceDelegateImplMac(); virtual ~MTPDeviceDelegateImplMac();
std::string device_id_; // Delegate for GetFileInfo, called on the UI thread.
base::FilePath root_path_; void GetFileInfoImpl(const base::FilePath& file_path,
base::PlatformFileInfo* file_info,
base::PlatformFileError* error);
// Stores a reference to worker pool thread. All requests and response of file // Delegate for ReadDirectory, called on the UI thread.
// operations are posted on |media_task_runner_|. All callbacks from the void ReadDirectoryImpl(
// camera will come through this task runner as well. const base::FilePath& root,
scoped_refptr<base::SequencedTaskRunner> media_task_runner_; const ReadDirectorySuccessCallback& success_callback,
const ErrorCallback& error_callback);
// Interface object for the camera underlying this MTP session. // Delegate for CreateSnapshotFile, called on the UI thread.
scoped_ptr<DeviceListener> camera_interface_; void DownloadFile(
const base::FilePath& device_file_path,
const base::FilePath& local_path,
const CreateSnapshotFileSuccessCallback& success_callback,
const ErrorCallback& error_callback);
// This lock protects all subsequent state. While calling into the delegate // Public for closures; should not be called except by
// from the FileSystem API happens in sequence, these calls may be // CancelTasksAndDeleteDelegate.
// interleaved with calls on other threads in the sequenced task runner void CancelAndDelete();
// forwarded from the device.
base::Lock mutex_;
// Weak pointer to the enumerator which may be listening for files to come in. // Cancels any outstanding downloads.
Enumerator* enumerator_; void CancelDownloads();
// Stores a map from filename to file metadata received from the camera. // If necessary, notifies the ReadDirectory callback that all data
base::hash_map<base::FilePath::StringType, base::PlatformFileInfo> file_info_; // has been read.
void NotifyReadDir();
std::string device_id_;
base::FilePath root_path_;
// Notification for pending download. // Interface object for the camera underlying this MTP session.
base::WaitableEvent* file_download_event_; scoped_ptr<DeviceListener> camera_interface_;
// Resulting error code for pending download. // Stores a map from filename to file metadata received from the camera.
base::PlatformFileError file_download_error_; base::hash_map<base::FilePath::StringType,
base::PlatformFileInfo> file_info_;
// List of files received from the camera. // List of files received from the camera.
std::vector<base::FilePath> file_paths_; std::vector<base::FilePath> file_paths_;
...@@ -123,6 +112,28 @@ class MTPDeviceDelegateImplMac : public fileapi::MTPDeviceDelegate { ...@@ -123,6 +112,28 @@ class MTPDeviceDelegateImplMac : public fileapi::MTPDeviceDelegate {
// Set to true when all file metadata has been received from the camera. // Set to true when all file metadata has been received from the camera.
bool received_all_files_; bool received_all_files_;
typedef std::map<std::string,
std::pair<CreateSnapshotFileSuccessCallback,
ErrorCallback> > ReadFileTransactionMap;
struct ReadDirectoryRequest {
ReadDirectoryRequest(const base::FilePath& dir,
ReadDirectorySuccessCallback success_cb,
ErrorCallback error_cb);
~ReadDirectoryRequest();
base::FilePath directory;
ReadDirectorySuccessCallback success_callback;
ErrorCallback error_callback;
};
typedef std::list<ReadDirectoryRequest> ReadDirTransactionList;
ReadFileTransactionMap read_file_transactions_;
ReadDirTransactionList read_dir_transactions_;
base::WeakPtrFactory<MTPDeviceDelegateImplMac> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMac); DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMac);
}; };
......
...@@ -5,17 +5,31 @@ ...@@ -5,17 +5,31 @@
#include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h" #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
#include "base/memory/scoped_nsobject.h" #include "base/memory/scoped_nsobject.h"
#include "base/sequenced_task_runner.h"
#include "base/sequenced_task_runner_helpers.h"
#include "base/threading/sequenced_worker_pool.h" #include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/media_galleries/mtp_device_delegate_impl.h" #include "chrome/browser/media_galleries/mtp_device_delegate_impl.h"
#include "chrome/browser/storage_monitor/image_capture_device.h" #include "chrome/browser/storage_monitor/image_capture_device.h"
#include "chrome/browser/storage_monitor/image_capture_device_manager.h" #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
#include "chrome/browser/storage_monitor/media_storage_util.h" #include "chrome/browser/storage_monitor/media_storage_util.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "webkit/fileapi/async_file_util.h"
namespace chrome { namespace chrome {
namespace {
int kReadDirectoryTimeLimitSeconds = 20;
using fileapi::MTPDeviceAsyncDelegate;
typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
CreateSnapshotFileSuccessCallback;
typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback;
typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback
GetFileInfoSuccessCallback;
typedef MTPDeviceAsyncDelegate::ReadDirectorySuccessCallback
ReadDirectorySuccessCallback;
} // namespace
// This class handles the UI-thread hand-offs needed to interface // This class handles the UI-thread hand-offs needed to interface
// with the ImageCapture library. It will forward callbacks to // with the ImageCapture library. It will forward callbacks to
// its delegate on the task runner with which it is created. All // its delegate on the task runner with which it is created. All
...@@ -42,6 +56,10 @@ class MTPDeviceDelegateImplMac::DeviceListener ...@@ -42,6 +56,10 @@ class MTPDeviceDelegateImplMac::DeviceListener
base::PlatformFileError error) OVERRIDE; base::PlatformFileError error) OVERRIDE;
virtual void DeviceRemoved() OVERRIDE; virtual void DeviceRemoved() OVERRIDE;
// Used during delegate destruction to ensure there are no more calls
// to the delegate by the listener.
virtual void ResetDelegate();
private: private:
scoped_nsobject<ImageCaptureDevice> camera_device_; scoped_nsobject<ImageCaptureDevice> camera_device_;
...@@ -75,35 +93,40 @@ void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile( ...@@ -75,35 +93,40 @@ void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded( void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
const std::string& name, const std::string& name,
const base::PlatformFileInfo& info) { const base::PlatformFileInfo& info) {
delegate_->ItemAdded(name, info); if (delegate_)
delegate_->ItemAdded(name, info);
} }
void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() { void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
delegate_->NoMoreItems(); if (delegate_)
delegate_->NoMoreItems();
} }
void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile( void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
const std::string& name, const std::string& name,
base::PlatformFileError error) { base::PlatformFileError error) {
delegate_->DownloadedFile(name, error); if (delegate_)
delegate_->DownloadedFile(name, error);
} }
void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() { void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
[camera_device_ close]; [camera_device_ close];
camera_device_.reset(); camera_device_.reset();
if (delegate_)
delegate_->NoMoreItems();
}
void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
delegate_ = NULL;
} }
MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac( MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac(
const std::string& device_id, const std::string& device_id,
const base::FilePath::StringType& synthetic_path, const base::FilePath::StringType& synthetic_path)
base::SequencedTaskRunner* media_task_runner)
: device_id_(device_id), : device_id_(device_id),
root_path_(synthetic_path), root_path_(synthetic_path),
media_task_runner_(media_task_runner), received_all_files_(false),
enumerator_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
file_download_event_(NULL),
file_download_error_(base::PLATFORM_FILE_OK),
received_all_files_(false) {
// Make a synthetic entry for the root of the filesystem. // Make a synthetic entry for the root of the filesystem.
base::PlatformFileInfo info; base::PlatformFileInfo info;
...@@ -118,99 +141,184 @@ MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac( ...@@ -118,99 +141,184 @@ MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac(
} }
MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() { MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
DCHECK(enumerator_ == NULL);
} }
base::PlatformFileError MTPDeviceDelegateImplMac::GetFileInfo( namespace {
void ForwardGetFileInfo(
base::PlatformFileInfo* info,
base::PlatformFileError* error,
const GetFileInfoSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
if (*error == base::PLATFORM_FILE_OK)
success_callback.Run(*info);
else
error_callback.Run(*error);
}
} // namespace
void MTPDeviceDelegateImplMac::GetFileInfo(
const base::FilePath& file_path,
const GetFileInfoSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
base::PlatformFileInfo* info = new base::PlatformFileInfo;
base::PlatformFileError* error = new base::PlatformFileError;
// Note: ownership of these objects passed into the reply callback.
content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI,
FROM_HERE,
base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl,
base::Unretained(this), file_path, info, error),
base::Bind(&ForwardGetFileInfo,
base::Owned(info), base::Owned(error),
success_callback, error_callback));
}
void MTPDeviceDelegateImplMac::ReadDirectory(
const base::FilePath& root,
const ReadDirectorySuccessCallback& success_callback,
const ErrorCallback& error_callback) {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
base::Unretained(this),
root, success_callback, error_callback));
}
void MTPDeviceDelegateImplMac::CreateSnapshotFile(
const base::FilePath& device_file_path,
const base::FilePath& local_path,
const CreateSnapshotFileSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&MTPDeviceDelegateImplMac::DownloadFile,
base::Unretained(this),
device_file_path, local_path,
success_callback, error_callback));
}
void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
base::Unretained(this)));
}
void MTPDeviceDelegateImplMac::GetFileInfoImpl(
const base::FilePath& file_path, const base::FilePath& file_path,
base::PlatformFileInfo* file_info) { base::PlatformFileInfo* file_info,
base::AutoLock lock(mutex_); base::PlatformFileError* error) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
base::hash_map<base::FilePath::StringType, base::hash_map<base::FilePath::StringType,
base::PlatformFileInfo>::const_iterator i = base::PlatformFileInfo>::const_iterator i =
file_info_.find(file_path.value()); file_info_.find(file_path.value());
if (i == file_info_.end()) if (i == file_info_.end()) {
return base::PLATFORM_FILE_ERROR_NOT_FOUND; *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
return;
}
*file_info = i->second; *file_info = i->second;
return base::PLATFORM_FILE_OK; *error = base::PLATFORM_FILE_OK;
} }
scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
MTPDeviceDelegateImplMac::CreateFileEnumerator(const base::FilePath& root, const base::FilePath& root,
bool recursive) { const ReadDirectorySuccessCallback& success_callback,
base::AutoLock lock(mutex_); const ErrorCallback& error_callback) {
DCHECK(!enumerator_); DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
enumerator_ = new Enumerator(this);
return make_scoped_ptr(enumerator_) read_dir_transactions_.push_back(ReadDirectoryRequest(
.PassAs<fileapi::FileSystemFileUtil::AbstractFileEnumerator>(); root, success_callback, error_callback));
}
base::PlatformFileError MTPDeviceDelegateImplMac::CreateSnapshotFile( if (received_all_files_) {
const base::FilePath& device_file_path, NotifyReadDir();
const base::FilePath& local_path, return;
base::PlatformFileInfo* file_info) {
base::PlatformFileError error = GetFileInfo(device_file_path, file_info);
if (error != base::PLATFORM_FILE_OK)
return error;
// Set up to wait for download. Callers are ensuring this particular function
// will not be re-entered.
base::WaitableEvent waiter(true, false);
{
base::AutoLock lock(mutex_);
DCHECK(file_download_event_ == NULL);
file_download_event_ = &waiter;
} }
// Start the download in the UI thread. // Schedule a timeout in case the directory read doesn't complete.
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, content::BrowserThread::PostDelayedTask(
base::Bind(&DeviceListener::DownloadFile, content::BrowserThread::UI, FROM_HERE,
base::Unretained(camera_interface_.get()), base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
device_file_path.BaseName().value(), local_path)); weak_factory_.GetWeakPtr(), root),
waiter.Wait(); base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
{ }
base::AutoLock lock(mutex_);
file_download_event_ = NULL; void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
error = file_download_error_; const base::FilePath& root) {
if (received_all_files_)
return;
for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
iter != read_dir_transactions_.end();) {
if (iter->directory != root) {
iter++;
continue;
}
iter->error_callback.Run(base::PLATFORM_FILE_ERROR_ABORT);
iter = read_dir_transactions_.erase(iter);
} }
}
// Modify the last modified time to null. This prevents the time stamp void MTPDeviceDelegateImplMac::DownloadFile(
// verification in LocalFileStreamReader. const base::FilePath& device_file_path,
file_info->last_modified = base::Time(); const base::FilePath& local_path,
const CreateSnapshotFileSuccessCallback& success_callback,
const ErrorCallback& error_callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
return error; base::PlatformFileError error;
base::PlatformFileInfo info;
GetFileInfoImpl(device_file_path, &info, &error);
if (error != base::PLATFORM_FILE_OK) {
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(error_callback,
error));
return;
}
read_file_transactions_[device_file_path.BaseName().value()] =
std::make_pair(success_callback, error_callback);
camera_interface_->DownloadFile(device_file_path.BaseName().value(),
local_path);
} }
void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() { void MTPDeviceDelegateImplMac::CancelAndDelete() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Artificially pretend that we have already gotten all items we're going // Artificially pretend that we have already gotten all items we're going
// to get. // to get.
NoMoreItems(); NoMoreItems();
{ CancelDownloads();
base::AutoLock lock(mutex_);
// Artificially wake up any downloads pending with an error code.
if (file_download_event_) {
file_download_error_ = base::PLATFORM_FILE_ERROR_FAILED;
file_download_event_->Signal();
}
}
// Schedule the camera session to be closed and the interface deleted. // Schedule the camera session to be closed and the interface deleted.
camera_interface_->ResetDelegate();
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&DeviceListener::CloseCameraSessionAndDelete, base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
base::Unretained(camera_interface_.release()))); base::Unretained(camera_interface_.release())));
media_task_runner_->DeleteSoon(FROM_HERE, this); delete this;
}
void MTPDeviceDelegateImplMac::CancelDownloads() {
// Cancel any outstanding callbacks.
for (ReadFileTransactionMap::iterator iter = read_file_transactions_.begin();
iter != read_file_transactions_.end(); ++iter) {
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(iter->second.second, base::PLATFORM_FILE_ERROR_ABORT));
}
read_file_transactions_.clear();
for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
iter != read_dir_transactions_.end(); ++iter) {
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(iter->error_callback, base::PLATFORM_FILE_ERROR_ABORT));
}
read_dir_transactions_.clear();
// TODO(gbillock): ImageCapture currently offers no way to cancel
// in-progress downloads.
} }
// Called on the UI thread by the listener
void MTPDeviceDelegateImplMac::ItemAdded( void MTPDeviceDelegateImplMac::ItemAdded(
const std::string& name, const base::PlatformFileInfo& info) { const std::string& name, const base::PlatformFileInfo& info) {
base::AutoLock lock(mutex_);
// Make sure that if we're canceled and an enumerator is awake, that
// it will stay consistent. May need to revisit this if we need
// notifications of files added after we think we're done.
if (received_all_files_) if (received_all_files_)
return; return;
...@@ -224,94 +332,74 @@ void MTPDeviceDelegateImplMac::ItemAdded( ...@@ -224,94 +332,74 @@ void MTPDeviceDelegateImplMac::ItemAdded(
file_info_[item_filename.value()] = info; file_info_[item_filename.value()] = info;
file_paths_.push_back(item_filename); file_paths_.push_back(item_filename);
if (enumerator_) // TODO(gbillock): Should we send new files to
enumerator_->ItemsChanged(); // read_dir_transactions_ callbacks?
} }
// Called in the UI thread by delegate.
void MTPDeviceDelegateImplMac::NoMoreItems() { void MTPDeviceDelegateImplMac::NoMoreItems() {
base::AutoLock lock(mutex_);
received_all_files_ = true; received_all_files_ = true;
NotifyReadDir();
}
if (enumerator_) void MTPDeviceDelegateImplMac::NotifyReadDir() {
enumerator_->ItemsChanged(); // Note: this assumes the only directory read we get is for the root.
// When this class supports directory hierarchies, this will change.
for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
iter != read_dir_transactions_.end(); ++iter) {
fileapi::AsyncFileUtil::EntryList entry_list;
for (size_t i = 0; i < file_paths_.size(); ++i) {
base::PlatformFileInfo info = file_info_[file_paths_[i].value()];
base::FileUtilProxy::Entry entry;
entry.name = file_paths_[i].value();
entry.is_directory = info.is_directory;
entry.size = info.size;
entry.last_modified_time = info.last_modified;
entry_list.push_back(entry);
}
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
base::Bind(iter->success_callback, entry_list, false));
}
} }
// Invoked on UI thread from the listener.
void MTPDeviceDelegateImplMac::DownloadedFile( void MTPDeviceDelegateImplMac::DownloadedFile(
const std::string& name, base::PlatformFileError error) { const std::string& name, base::PlatformFileError error) {
// If we're cancelled and deleting, we have already signaled all enumerators. // If we're cancelled and deleting, we may have deleted the camera.
if (!camera_interface_.get()) if (!camera_interface_.get())
return; return;
base::AutoLock lock(mutex_); ReadFileTransactionMap::iterator iter = read_file_transactions_.find(name);
file_download_error_ = error; if (iter == read_file_transactions_.end())
file_download_event_->Signal(); return;
}
base::FilePath MTPDeviceDelegateImplMac::GetFile(size_t index) {
base::AutoLock lock(mutex_);
if (index >= file_paths_.size())
return base::FilePath();
else
return file_paths_[index];
}
bool MTPDeviceDelegateImplMac::ReceivedAllFiles() {
base::AutoLock lock(mutex_);
return received_all_files_;
}
void MTPDeviceDelegateImplMac::RemoveEnumerator(Enumerator* enumerator) {
base::AutoLock lock(mutex_);
DCHECK(enumerator_ == enumerator);
enumerator_ = NULL;
}
MTPDeviceDelegateImplMac::Enumerator::Enumerator(
MTPDeviceDelegateImplMac* delegate)
: delegate_(delegate),
position_(0),
wait_for_items_(false, false) {}
MTPDeviceDelegateImplMac::Enumerator::~Enumerator() {
delegate_->RemoveEnumerator(this);
}
base::FilePath MTPDeviceDelegateImplMac::Enumerator::Next() { if (error != base::PLATFORM_FILE_OK) {
base::FilePath next_file = delegate_->GetFile(position_); content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
while (next_file.empty() && !delegate_->ReceivedAllFiles()) { base::Bind(iter->second.second, error));
wait_for_items_.Wait(); read_file_transactions_.erase(iter);
next_file = delegate_->GetFile(position_); return;
} }
position_++; base::PlatformFileInfo info = file_info_[name];
return next_file; content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
} base::Bind(iter->second.first, info, base::FilePath(name)));
read_file_transactions_.erase(iter);
int64 MTPDeviceDelegateImplMac::Enumerator::Size() {
base::PlatformFileInfo info;
delegate_->GetFileInfo(delegate_->GetFile(position_ - 1), &info);
return info.size;
}
base::Time MTPDeviceDelegateImplMac::Enumerator::LastModifiedTime() {
base::PlatformFileInfo info;
delegate_->GetFileInfo(delegate_->GetFile(position_ - 1), &info);
return info.last_modified;
} }
bool MTPDeviceDelegateImplMac::Enumerator::IsDirectory() { MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
base::PlatformFileInfo info; const base::FilePath& dir,
delegate_->GetFileInfo(delegate_->GetFile(position_ - 1), &info); ReadDirectorySuccessCallback success_cb,
return info.is_directory; ErrorCallback error_cb)
} : directory(dir),
success_callback(success_cb),
error_callback(error_cb) {}
void MTPDeviceDelegateImplMac::Enumerator::ItemsChanged() { MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
wait_for_items_.Signal();
}
void CreateMTPDeviceDelegate(const std::string& device_location, void CreateMTPDeviceAsyncDelegate(
base::SequencedTaskRunner* media_task_runner, const base::FilePath::StringType& device_location,
const CreateMTPDeviceDelegateCallback& cb) { const CreateMTPDeviceAsyncDelegateCallback& cb) {
std::string device_name = base::FilePath(device_location).BaseName().value(); std::string device_name = base::FilePath(device_location).BaseName().value();
std::string device_id; std::string device_id;
MediaStorageUtil::Type type; MediaStorageUtil::Type type;
...@@ -320,8 +408,7 @@ void CreateMTPDeviceDelegate(const std::string& device_location, ...@@ -320,8 +408,7 @@ void CreateMTPDeviceDelegate(const std::string& device_location,
DCHECK(cracked); DCHECK(cracked);
DCHECK_EQ(MediaStorageUtil::MAC_IMAGE_CAPTURE, type); DCHECK_EQ(MediaStorageUtil::MAC_IMAGE_CAPTURE, type);
cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location, cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));
media_task_runner));
} }
} // namespace chrome } // namespace chrome
......
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
#import <ImageCaptureCore/ImageCaptureCore.h> #import <ImageCaptureCore/ImageCaptureCore.h>
#include "base/file_util.h" #include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/mac/cocoa_protocols.h" #include "base/mac/cocoa_protocols.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/memory/scoped_nsobject.h" #include "base/memory/scoped_nsobject.h"
#include "base/message_loop.h" #include "base/message_loop.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/test/sequenced_worker_pool_owner.h" #include "base/test/sequenced_worker_pool_owner.h"
...@@ -38,6 +40,7 @@ ...@@ -38,6 +40,7 @@
namespace { namespace {
const char kDeviceId[] = "id"; const char kDeviceId[] = "id";
const char kDevicePath[] = "/ic:id";
const char kTestFileContents[] = "test"; const char kTestFileContents[] = "test";
} // namespace } // namespace
...@@ -175,6 +178,11 @@ class MTPDeviceDelegateImplMacTest : public testing::Test { ...@@ -175,6 +178,11 @@ class MTPDeviceDelegateImplMacTest : public testing::Test {
virtual void SetUp() OVERRIDE { virtual void SetUp() OVERRIDE {
ui_thread_.reset(new content::TestBrowserThread( ui_thread_.reset(new content::TestBrowserThread(
content::BrowserThread::UI, &message_loop_)); content::BrowserThread::UI, &message_loop_));
file_thread_.reset(new content::TestBrowserThread(
content::BrowserThread::FILE, &message_loop_));
io_thread_.reset(new content::TestBrowserThread(
content::BrowserThread::IO));
ASSERT_TRUE(io_thread_->Start());
manager_.SetNotifications(monitor_.receiver()); manager_.SetNotifications(monitor_.receiver());
...@@ -182,34 +190,134 @@ class MTPDeviceDelegateImplMacTest : public testing::Test { ...@@ -182,34 +190,134 @@ class MTPDeviceDelegateImplMacTest : public testing::Test {
id<ICDeviceBrowserDelegate> delegate = manager_.device_browser(); id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
[delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO]; [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); delegate_ = new chrome::MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
task_runner_ = pool->GetSequencedTaskRunner( }
pool->GetNamedSequenceToken("token-name"));
delegate_ = new chrome::MTPDeviceDelegateImplMac( void OnError(base::WaitableEvent* event, base::PlatformFileError error) {
"id", "/ic:id", task_runner_.get()); error_ = error;
event->Signal();
}
void OverlappedOnError(base::WaitableEvent* event,
base::PlatformFileError error) {
overlapped_error_ = error;
event->Signal();
}
void OnFileInfo(base::WaitableEvent* event,
const base::PlatformFileInfo& info) {
error_ = base::PLATFORM_FILE_OK;
info_ = info;
event->Signal();
}
void OnReadDir(base::WaitableEvent* event,
const fileapi::AsyncFileUtil::EntryList& files,
bool has_more) {
error_ = base::PLATFORM_FILE_OK;
ASSERT_FALSE(has_more);
file_list_ = files;
event->Signal();
}
void OverlappedOnReadDir(base::WaitableEvent* event,
const fileapi::AsyncFileUtil::EntryList& files,
bool has_more) {
overlapped_error_ = base::PLATFORM_FILE_OK;
ASSERT_FALSE(has_more);
overlapped_file_list_ = files;
event->Signal();
}
void OnDownload(base::WaitableEvent* event,
const base::PlatformFileInfo& file_info,
const base::FilePath& local_path) {
error_ = base::PLATFORM_FILE_OK;
event->Signal();
}
base::PlatformFileError GetFileInfo(const base::FilePath& path,
base::PlatformFileInfo* info) {
base::WaitableEvent wait(true, false);
delegate_->GetFileInfo(
path,
base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
base::Unretained(this),
&wait),
base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
base::Unretained(this),
&wait));
base::RunLoop loop;
loop.RunUntilIdle();
EXPECT_TRUE(wait.IsSignaled());
*info = info_;
return error_;
}
base::PlatformFileError ReadDir(const base::FilePath& path) {
base::WaitableEvent wait(true, false);
delegate_->ReadDirectory(
path,
base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
base::Unretained(this),
&wait),
base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
base::Unretained(this),
&wait));
base::RunLoop loop;
loop.RunUntilIdle();
wait.Wait();
return error_;
}
base::PlatformFileError DownloadFile(
const base::FilePath& path,
const base::FilePath& local_path) {
base::WaitableEvent wait(true, false);
delegate_->CreateSnapshotFile(
path, local_path,
base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
base::Unretained(this),
&wait),
base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
base::Unretained(this),
&wait));
base::RunLoop loop;
loop.RunUntilIdle();
wait.Wait();
return error_;
} }
virtual void TearDown() OVERRIDE { virtual void TearDown() OVERRIDE {
id<ICDeviceBrowserDelegate> delegate = manager_.device_browser(); id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
[delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO]; [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
task_runner_->PostTask(FROM_HERE, delegate_->CancelPendingTasksAndDeleteDelegate();
base::Bind(&chrome::MTPDeviceDelegateImplMac::
CancelPendingTasksAndDeleteDelegate, io_thread_->Stop();
base::Unretained(delegate_)));
} }
protected: protected:
MessageLoopForUI message_loop_; MessageLoopForUI message_loop_;
// Note: threads must be made in this order: UI > FILE > IO
scoped_ptr<content::TestBrowserThread> ui_thread_; scoped_ptr<content::TestBrowserThread> ui_thread_;
scoped_ptr<content::TestBrowserThread> file_thread_;
scoped_ptr<content::TestBrowserThread> io_thread_;
base::ScopedTempDir temp_dir_;
chrome::test::TestStorageMonitor monitor_; chrome::test::TestStorageMonitor monitor_;
chrome::ImageCaptureDeviceManager manager_; chrome::ImageCaptureDeviceManager manager_;
ICCameraDevice* camera_; MockMTPICCameraDevice* camera_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
// This object needs special deletion inside the above |task_runner_|. // This object needs special deletion inside the above |task_runner_|.
chrome::MTPDeviceDelegateImplMac* delegate_; chrome::MTPDeviceDelegateImplMac* delegate_;
base::PlatformFileError error_;
base::PlatformFileInfo info_;
fileapi::AsyncFileUtil::EntryList file_list_;
base::PlatformFileError overlapped_error_;
fileapi::AsyncFileUtil::EntryList overlapped_file_list_;
private: private:
DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest); DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
}; };
...@@ -219,17 +327,61 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) { ...@@ -219,17 +327,61 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) {
// Making a fresh delegate should have a single file entry for the synthetic // Making a fresh delegate should have a single file entry for the synthetic
// root directory, with the name equal to the device id string. // root directory, with the name equal to the device id string.
EXPECT_EQ(base::PLATFORM_FILE_OK, EXPECT_EQ(base::PLATFORM_FILE_OK,
delegate_->GetFileInfo(base::FilePath("/ic:id"), &info)); GetFileInfo(base::FilePath(kDevicePath), &info));
EXPECT_TRUE(info.is_directory); EXPECT_TRUE(info.is_directory);
EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
delegate_->GetFileInfo(base::FilePath("/nonexistent"), &info)); GetFileInfo(base::FilePath("/nonexistent"), &info));
// Signal the delegate that no files are coming. // Signal the delegate that no files are coming.
delegate_->NoMoreItems(); delegate_->NoMoreItems();
scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> enumerator = EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
delegate_->CreateFileEnumerator(base::FilePath("/ic:id"), true); EXPECT_EQ(0U, file_list_.size());
EXPECT_TRUE(enumerator->Next().empty()); }
TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
base::Time time1 = base::Time::Now();
base::PlatformFileInfo info1;
info1.size = 1;
info1.is_directory = false;
info1.is_symbolic_link = false;
info1.last_modified = time1;
info1.last_accessed = time1;
info1.creation_time = time1;
delegate_->ItemAdded("name1", info1);
base::WaitableEvent wait(true, false);
delegate_->ReadDirectory(
base::FilePath(kDevicePath),
base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
base::Unretained(this),
&wait),
base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
base::Unretained(this),
&wait));
delegate_->ReadDirectory(
base::FilePath(kDevicePath),
base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
base::Unretained(this),
&wait),
base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
base::Unretained(this),
&wait));
// Signal the delegate that no files are coming.
delegate_->NoMoreItems();
base::RunLoop loop;
loop.RunUntilIdle();
wait.Wait();
EXPECT_EQ(base::PLATFORM_FILE_OK, error_);
EXPECT_EQ(1U, file_list_.size());
EXPECT_EQ(base::PLATFORM_FILE_OK, overlapped_error_);
EXPECT_EQ(1U, overlapped_file_list_.size());
} }
TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) { TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
...@@ -245,7 +397,7 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) { ...@@ -245,7 +397,7 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
base::PlatformFileInfo info; base::PlatformFileInfo info;
EXPECT_EQ(base::PLATFORM_FILE_OK, EXPECT_EQ(base::PLATFORM_FILE_OK,
delegate_->GetFileInfo(base::FilePath("/ic:id/name1"), &info)); GetFileInfo(base::FilePath("/ic:id/name1"), &info));
EXPECT_EQ(info1.size, info.size); EXPECT_EQ(info1.size, info.size);
EXPECT_EQ(info1.is_directory, info.is_directory); EXPECT_EQ(info1.is_directory, info.is_directory);
EXPECT_EQ(info1.last_modified, info.last_modified); EXPECT_EQ(info1.last_modified, info.last_modified);
...@@ -257,27 +409,19 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) { ...@@ -257,27 +409,19 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
delegate_->NoMoreItems(); delegate_->NoMoreItems();
EXPECT_EQ(base::PLATFORM_FILE_OK, EXPECT_EQ(base::PLATFORM_FILE_OK,
delegate_->GetFileInfo(base::FilePath("/ic:id/name2"), &info)); GetFileInfo(base::FilePath("/ic:id/name2"), &info));
EXPECT_EQ(info1.size, info.size); EXPECT_EQ(info1.size, info.size);
scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> enumerator = EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
delegate_->CreateFileEnumerator(base::FilePath("/ic:id"), true);
base::FilePath next = enumerator->Next(); ASSERT_EQ(2U, file_list_.size());
ASSERT_FALSE(next.empty()); EXPECT_EQ(time1, file_list_[0].last_modified_time);
EXPECT_EQ(1, enumerator->Size()); EXPECT_FALSE(file_list_[0].is_directory);
EXPECT_EQ(time1, enumerator->LastModifiedTime()); EXPECT_EQ("/ic:id/name1", file_list_[0].name);
EXPECT_FALSE(enumerator->IsDirectory());
EXPECT_EQ("/ic:id/name1", next.value()); EXPECT_EQ(time1, file_list_[1].last_modified_time);
EXPECT_FALSE(file_list_[1].is_directory);
next = enumerator->Next(); EXPECT_EQ("/ic:id/name2", file_list_[1].name);
ASSERT_FALSE(next.empty());
EXPECT_EQ(2, enumerator->Size());
EXPECT_EQ(time1, enumerator->LastModifiedTime());
EXPECT_FALSE(enumerator->IsDirectory());
EXPECT_EQ("/ic:id/name2", next.value());
next = enumerator->Next();
EXPECT_TRUE(next.empty());
} }
TEST_F(MTPDeviceDelegateImplMacTest, TestIgnoreDirectories) { TEST_F(MTPDeviceDelegateImplMacTest, TestIgnoreDirectories) {
...@@ -299,54 +443,49 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestIgnoreDirectories) { ...@@ -299,54 +443,49 @@ TEST_F(MTPDeviceDelegateImplMacTest, TestIgnoreDirectories) {
delegate_->ItemAdded("name2", info1); delegate_->ItemAdded("name2", info1);
delegate_->NoMoreItems(); delegate_->NoMoreItems();
scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> enumerator = EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
delegate_->CreateFileEnumerator(base::FilePath("/ic:id"), true);
base::FilePath next = enumerator->Next();
ASSERT_FALSE(next.empty());
EXPECT_EQ("/ic:id/name1", next.value());
next = enumerator->Next(); ASSERT_EQ(2U, file_list_.size());
ASSERT_FALSE(next.empty()); EXPECT_EQ(time1, file_list_[0].last_modified_time);
EXPECT_EQ("/ic:id/name2", next.value()); EXPECT_FALSE(file_list_[0].is_directory);
EXPECT_EQ("/ic:id/name1", file_list_[0].name);
next = enumerator->Next(); EXPECT_EQ(time1, file_list_[1].last_modified_time);
EXPECT_TRUE(next.empty()); EXPECT_FALSE(file_list_[1].is_directory);
EXPECT_EQ("/ic:id/name2", file_list_[1].name);
} }
TEST_F(MTPDeviceDelegateImplMacTest, EnumeratorWaitsForEntries) { TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
base::Time time1 = base::Time::Now(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base::PlatformFileInfo info1; base::Time t1 = base::Time::Now();
info1.size = 1; base::PlatformFileInfo info;
info1.is_directory = false; info.size = 4;
info1.is_symbolic_link = false; info.is_directory = false;
info1.last_modified = time1; info.is_symbolic_link = false;
info1.last_accessed = time1; info.last_modified = t1;
info1.creation_time = time1; info.last_accessed = t1;
delegate_->ItemAdded("name1", info1); info.creation_time = t1;
std::string kTestFileName("filename");
scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> enumerator = scoped_nsobject<MockMTPICCameraFile> picture1(
delegate_->CreateFileEnumerator(base::FilePath("/ic:id"), true); [[MockMTPICCameraFile alloc]
// Event is manually reset, initially unsignaled init:base::SysUTF8ToNSString(kTestFileName)]);
base::WaitableEvent event(true, false); [camera_ addMediaFile:picture1];
base::FilePath next; delegate_->ItemAdded(kTestFileName, info);
task_runner_->PostTask(FROM_HERE,
base::Bind(&EnumerateAndSignal,
enumerator.get(), &event, &next));
message_loop_.RunUntilIdle();
ASSERT_TRUE(event.IsSignaled());
EXPECT_EQ("/ic:id/name1", next.value());
event.Reset();
// This method will block until it is sure there are no more items.
task_runner_->PostTask(FROM_HERE,
base::Bind(&EnumerateAndSignal,
enumerator.get(), &event, &next));
message_loop_.RunUntilIdle();
ASSERT_FALSE(event.IsSignaled());
delegate_->NoMoreItems(); delegate_->NoMoreItems();
event.Wait();
ASSERT_TRUE(event.IsSignaled()); EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
EXPECT_TRUE(next.empty()); ASSERT_EQ(1U, file_list_.size());
message_loop_.RunUntilIdle(); ASSERT_EQ("/ic:id/filename", file_list_[0].name);
EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
DownloadFile(base::FilePath("/ic:id/nonexist"),
temp_dir_.path().Append("target")));
EXPECT_EQ(base::PLATFORM_FILE_OK,
DownloadFile(base::FilePath("/ic:id/filename"),
temp_dir_.path().Append("target")));
std::string contents;
EXPECT_TRUE(file_util::ReadFileToString(temp_dir_.path().Append("target"),
&contents));
EXPECT_EQ(kTestFileContents, contents);
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// Use asynchronous MTP device delegate API. // Use asynchronous MTP device delegate API.
// TODO(kmadhusu): remove this define and make this default. // TODO(kmadhusu): remove this define and make this default.
// Note that OS_LINUX implies OS_CHROMEOS // Note that OS_LINUX implies OS_CHROMEOS
#if defined(OS_WIN) || defined(OS_LINUX) #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
#define USE_MTP_DEVICE_ASYNC_DELEGATE #define USE_MTP_DEVICE_ASYNC_DELEGATE
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment