Home | History | Annotate | Download | only in mac
      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.
      4 
      5 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
      6 
      7 #include <algorithm>
      8 
      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 "webkit/browser/fileapi/async_file_util.h"
     15 
     16 namespace {
     17 
     18 int kReadDirectoryTimeLimitSeconds = 20;
     19 
     20 typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
     21     CreateSnapshotFileSuccessCallback;
     22 typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback;
     23 typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback
     24     GetFileInfoSuccessCallback;
     25 typedef MTPDeviceAsyncDelegate::ReadDirectorySuccessCallback
     26     ReadDirectorySuccessCallback;
     27 
     28 }  // namespace
     29 
     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   virtual ~DeviceListener() {}
     42 
     43   void OpenCameraSession(const std::string& device_id);
     44   void CloseCameraSessionAndDelete();
     45 
     46   void DownloadFile(const std::string& name, const base::FilePath& local_path);
     47 
     48   // ImageCaptureDeviceListener
     49   virtual void ItemAdded(const std::string& name,
     50                          const base::File::Info& info) OVERRIDE;
     51   virtual void NoMoreItems() OVERRIDE;
     52   virtual void DownloadedFile(const std::string& name,
     53                               base::File::Error error) OVERRIDE;
     54   virtual void DeviceRemoved() OVERRIDE;
     55 
     56   // Used during delegate destruction to ensure there are no more calls
     57   // to the delegate by the listener.
     58   virtual void ResetDelegate();
     59 
     60  private:
     61   base::scoped_nsobject<ImageCaptureDevice> camera_device_;
     62 
     63   // Weak pointer
     64   MTPDeviceDelegateImplMac* delegate_;
     65 
     66   DISALLOW_COPY_AND_ASSIGN(DeviceListener);
     67 };
     68 
     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];
     76 }
     77 
     78 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
     79   [camera_device_ close];
     80   [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
     81 
     82   delete this;
     83 }
     84 
     85 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
     86     const std::string& name,
     87     const base::FilePath& local_path) {
     88   [camera_device_ downloadFile:name localPath:local_path];
     89 }
     90 
     91 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
     92     const std::string& name,
     93     const base::File::Info& info) {
     94   if (delegate_)
     95     delegate_->ItemAdded(name, info);
     96 }
     97 
     98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
     99   if (delegate_)
    100     delegate_->NoMoreItems();
    101 }
    102 
    103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
    104     const std::string& name,
    105     base::File::Error error) {
    106   if (delegate_)
    107     delegate_->DownloadedFile(name, error);
    108 }
    109 
    110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
    111   [camera_device_ close];
    112   camera_device_.reset();
    113   if (delegate_)
    114     delegate_->NoMoreItems();
    115 }
    116 
    117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
    118   delegate_ = NULL;
    119 }
    120 
    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) {
    128 
    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;
    134 
    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_));
    140 }
    141 
    142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
    143 }
    144 
    145 namespace {
    146 
    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);
    156 }
    157 
    158 }  // namespace
    159 
    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));
    174 }
    175 
    176 void MTPDeviceDelegateImplMac::ReadDirectory(
    177       const base::FilePath& root,
    178       const ReadDirectorySuccessCallback& success_callback,
    179       const ErrorCallback& error_callback) {
    180   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    181       base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
    182                  base::Unretained(this),
    183                  root, success_callback, error_callback));
    184 }
    185 
    186 void MTPDeviceDelegateImplMac::CreateSnapshotFile(
    187       const base::FilePath& device_file_path,
    188       const base::FilePath& local_path,
    189       const CreateSnapshotFileSuccessCallback& success_callback,
    190       const ErrorCallback& error_callback) {
    191   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    192       base::Bind(&MTPDeviceDelegateImplMac::DownloadFile,
    193                  base::Unretained(this),
    194                  device_file_path, local_path,
    195                  success_callback, error_callback));
    196 }
    197 
    198 bool MTPDeviceDelegateImplMac::IsStreaming() {
    199   return false;
    200 }
    201 
    202 void MTPDeviceDelegateImplMac::ReadBytes(
    203     const base::FilePath& device_file_path,
    204     net::IOBuffer* buf, int64 offset, int buf_len,
    205     const ReadBytesSuccessCallback& success_callback,
    206     const ErrorCallback& error_callback) {
    207   NOTREACHED();
    208 }
    209 
    210 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
    211   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    212       base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
    213                  base::Unretained(this)));
    214 }
    215 
    216 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
    217     const base::FilePath& file_path,
    218     base::File::Info* file_info,
    219     base::File::Error* error) {
    220   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    221   base::hash_map<base::FilePath::StringType,
    222                  base::File::Info>::const_iterator i =
    223       file_info_.find(file_path.value());
    224   if (i == file_info_.end()) {
    225     *error = base::File::FILE_ERROR_NOT_FOUND;
    226     return;
    227   }
    228   *file_info = i->second;
    229   *error = base::File::FILE_OK;
    230 }
    231 
    232 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
    233       const base::FilePath& root,
    234       const ReadDirectorySuccessCallback& success_callback,
    235       const ErrorCallback& error_callback) {
    236   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    237 
    238   read_dir_transactions_.push_back(ReadDirectoryRequest(
    239       root, success_callback, error_callback));
    240 
    241   if (received_all_files_) {
    242     NotifyReadDir();
    243     return;
    244   }
    245 
    246   // Schedule a timeout in case the directory read doesn't complete.
    247   content::BrowserThread::PostDelayedTask(
    248       content::BrowserThread::UI, FROM_HERE,
    249       base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
    250                  weak_factory_.GetWeakPtr(), root),
    251       base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
    252 }
    253 
    254 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
    255     const base::FilePath& root) {
    256   if (received_all_files_)
    257     return;
    258 
    259   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
    260        iter != read_dir_transactions_.end();) {
    261     if (iter->directory != root) {
    262       ++iter;
    263       continue;
    264     }
    265     iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
    266     iter = read_dir_transactions_.erase(iter);
    267   }
    268 }
    269 
    270 void MTPDeviceDelegateImplMac::DownloadFile(
    271       const base::FilePath& device_file_path,
    272       const base::FilePath& local_path,
    273       const CreateSnapshotFileSuccessCallback& success_callback,
    274       const ErrorCallback& error_callback) {
    275   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    276 
    277   base::File::Error error;
    278   base::File::Info info;
    279   GetFileInfoImpl(device_file_path, &info, &error);
    280   if (error != base::File::FILE_OK) {
    281     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    282                                      base::Bind(error_callback,
    283                                                 error));
    284     return;
    285   }
    286 
    287   base::FilePath relative_path;
    288   root_path_.AppendRelativePath(device_file_path, &relative_path);
    289 
    290   read_file_transactions_.push_back(
    291       ReadFileRequest(relative_path.value(), local_path,
    292                       success_callback, error_callback));
    293 
    294   camera_interface_->DownloadFile(relative_path.value(), local_path);
    295 }
    296 
    297 void MTPDeviceDelegateImplMac::CancelAndDelete() {
    298   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    299   // Artificially pretend that we have already gotten all items we're going
    300   // to get.
    301   NoMoreItems();
    302 
    303   CancelDownloads();
    304 
    305   // Schedule the camera session to be closed and the interface deleted.
    306   // This will cancel any downloads in progress.
    307   camera_interface_->ResetDelegate();
    308   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    309       base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
    310                  base::Unretained(camera_interface_.release())));
    311 
    312   delete this;
    313 }
    314 
    315 void MTPDeviceDelegateImplMac::CancelDownloads() {
    316   // Cancel any outstanding callbacks.
    317   for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
    318        iter != read_file_transactions_.end(); ++iter) {
    319     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    320         base::Bind(iter->error_callback,
    321                    base::File::FILE_ERROR_ABORT));
    322   }
    323   read_file_transactions_.clear();
    324 
    325   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
    326        iter != read_dir_transactions_.end(); ++iter) {
    327     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    328         base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT));
    329   }
    330   read_dir_transactions_.clear();
    331 }
    332 
    333 // Called on the UI thread by the listener
    334 void MTPDeviceDelegateImplMac::ItemAdded(
    335     const std::string& name, const base::File::Info& info) {
    336   if (received_all_files_)
    337     return;
    338 
    339   // This kinda should go in a Join method in FilePath...
    340   base::FilePath relative_path(name);
    341   std::vector<base::FilePath::StringType> components;
    342   relative_path.GetComponents(&components);
    343   base::FilePath item_filename = root_path_;
    344   for (std::vector<base::FilePath::StringType>::iterator iter =
    345            components.begin();
    346        iter != components.end(); ++iter) {
    347     item_filename = item_filename.Append(*iter);
    348   }
    349 
    350   file_info_[item_filename.value()] = info;
    351   file_paths_.push_back(item_filename);
    352 
    353   // TODO(gbillock): Should we send new files to
    354   // read_dir_transactions_ callbacks?
    355 }
    356 
    357 // Called in the UI thread by delegate.
    358 void MTPDeviceDelegateImplMac::NoMoreItems() {
    359   received_all_files_ = true;
    360   std::sort(file_paths_.begin(), file_paths_.end());
    361 
    362   NotifyReadDir();
    363 }
    364 
    365 void MTPDeviceDelegateImplMac::NotifyReadDir() {
    366   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
    367        iter != read_dir_transactions_.end(); ++iter) {
    368     base::FilePath read_path = iter->directory;
    369     // This code assumes that the list of paths is sorted, so we skip to
    370     // where we find the entry for the directory, then read out all first-level
    371     // children. We then break when the DirName is greater than the read_path,
    372     // as that means we've passed the subdir we're reading.
    373     fileapi::AsyncFileUtil::EntryList entry_list;
    374     bool found_path = false;
    375     for (size_t i = 0; i < file_paths_.size(); ++i) {
    376       if (file_paths_[i] == read_path) {
    377         found_path = true;
    378         continue;
    379       }
    380       if (!read_path.IsParent(file_paths_[i])) {
    381         if (read_path < file_paths_[i].DirName())
    382           break;
    383         continue;
    384       }
    385       if (file_paths_[i].DirName() != read_path)
    386         continue;
    387 
    388       base::FilePath relative_path;
    389       read_path.AppendRelativePath(file_paths_[i], &relative_path);
    390       base::File::Info info = file_info_[file_paths_[i].value()];
    391       fileapi::DirectoryEntry entry;
    392       entry.name = relative_path.value();
    393       entry.is_directory = info.is_directory;
    394       entry.size = info.size;
    395       entry.last_modified_time = info.last_modified;
    396       entry_list.push_back(entry);
    397     }
    398 
    399     if (found_path) {
    400       content::BrowserThread::PostTask(content::BrowserThread::IO,
    401           FROM_HERE,
    402           base::Bind(iter->success_callback, entry_list, false));
    403     } else {
    404       content::BrowserThread::PostTask(content::BrowserThread::IO,
    405           FROM_HERE,
    406           base::Bind(iter->error_callback,
    407                      base::File::FILE_ERROR_NOT_FOUND));
    408     }
    409   }
    410 
    411   read_dir_transactions_.clear();
    412 }
    413 
    414 // Invoked on UI thread from the listener.
    415 void MTPDeviceDelegateImplMac::DownloadedFile(
    416     const std::string& name, base::File::Error error) {
    417   // If we're cancelled and deleting, we may have deleted the camera.
    418   if (!camera_interface_.get())
    419     return;
    420 
    421   bool found = false;
    422   ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
    423   for (; iter != read_file_transactions_.end(); ++iter) {
    424     if (iter->request_file == name) {
    425       found = true;
    426       break;
    427     }
    428   }
    429   if (!found)
    430     return;
    431 
    432   if (error != base::File::FILE_OK) {
    433     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    434         base::Bind(iter->error_callback, error));
    435     read_file_transactions_.erase(iter);
    436     return;
    437   }
    438 
    439   base::FilePath relative_path(name);
    440   std::vector<base::FilePath::StringType> components;
    441   relative_path.GetComponents(&components);
    442   base::FilePath item_filename = root_path_;
    443   for (std::vector<base::FilePath::StringType>::iterator i =
    444            components.begin();
    445        i != components.end(); ++i) {
    446     item_filename = item_filename.Append(*i);
    447   }
    448 
    449   base::File::Info info = file_info_[item_filename.value()];
    450   content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    451       base::Bind(iter->success_callback, info, iter->snapshot_file));
    452   read_file_transactions_.erase(iter);
    453 }
    454 
    455 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
    456     const std::string& file,
    457     const base::FilePath& snapshot_filename,
    458     CreateSnapshotFileSuccessCallback success_cb,
    459     ErrorCallback error_cb)
    460     : request_file(file),
    461       snapshot_file(snapshot_filename),
    462       success_callback(success_cb),
    463       error_callback(error_cb) {}
    464 
    465 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
    466 
    467 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
    468 
    469 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
    470     const base::FilePath& dir,
    471     ReadDirectorySuccessCallback success_cb,
    472     ErrorCallback error_cb)
    473     : directory(dir),
    474       success_callback(success_cb),
    475       error_callback(error_cb) {}
    476 
    477 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
    478 
    479 void CreateMTPDeviceAsyncDelegate(
    480     const base::FilePath::StringType& device_location,
    481     const CreateMTPDeviceAsyncDelegateCallback& cb) {
    482   std::string device_name = base::FilePath(device_location).BaseName().value();
    483   std::string device_id;
    484   storage_monitor::StorageInfo::Type type;
    485   bool cracked = storage_monitor::StorageInfo::CrackDeviceId(
    486       device_name, &type, &device_id);
    487   DCHECK(cracked);
    488   DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
    489 
    490   cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));
    491 }
    492