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 "components/storage_monitor/image_capture_device.h"
12 #include "components/storage_monitor/image_capture_device_manager.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "storage/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 storage_monitor::ImageCaptureDeviceListener,
37 public base::SupportsWeakPtr<DeviceListener> {
39 DeviceListener(MTPDeviceDelegateImplMac* delegate)
40 : delegate_(delegate) {}
41 ~DeviceListener() override {}
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 void ItemAdded(const std::string& name,
50 const base::File::Info& info) override;
51 void NoMoreItems() override;
52 void DownloadedFile(const std::string& name,
53 base::File::Error error) override;
54 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 [storage_monitor::ImageCaptureDeviceManager::deviceForUUID(device_id)
74 [camera_device_ setListener:AsWeakPtr()];
75 [camera_device_ open];
78 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
79 [camera_device_ close];
80 [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
85 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
86 const std::string& name,
87 const base::FilePath& local_path) {
88 [camera_device_ downloadFile:name localPath:local_path];
91 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
92 const std::string& name,
93 const base::File::Info& info) {
95 delegate_->ItemAdded(name, info);
98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
100 delegate_->NoMoreItems();
103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
104 const std::string& name,
105 base::File::Error error) {
107 delegate_->DownloadedFile(name, error);
110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
111 [camera_device_ close];
112 camera_device_.reset();
114 delegate_->NoMoreItems();
117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
121 MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac(
122 const std::string& device_id,
123 const base::FilePath::StringType& synthetic_path)
124 : device_id_(device_id),
125 root_path_(synthetic_path),
126 received_all_files_(false),
127 weak_factory_(this) {
129 // Make a synthetic entry for the root of the filesystem.
130 base::File::Info info;
131 info.is_directory = true;
132 file_paths_.push_back(root_path_);
133 file_info_[root_path_.value()] = info;
135 camera_interface_.reset(new DeviceListener(this));
136 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
137 base::Bind(&DeviceListener::OpenCameraSession,
138 base::Unretained(camera_interface_.get()),
142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
147 void ForwardGetFileInfo(
148 base::File::Info* info,
149 base::File::Error* error,
150 const GetFileInfoSuccessCallback& success_callback,
151 const ErrorCallback& error_callback) {
152 if (*error == base::File::FILE_OK)
153 success_callback.Run(*info);
155 error_callback.Run(*error);
160 void MTPDeviceDelegateImplMac::GetFileInfo(
161 const base::FilePath& file_path,
162 const GetFileInfoSuccessCallback& success_callback,
163 const ErrorCallback& error_callback) {
164 base::File::Info* info = new base::File::Info;
165 base::File::Error* error = new base::File::Error;
166 // Note: ownership of these objects passed into the reply callback.
167 content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI,
169 base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl,
170 base::Unretained(this), file_path, info, error),
171 base::Bind(&ForwardGetFileInfo,
172 base::Owned(info), base::Owned(error),
173 success_callback, error_callback));
176 void MTPDeviceDelegateImplMac::CreateDirectory(
177 const base::FilePath& directory_path,
178 const bool exclusive,
179 const bool recursive,
180 const CreateDirectorySuccessCallback& success_callback,
181 const ErrorCallback& error_callback) {
185 void MTPDeviceDelegateImplMac::ReadDirectory(
186 const base::FilePath& root,
187 const ReadDirectorySuccessCallback& success_callback,
188 const ErrorCallback& error_callback) {
189 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
190 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
191 base::Unretained(this),
192 root, success_callback, error_callback));
195 void MTPDeviceDelegateImplMac::CreateSnapshotFile(
196 const base::FilePath& device_file_path,
197 const base::FilePath& local_path,
198 const CreateSnapshotFileSuccessCallback& success_callback,
199 const ErrorCallback& error_callback) {
200 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
201 base::Bind(&MTPDeviceDelegateImplMac::DownloadFile,
202 base::Unretained(this),
203 device_file_path, local_path,
204 success_callback, error_callback));
207 bool MTPDeviceDelegateImplMac::IsStreaming() {
211 void MTPDeviceDelegateImplMac::ReadBytes(
212 const base::FilePath& device_file_path,
213 const scoped_refptr<net::IOBuffer>& buf,
216 const ReadBytesSuccessCallback& success_callback,
217 const ErrorCallback& error_callback) {
221 bool MTPDeviceDelegateImplMac::IsReadOnly() const {
225 void MTPDeviceDelegateImplMac::CopyFileLocal(
226 const base::FilePath& source_file_path,
227 const base::FilePath& device_file_path,
228 const CreateTemporaryFileCallback& create_temporary_file_callback,
229 const CopyFileProgressCallback& progress_callback,
230 const CopyFileLocalSuccessCallback& success_callback,
231 const ErrorCallback& error_callback) {
235 void MTPDeviceDelegateImplMac::MoveFileLocal(
236 const base::FilePath& source_file_path,
237 const base::FilePath& device_file_path,
238 const CreateTemporaryFileCallback& create_temporary_file_callback,
239 const MoveFileLocalSuccessCallback& success_callback,
240 const ErrorCallback& error_callback) {
244 void MTPDeviceDelegateImplMac::CopyFileFromLocal(
245 const base::FilePath& source_file_path,
246 const base::FilePath& device_file_path,
247 const CopyFileFromLocalSuccessCallback& success_callback,
248 const ErrorCallback& error_callback) {
252 void MTPDeviceDelegateImplMac::DeleteFile(
253 const base::FilePath& file_path,
254 const DeleteFileSuccessCallback& success_callback,
255 const ErrorCallback& error_callback) {
259 void MTPDeviceDelegateImplMac::DeleteDirectory(
260 const base::FilePath& file_path,
261 const DeleteDirectorySuccessCallback& success_callback,
262 const ErrorCallback& error_callback) {
266 void MTPDeviceDelegateImplMac::AddWatcher(
268 const base::FilePath& file_path,
269 const bool recursive,
270 const storage::WatcherManager::StatusCallback& callback,
271 const storage::WatcherManager::NotificationCallback&
272 notification_callback) {
274 callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
277 void MTPDeviceDelegateImplMac::RemoveWatcher(
279 const base::FilePath& file_path,
280 const bool recursive,
281 const storage::WatcherManager::StatusCallback& callback) {
283 callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
286 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
287 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
288 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
289 base::Unretained(this)));
292 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
293 const base::FilePath& file_path,
294 base::File::Info* file_info,
295 base::File::Error* error) {
296 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
297 base::hash_map<base::FilePath::StringType,
298 base::File::Info>::const_iterator i =
299 file_info_.find(file_path.value());
300 if (i == file_info_.end()) {
301 *error = base::File::FILE_ERROR_NOT_FOUND;
304 *file_info = i->second;
305 *error = base::File::FILE_OK;
308 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
309 const base::FilePath& root,
310 const ReadDirectorySuccessCallback& success_callback,
311 const ErrorCallback& error_callback) {
312 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
314 read_dir_transactions_.push_back(ReadDirectoryRequest(
315 root, success_callback, error_callback));
317 if (received_all_files_) {
322 // Schedule a timeout in case the directory read doesn't complete.
323 content::BrowserThread::PostDelayedTask(
324 content::BrowserThread::UI, FROM_HERE,
325 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
326 weak_factory_.GetWeakPtr(), root),
327 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
330 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
331 const base::FilePath& root) {
332 if (received_all_files_)
335 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
336 iter != read_dir_transactions_.end();) {
337 if (iter->directory != root) {
341 iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
342 iter = read_dir_transactions_.erase(iter);
346 void MTPDeviceDelegateImplMac::DownloadFile(
347 const base::FilePath& device_file_path,
348 const base::FilePath& local_path,
349 const CreateSnapshotFileSuccessCallback& success_callback,
350 const ErrorCallback& error_callback) {
351 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
353 base::File::Error error;
354 base::File::Info info;
355 GetFileInfoImpl(device_file_path, &info, &error);
356 if (error != base::File::FILE_OK) {
357 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
358 base::Bind(error_callback,
363 base::FilePath relative_path;
364 root_path_.AppendRelativePath(device_file_path, &relative_path);
366 read_file_transactions_.push_back(
367 ReadFileRequest(relative_path.value(), local_path,
368 success_callback, error_callback));
370 camera_interface_->DownloadFile(relative_path.value(), local_path);
373 void MTPDeviceDelegateImplMac::CancelAndDelete() {
374 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
375 // Artificially pretend that we have already gotten all items we're going
381 // Schedule the camera session to be closed and the interface deleted.
382 // This will cancel any downloads in progress.
383 camera_interface_->ResetDelegate();
384 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
385 base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
386 base::Unretained(camera_interface_.release())));
391 void MTPDeviceDelegateImplMac::CancelDownloads() {
392 // Cancel any outstanding callbacks.
393 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
394 iter != read_file_transactions_.end(); ++iter) {
395 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
396 base::Bind(iter->error_callback,
397 base::File::FILE_ERROR_ABORT));
399 read_file_transactions_.clear();
401 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
402 iter != read_dir_transactions_.end(); ++iter) {
403 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
404 base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT));
406 read_dir_transactions_.clear();
409 // Called on the UI thread by the listener
410 void MTPDeviceDelegateImplMac::ItemAdded(
411 const std::string& name, const base::File::Info& info) {
412 if (received_all_files_)
415 // This kinda should go in a Join method in FilePath...
416 base::FilePath relative_path(name);
417 std::vector<base::FilePath::StringType> components;
418 relative_path.GetComponents(&components);
419 base::FilePath item_filename = root_path_;
420 for (std::vector<base::FilePath::StringType>::iterator iter =
422 iter != components.end(); ++iter) {
423 item_filename = item_filename.Append(*iter);
426 file_info_[item_filename.value()] = info;
427 file_paths_.push_back(item_filename);
429 // TODO(gbillock): Should we send new files to
430 // read_dir_transactions_ callbacks?
433 // Called in the UI thread by delegate.
434 void MTPDeviceDelegateImplMac::NoMoreItems() {
435 received_all_files_ = true;
436 std::sort(file_paths_.begin(), file_paths_.end());
441 void MTPDeviceDelegateImplMac::NotifyReadDir() {
442 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
443 iter != read_dir_transactions_.end(); ++iter) {
444 base::FilePath read_path = iter->directory;
445 // This code assumes that the list of paths is sorted, so we skip to
446 // where we find the entry for the directory, then read out all first-level
447 // children. We then break when the DirName is greater than the read_path,
448 // as that means we've passed the subdir we're reading.
449 storage::AsyncFileUtil::EntryList entry_list;
450 bool found_path = false;
451 for (size_t i = 0; i < file_paths_.size(); ++i) {
452 if (file_paths_[i] == read_path) {
456 if (!read_path.IsParent(file_paths_[i])) {
457 if (read_path < file_paths_[i].DirName())
461 if (file_paths_[i].DirName() != read_path)
464 base::FilePath relative_path;
465 read_path.AppendRelativePath(file_paths_[i], &relative_path);
466 base::File::Info info = file_info_[file_paths_[i].value()];
467 storage::DirectoryEntry entry;
468 entry.name = relative_path.value();
469 entry.is_directory = info.is_directory;
470 entry.size = info.size;
471 entry.last_modified_time = info.last_modified;
472 entry_list.push_back(entry);
476 content::BrowserThread::PostTask(content::BrowserThread::IO,
478 base::Bind(iter->success_callback, entry_list, false));
480 content::BrowserThread::PostTask(content::BrowserThread::IO,
482 base::Bind(iter->error_callback,
483 base::File::FILE_ERROR_NOT_FOUND));
487 read_dir_transactions_.clear();
490 // Invoked on UI thread from the listener.
491 void MTPDeviceDelegateImplMac::DownloadedFile(
492 const std::string& name, base::File::Error error) {
493 // If we're cancelled and deleting, we may have deleted the camera.
494 if (!camera_interface_.get())
498 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
499 for (; iter != read_file_transactions_.end(); ++iter) {
500 if (iter->request_file == name) {
508 if (error != base::File::FILE_OK) {
509 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
510 base::Bind(iter->error_callback, error));
511 read_file_transactions_.erase(iter);
515 base::FilePath relative_path(name);
516 std::vector<base::FilePath::StringType> components;
517 relative_path.GetComponents(&components);
518 base::FilePath item_filename = root_path_;
519 for (std::vector<base::FilePath::StringType>::iterator i =
521 i != components.end(); ++i) {
522 item_filename = item_filename.Append(*i);
525 base::File::Info info = file_info_[item_filename.value()];
526 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
527 base::Bind(iter->success_callback, info, iter->snapshot_file));
528 read_file_transactions_.erase(iter);
531 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
532 const std::string& file,
533 const base::FilePath& snapshot_filename,
534 CreateSnapshotFileSuccessCallback success_cb,
535 ErrorCallback error_cb)
536 : request_file(file),
537 snapshot_file(snapshot_filename),
538 success_callback(success_cb),
539 error_callback(error_cb) {}
541 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
543 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
545 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
546 const base::FilePath& dir,
547 ReadDirectorySuccessCallback success_cb,
548 ErrorCallback error_cb)
550 success_callback(success_cb),
551 error_callback(error_cb) {}
553 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
555 void CreateMTPDeviceAsyncDelegate(
556 const base::FilePath::StringType& device_location,
557 const bool read_only,
558 const CreateMTPDeviceAsyncDelegateCallback& cb) {
559 // Write operation is not supported on Mac.
562 std::string device_name = base::FilePath(device_location).BaseName().value();
563 std::string device_id;
564 storage_monitor::StorageInfo::Type type;
565 bool cracked = storage_monitor::StorageInfo::CrackDeviceId(
566 device_name, &type, &device_id);
568 DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
570 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));