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::CancelPendingTasksAndDeleteDelegate() {
267 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
268 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
269 base::Unretained(this)));
272 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
273 const base::FilePath& file_path,
274 base::File::Info* file_info,
275 base::File::Error* error) {
276 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
277 base::hash_map<base::FilePath::StringType,
278 base::File::Info>::const_iterator i =
279 file_info_.find(file_path.value());
280 if (i == file_info_.end()) {
281 *error = base::File::FILE_ERROR_NOT_FOUND;
284 *file_info = i->second;
285 *error = base::File::FILE_OK;
288 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
289 const base::FilePath& root,
290 const ReadDirectorySuccessCallback& success_callback,
291 const ErrorCallback& error_callback) {
292 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
294 read_dir_transactions_.push_back(ReadDirectoryRequest(
295 root, success_callback, error_callback));
297 if (received_all_files_) {
302 // Schedule a timeout in case the directory read doesn't complete.
303 content::BrowserThread::PostDelayedTask(
304 content::BrowserThread::UI, FROM_HERE,
305 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
306 weak_factory_.GetWeakPtr(), root),
307 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
310 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
311 const base::FilePath& root) {
312 if (received_all_files_)
315 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
316 iter != read_dir_transactions_.end();) {
317 if (iter->directory != root) {
321 iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
322 iter = read_dir_transactions_.erase(iter);
326 void MTPDeviceDelegateImplMac::DownloadFile(
327 const base::FilePath& device_file_path,
328 const base::FilePath& local_path,
329 const CreateSnapshotFileSuccessCallback& success_callback,
330 const ErrorCallback& error_callback) {
331 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
333 base::File::Error error;
334 base::File::Info info;
335 GetFileInfoImpl(device_file_path, &info, &error);
336 if (error != base::File::FILE_OK) {
337 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
338 base::Bind(error_callback,
343 base::FilePath relative_path;
344 root_path_.AppendRelativePath(device_file_path, &relative_path);
346 read_file_transactions_.push_back(
347 ReadFileRequest(relative_path.value(), local_path,
348 success_callback, error_callback));
350 camera_interface_->DownloadFile(relative_path.value(), local_path);
353 void MTPDeviceDelegateImplMac::CancelAndDelete() {
354 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
355 // Artificially pretend that we have already gotten all items we're going
361 // Schedule the camera session to be closed and the interface deleted.
362 // This will cancel any downloads in progress.
363 camera_interface_->ResetDelegate();
364 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
365 base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
366 base::Unretained(camera_interface_.release())));
371 void MTPDeviceDelegateImplMac::CancelDownloads() {
372 // Cancel any outstanding callbacks.
373 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
374 iter != read_file_transactions_.end(); ++iter) {
375 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
376 base::Bind(iter->error_callback,
377 base::File::FILE_ERROR_ABORT));
379 read_file_transactions_.clear();
381 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
382 iter != read_dir_transactions_.end(); ++iter) {
383 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
384 base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT));
386 read_dir_transactions_.clear();
389 // Called on the UI thread by the listener
390 void MTPDeviceDelegateImplMac::ItemAdded(
391 const std::string& name, const base::File::Info& info) {
392 if (received_all_files_)
395 // This kinda should go in a Join method in FilePath...
396 base::FilePath relative_path(name);
397 std::vector<base::FilePath::StringType> components;
398 relative_path.GetComponents(&components);
399 base::FilePath item_filename = root_path_;
400 for (std::vector<base::FilePath::StringType>::iterator iter =
402 iter != components.end(); ++iter) {
403 item_filename = item_filename.Append(*iter);
406 file_info_[item_filename.value()] = info;
407 file_paths_.push_back(item_filename);
409 // TODO(gbillock): Should we send new files to
410 // read_dir_transactions_ callbacks?
413 // Called in the UI thread by delegate.
414 void MTPDeviceDelegateImplMac::NoMoreItems() {
415 received_all_files_ = true;
416 std::sort(file_paths_.begin(), file_paths_.end());
421 void MTPDeviceDelegateImplMac::NotifyReadDir() {
422 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
423 iter != read_dir_transactions_.end(); ++iter) {
424 base::FilePath read_path = iter->directory;
425 // This code assumes that the list of paths is sorted, so we skip to
426 // where we find the entry for the directory, then read out all first-level
427 // children. We then break when the DirName is greater than the read_path,
428 // as that means we've passed the subdir we're reading.
429 storage::AsyncFileUtil::EntryList entry_list;
430 bool found_path = false;
431 for (size_t i = 0; i < file_paths_.size(); ++i) {
432 if (file_paths_[i] == read_path) {
436 if (!read_path.IsParent(file_paths_[i])) {
437 if (read_path < file_paths_[i].DirName())
441 if (file_paths_[i].DirName() != read_path)
444 base::FilePath relative_path;
445 read_path.AppendRelativePath(file_paths_[i], &relative_path);
446 base::File::Info info = file_info_[file_paths_[i].value()];
447 storage::DirectoryEntry entry;
448 entry.name = relative_path.value();
449 entry.is_directory = info.is_directory;
450 entry.size = info.size;
451 entry.last_modified_time = info.last_modified;
452 entry_list.push_back(entry);
456 content::BrowserThread::PostTask(content::BrowserThread::IO,
458 base::Bind(iter->success_callback, entry_list, false));
460 content::BrowserThread::PostTask(content::BrowserThread::IO,
462 base::Bind(iter->error_callback,
463 base::File::FILE_ERROR_NOT_FOUND));
467 read_dir_transactions_.clear();
470 // Invoked on UI thread from the listener.
471 void MTPDeviceDelegateImplMac::DownloadedFile(
472 const std::string& name, base::File::Error error) {
473 // If we're cancelled and deleting, we may have deleted the camera.
474 if (!camera_interface_.get())
478 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
479 for (; iter != read_file_transactions_.end(); ++iter) {
480 if (iter->request_file == name) {
488 if (error != base::File::FILE_OK) {
489 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
490 base::Bind(iter->error_callback, error));
491 read_file_transactions_.erase(iter);
495 base::FilePath relative_path(name);
496 std::vector<base::FilePath::StringType> components;
497 relative_path.GetComponents(&components);
498 base::FilePath item_filename = root_path_;
499 for (std::vector<base::FilePath::StringType>::iterator i =
501 i != components.end(); ++i) {
502 item_filename = item_filename.Append(*i);
505 base::File::Info info = file_info_[item_filename.value()];
506 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
507 base::Bind(iter->success_callback, info, iter->snapshot_file));
508 read_file_transactions_.erase(iter);
511 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
512 const std::string& file,
513 const base::FilePath& snapshot_filename,
514 CreateSnapshotFileSuccessCallback success_cb,
515 ErrorCallback error_cb)
516 : request_file(file),
517 snapshot_file(snapshot_filename),
518 success_callback(success_cb),
519 error_callback(error_cb) {}
521 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
523 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
525 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
526 const base::FilePath& dir,
527 ReadDirectorySuccessCallback success_cb,
528 ErrorCallback error_cb)
530 success_callback(success_cb),
531 error_callback(error_cb) {}
533 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
535 void CreateMTPDeviceAsyncDelegate(
536 const base::FilePath::StringType& device_location,
537 const bool read_only,
538 const CreateMTPDeviceAsyncDelegateCallback& cb) {
539 // Write operation is not supported on Mac.
542 std::string device_name = base::FilePath(device_location).BaseName().value();
543 std::string device_id;
544 storage_monitor::StorageInfo::Type type;
545 bool cracked = storage_monitor::StorageInfo::CrackDeviceId(
546 device_name, &type, &device_id);
548 DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
550 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));