1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "chrome/browser/storage_monitor/image_capture_device.h"
12 #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "webkit/browser/fileapi/async_file_util.h"
18 int kReadDirectoryTimeLimitSeconds = 20;
20 typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
21 CreateSnapshotFileSuccessCallback;
22 typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback;
23 typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback
24 GetFileInfoSuccessCallback;
25 typedef MTPDeviceAsyncDelegate::ReadDirectorySuccessCallback
26 ReadDirectorySuccessCallback;
30 // This class handles the UI-thread hand-offs needed to interface
31 // with the ImageCapture library. It will forward callbacks to
32 // its delegate on the task runner with which it is created. All
33 // interactions with it are done on the UI thread, but it may be
34 // created/destroyed on another thread.
35 class MTPDeviceDelegateImplMac::DeviceListener
36 : public ImageCaptureDeviceListener,
37 public base::SupportsWeakPtr<DeviceListener> {
39 DeviceListener(MTPDeviceDelegateImplMac* delegate)
40 : delegate_(delegate) {}
41 virtual ~DeviceListener() {}
43 void OpenCameraSession(const std::string& device_id);
44 void CloseCameraSessionAndDelete();
46 void DownloadFile(const std::string& name, const base::FilePath& local_path);
48 // ImageCaptureDeviceListener
49 virtual void ItemAdded(const std::string& name,
50 const base::PlatformFileInfo& info) OVERRIDE;
51 virtual void NoMoreItems() OVERRIDE;
52 virtual void DownloadedFile(const std::string& name,
53 base::PlatformFileError error) OVERRIDE;
54 virtual void DeviceRemoved() OVERRIDE;
56 // Used during delegate destruction to ensure there are no more calls
57 // to the delegate by the listener.
58 virtual void ResetDelegate();
61 base::scoped_nsobject<ImageCaptureDevice> camera_device_;
64 MTPDeviceDelegateImplMac* delegate_;
66 DISALLOW_COPY_AND_ASSIGN(DeviceListener);
69 void MTPDeviceDelegateImplMac::DeviceListener::OpenCameraSession(
70 const std::string& device_id) {
72 [ImageCaptureDeviceManager::deviceForUUID(device_id) retain]);
73 [camera_device_ setListener:AsWeakPtr()];
74 [camera_device_ open];
77 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
78 [camera_device_ close];
79 [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
84 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
85 const std::string& name,
86 const base::FilePath& local_path) {
87 [camera_device_ downloadFile:name localPath:local_path];
90 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
91 const std::string& name,
92 const base::PlatformFileInfo& info) {
94 delegate_->ItemAdded(name, info);
97 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
99 delegate_->NoMoreItems();
102 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
103 const std::string& name,
104 base::PlatformFileError error) {
106 delegate_->DownloadedFile(name, error);
109 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
110 [camera_device_ close];
111 camera_device_.reset();
113 delegate_->NoMoreItems();
116 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
120 MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac(
121 const std::string& device_id,
122 const base::FilePath::StringType& synthetic_path)
123 : device_id_(device_id),
124 root_path_(synthetic_path),
125 received_all_files_(false),
126 weak_factory_(this) {
128 // Make a synthetic entry for the root of the filesystem.
129 base::PlatformFileInfo info;
130 info.is_directory = true;
131 file_paths_.push_back(root_path_);
132 file_info_[root_path_.value()] = info;
134 camera_interface_.reset(new DeviceListener(this));
135 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
136 base::Bind(&DeviceListener::OpenCameraSession,
137 base::Unretained(camera_interface_.get()),
141 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
146 void ForwardGetFileInfo(
147 base::PlatformFileInfo* info,
148 base::PlatformFileError* error,
149 const GetFileInfoSuccessCallback& success_callback,
150 const ErrorCallback& error_callback) {
151 if (*error == base::PLATFORM_FILE_OK)
152 success_callback.Run(*info);
154 error_callback.Run(*error);
159 void MTPDeviceDelegateImplMac::GetFileInfo(
160 const base::FilePath& file_path,
161 const GetFileInfoSuccessCallback& success_callback,
162 const ErrorCallback& error_callback) {
163 base::PlatformFileInfo* info = new base::PlatformFileInfo;
164 base::PlatformFileError* error = new base::PlatformFileError;
165 // Note: ownership of these objects passed into the reply callback.
166 content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI,
168 base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl,
169 base::Unretained(this), file_path, info, error),
170 base::Bind(&ForwardGetFileInfo,
171 base::Owned(info), base::Owned(error),
172 success_callback, error_callback));
175 void MTPDeviceDelegateImplMac::ReadDirectory(
176 const base::FilePath& root,
177 const ReadDirectorySuccessCallback& success_callback,
178 const ErrorCallback& error_callback) {
179 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
180 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
181 base::Unretained(this),
182 root, success_callback, error_callback));
185 void MTPDeviceDelegateImplMac::CreateSnapshotFile(
186 const base::FilePath& device_file_path,
187 const base::FilePath& local_path,
188 const CreateSnapshotFileSuccessCallback& success_callback,
189 const ErrorCallback& error_callback) {
190 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
191 base::Bind(&MTPDeviceDelegateImplMac::DownloadFile,
192 base::Unretained(this),
193 device_file_path, local_path,
194 success_callback, error_callback));
197 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
198 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
199 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
200 base::Unretained(this)));
203 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
204 const base::FilePath& file_path,
205 base::PlatformFileInfo* file_info,
206 base::PlatformFileError* error) {
207 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
208 base::hash_map<base::FilePath::StringType,
209 base::PlatformFileInfo>::const_iterator i =
210 file_info_.find(file_path.value());
211 if (i == file_info_.end()) {
212 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
215 *file_info = i->second;
216 *error = base::PLATFORM_FILE_OK;
219 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
220 const base::FilePath& root,
221 const ReadDirectorySuccessCallback& success_callback,
222 const ErrorCallback& error_callback) {
223 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
225 read_dir_transactions_.push_back(ReadDirectoryRequest(
226 root, success_callback, error_callback));
228 if (received_all_files_) {
233 // Schedule a timeout in case the directory read doesn't complete.
234 content::BrowserThread::PostDelayedTask(
235 content::BrowserThread::UI, FROM_HERE,
236 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
237 weak_factory_.GetWeakPtr(), root),
238 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
241 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
242 const base::FilePath& root) {
243 if (received_all_files_)
246 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
247 iter != read_dir_transactions_.end();) {
248 if (iter->directory != root) {
252 iter->error_callback.Run(base::PLATFORM_FILE_ERROR_ABORT);
253 iter = read_dir_transactions_.erase(iter);
257 void MTPDeviceDelegateImplMac::DownloadFile(
258 const base::FilePath& device_file_path,
259 const base::FilePath& local_path,
260 const CreateSnapshotFileSuccessCallback& success_callback,
261 const ErrorCallback& error_callback) {
262 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
264 base::PlatformFileError error;
265 base::PlatformFileInfo info;
266 GetFileInfoImpl(device_file_path, &info, &error);
267 if (error != base::PLATFORM_FILE_OK) {
268 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
269 base::Bind(error_callback,
274 base::FilePath relative_path;
275 root_path_.AppendRelativePath(device_file_path, &relative_path);
277 read_file_transactions_.push_back(
278 ReadFileRequest(relative_path.value(), local_path,
279 success_callback, error_callback));
281 camera_interface_->DownloadFile(relative_path.value(), local_path);
284 void MTPDeviceDelegateImplMac::CancelAndDelete() {
285 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
286 // Artificially pretend that we have already gotten all items we're going
292 // Schedule the camera session to be closed and the interface deleted.
293 // This will cancel any downloads in progress.
294 camera_interface_->ResetDelegate();
295 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
296 base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
297 base::Unretained(camera_interface_.release())));
302 void MTPDeviceDelegateImplMac::CancelDownloads() {
303 // Cancel any outstanding callbacks.
304 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
305 iter != read_file_transactions_.end(); ++iter) {
306 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
307 base::Bind(iter->error_callback,
308 base::PLATFORM_FILE_ERROR_ABORT));
310 read_file_transactions_.clear();
312 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
313 iter != read_dir_transactions_.end(); ++iter) {
314 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
315 base::Bind(iter->error_callback, base::PLATFORM_FILE_ERROR_ABORT));
317 read_dir_transactions_.clear();
320 // Called on the UI thread by the listener
321 void MTPDeviceDelegateImplMac::ItemAdded(
322 const std::string& name, const base::PlatformFileInfo& info) {
323 if (received_all_files_)
326 // This kinda should go in a Join method in FilePath...
327 base::FilePath relative_path(name);
328 std::vector<base::FilePath::StringType> components;
329 relative_path.GetComponents(&components);
330 base::FilePath item_filename = root_path_;
331 for (std::vector<base::FilePath::StringType>::iterator iter =
333 iter != components.end(); ++iter) {
334 item_filename = item_filename.Append(*iter);
337 file_info_[item_filename.value()] = info;
338 file_paths_.push_back(item_filename);
340 // TODO(gbillock): Should we send new files to
341 // read_dir_transactions_ callbacks?
344 // Called in the UI thread by delegate.
345 void MTPDeviceDelegateImplMac::NoMoreItems() {
346 received_all_files_ = true;
347 std::sort(file_paths_.begin(), file_paths_.end());
352 void MTPDeviceDelegateImplMac::NotifyReadDir() {
353 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
354 iter != read_dir_transactions_.end(); ++iter) {
355 base::FilePath read_path = iter->directory;
356 // This code assumes that the list of paths is sorted, so we skip to
357 // where we find the entry for the directory, then read out all first-level
358 // children. We then break when the DirName is greater than the read_path,
359 // as that means we've passed the subdir we're reading.
360 fileapi::AsyncFileUtil::EntryList entry_list;
361 bool found_path = false;
362 for (size_t i = 0; i < file_paths_.size(); ++i) {
363 if (file_paths_[i] == read_path) {
367 if (!read_path.IsParent(file_paths_[i])) {
368 if (read_path < file_paths_[i].DirName())
372 if (file_paths_[i].DirName() != read_path)
375 base::FilePath relative_path;
376 read_path.AppendRelativePath(file_paths_[i], &relative_path);
377 base::PlatformFileInfo info = file_info_[file_paths_[i].value()];
378 fileapi::DirectoryEntry entry;
379 entry.name = relative_path.value();
380 entry.is_directory = info.is_directory;
381 entry.size = info.size;
382 entry.last_modified_time = info.last_modified;
383 entry_list.push_back(entry);
387 content::BrowserThread::PostTask(content::BrowserThread::IO,
389 base::Bind(iter->success_callback, entry_list, false));
391 content::BrowserThread::PostTask(content::BrowserThread::IO,
393 base::Bind(iter->error_callback,
394 base::PLATFORM_FILE_ERROR_NOT_FOUND));
398 read_dir_transactions_.clear();
401 // Invoked on UI thread from the listener.
402 void MTPDeviceDelegateImplMac::DownloadedFile(
403 const std::string& name, base::PlatformFileError error) {
404 // If we're cancelled and deleting, we may have deleted the camera.
405 if (!camera_interface_.get())
409 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
410 for (; iter != read_file_transactions_.end(); ++iter) {
411 if (iter->request_file == name) {
419 if (error != base::PLATFORM_FILE_OK) {
420 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
421 base::Bind(iter->error_callback, error));
422 read_file_transactions_.erase(iter);
426 base::FilePath relative_path(name);
427 std::vector<base::FilePath::StringType> components;
428 relative_path.GetComponents(&components);
429 base::FilePath item_filename = root_path_;
430 for (std::vector<base::FilePath::StringType>::iterator i =
432 i != components.end(); ++i) {
433 item_filename = item_filename.Append(*i);
436 base::PlatformFileInfo info = file_info_[item_filename.value()];
437 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
438 base::Bind(iter->success_callback, info, iter->snapshot_file));
439 read_file_transactions_.erase(iter);
442 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
443 const std::string& file,
444 const base::FilePath& snapshot_filename,
445 CreateSnapshotFileSuccessCallback success_cb,
446 ErrorCallback error_cb)
447 : request_file(file),
448 snapshot_file(snapshot_filename),
449 success_callback(success_cb),
450 error_callback(error_cb) {}
452 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
454 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
456 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
457 const base::FilePath& dir,
458 ReadDirectorySuccessCallback success_cb,
459 ErrorCallback error_cb)
461 success_callback(success_cb),
462 error_callback(error_cb) {}
464 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
466 void CreateMTPDeviceAsyncDelegate(
467 const base::FilePath::StringType& device_location,
468 const CreateMTPDeviceAsyncDelegateCallback& cb) {
469 std::string device_name = base::FilePath(device_location).BaseName().value();
470 std::string device_id;
471 StorageInfo::Type type;
472 bool cracked = StorageInfo::CrackDeviceId(device_name, &type, &device_id);
474 DCHECK_EQ(StorageInfo::MAC_IMAGE_CAPTURE, type);
476 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));