Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / media_galleries / mac / mtp_device_delegate_impl_mac.mm
blob89e51088bc73b7f9a297863359d2e53bc203c730
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"
7 #include <algorithm>
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"
16 namespace {
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;
28 }  // namespace
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> {
38  public:
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();
60  private:
61   base::scoped_nsobject<ImageCaptureDevice> camera_device_;
63   // Weak pointer
64   MTPDeviceDelegateImplMac* delegate_;
66   DISALLOW_COPY_AND_ASSIGN(DeviceListener);
69 void MTPDeviceDelegateImplMac::DeviceListener::OpenCameraSession(
70     const std::string& device_id) {
71   camera_device_.reset(
72       [storage_monitor::ImageCaptureDeviceManager::deviceForUUID(device_id)
73           retain]);
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>()];
82   delete this;
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) {
94   if (delegate_)
95     delegate_->ItemAdded(name, info);
98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
99   if (delegate_)
100     delegate_->NoMoreItems();
103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
104     const std::string& name,
105     base::File::Error error) {
106   if (delegate_)
107     delegate_->DownloadedFile(name, error);
110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
111   [camera_device_ close];
112   camera_device_.reset();
113   if (delegate_)
114     delegate_->NoMoreItems();
117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
118   delegate_ = NULL;
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()),
139                  device_id_));
142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
145 namespace {
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);
154   else
155     error_callback.Run(*error);
158 }  // namespace
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,
168       FROM_HERE,
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) {
182   NOTREACHED();
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() {
208   return false;
211 void MTPDeviceDelegateImplMac::ReadBytes(
212     const base::FilePath& device_file_path,
213     const scoped_refptr<net::IOBuffer>& buf,
214     int64 offset,
215     int buf_len,
216     const ReadBytesSuccessCallback& success_callback,
217     const ErrorCallback& error_callback) {
218   NOTREACHED();
221 bool MTPDeviceDelegateImplMac::IsReadOnly() const {
222   return true;
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) {
232   NOTREACHED();
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) {
241   NOTREACHED();
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) {
249   NOTREACHED();
252 void MTPDeviceDelegateImplMac::DeleteFile(
253     const base::FilePath& file_path,
254     const DeleteFileSuccessCallback& success_callback,
255     const ErrorCallback& error_callback) {
256   NOTREACHED();
259 void MTPDeviceDelegateImplMac::DeleteDirectory(
260     const base::FilePath& file_path,
261     const DeleteDirectorySuccessCallback& success_callback,
262     const ErrorCallback& error_callback) {
263   NOTREACHED();
266 void MTPDeviceDelegateImplMac::AddWatcher(
267     const GURL& origin,
268     const base::FilePath& file_path,
269     const bool recursive,
270     const storage::WatcherManager::StatusCallback& callback,
271     const storage::WatcherManager::NotificationCallback&
272         notification_callback) {
273   NOTIMPLEMENTED();
274   callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
277 void MTPDeviceDelegateImplMac::RemoveWatcher(
278     const GURL& origin,
279     const base::FilePath& file_path,
280     const bool recursive,
281     const storage::WatcherManager::StatusCallback& callback) {
282   NOTIMPLEMENTED();
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;
302     return;
303   }
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_) {
318     NotifyReadDir();
319     return;
320   }
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_)
333     return;
335   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
336        iter != read_dir_transactions_.end();) {
337     if (iter->directory != root) {
338       ++iter;
339       continue;
340     }
341     iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
342     iter = read_dir_transactions_.erase(iter);
343   }
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,
359                                                 error));
360     return;
361   }
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
376   // to get.
377   NoMoreItems();
379   CancelDownloads();
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())));
388   delete this;
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));
398   }
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));
405   }
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_)
413     return;
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 =
421            components.begin();
422        iter != components.end(); ++iter) {
423     item_filename = item_filename.Append(*iter);
424   }
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());
438   NotifyReadDir();
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) {
453         found_path = true;
454         continue;
455       }
456       if (!read_path.IsParent(file_paths_[i])) {
457         if (read_path < file_paths_[i].DirName())
458           break;
459         continue;
460       }
461       if (file_paths_[i].DirName() != read_path)
462         continue;
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);
473     }
475     if (found_path) {
476       content::BrowserThread::PostTask(content::BrowserThread::IO,
477           FROM_HERE,
478           base::Bind(iter->success_callback, entry_list, false));
479     } else {
480       content::BrowserThread::PostTask(content::BrowserThread::IO,
481           FROM_HERE,
482           base::Bind(iter->error_callback,
483                      base::File::FILE_ERROR_NOT_FOUND));
484     }
485   }
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())
495     return;
497   bool found = false;
498   ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
499   for (; iter != read_file_transactions_.end(); ++iter) {
500     if (iter->request_file == name) {
501       found = true;
502       break;
503     }
504   }
505   if (!found)
506     return;
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);
512     return;
513   }
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 =
520            components.begin();
521        i != components.end(); ++i) {
522     item_filename = item_filename.Append(*i);
523   }
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)
549     : directory(dir),
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.
560   DCHECK(read_only);
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);
567   DCHECK(cracked);
568   DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
570   cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));