Home | History | Annotate | Download | only in fileapi
      1 // Copyright (c) 2013 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/fileapi/device_media_async_file_util.h"
      6 
      7 #include "base/callback.h"
      8 #include "base/file_util.h"
      9 #include "base/single_thread_task_runner.h"
     10 #include "base/task_runner_util.h"
     11 #include "chrome/browser/media_galleries/fileapi/media_path_filter.h"
     12 #include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h"
     13 #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
     14 #include "chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h"
     15 #include "chrome/browser/media_galleries/fileapi/native_media_file_util.h"
     16 #include "chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "webkit/browser/blob/file_stream_reader.h"
     19 #include "webkit/browser/fileapi/file_system_context.h"
     20 #include "webkit/browser/fileapi/file_system_operation_context.h"
     21 #include "webkit/browser/fileapi/file_system_url.h"
     22 #include "webkit/browser/fileapi/native_file_util.h"
     23 #include "webkit/common/blob/shareable_file_reference.h"
     24 
     25 using fileapi::AsyncFileUtil;
     26 using fileapi::FileSystemOperationContext;
     27 using fileapi::FileSystemURL;
     28 using webkit_blob::ShareableFileReference;
     29 
     30 namespace {
     31 
     32 const char kDeviceMediaAsyncFileUtilTempDir[] = "DeviceMediaFileSystem";
     33 
     34 MTPDeviceAsyncDelegate* GetMTPDeviceDelegate(const FileSystemURL& url) {
     35   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     36   return MTPDeviceMapService::GetInstance()->GetMTPDeviceAsyncDelegate(
     37       url.filesystem_id());
     38 }
     39 
     40 // Called when GetFileInfo method call failed to get the details of file
     41 // specified by the requested url. |callback| is invoked to notify the
     42 // caller about the file |error|.
     43 void OnGetFileInfoError(const AsyncFileUtil::GetFileInfoCallback& callback,
     44                         base::File::Error error) {
     45   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     46   callback.Run(error, base::File::Info());
     47 }
     48 
     49 // Called after OnDidGetFileInfo finishes media check.
     50 // |callback| is invoked to complete the GetFileInfo request.
     51 void OnDidCheckMediaForGetFileInfo(
     52     const AsyncFileUtil::GetFileInfoCallback& callback,
     53     const base::File::Info& file_info,
     54     bool is_valid_file) {
     55   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     56   if (!is_valid_file) {
     57     OnGetFileInfoError(callback, base::File::FILE_ERROR_NOT_FOUND);
     58     return;
     59   }
     60   callback.Run(base::File::FILE_OK, file_info);
     61 }
     62 
     63 // Called after OnDidReadDirectory finishes media check.
     64 // |callback| is invoked to complete the ReadDirectory request.
     65 void OnDidCheckMediaForReadDirectory(
     66     const AsyncFileUtil::ReadDirectoryCallback& callback,
     67     bool has_more,
     68     const AsyncFileUtil::EntryList& file_list) {
     69   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     70   callback.Run(base::File::FILE_OK, file_list, has_more);
     71 }
     72 
     73 // Called when ReadDirectory method call failed to enumerate the directory
     74 // objects. |callback| is invoked to notify the caller about the |error|
     75 // that occured while reading the directory objects.
     76 void OnReadDirectoryError(const AsyncFileUtil::ReadDirectoryCallback& callback,
     77                           base::File::Error error) {
     78   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     79   callback.Run(error, AsyncFileUtil::EntryList(), false /*no more*/);
     80 }
     81 
     82 // Called on a blocking pool thread to create a snapshot file to hold the
     83 // contents of |device_file_path|. The snapshot file is created in the
     84 // "profile_path/kDeviceMediaAsyncFileUtilTempDir" directory. Return the
     85 // snapshot file path or an empty path on failure.
     86 base::FilePath CreateSnapshotFileOnBlockingPool(
     87     const base::FilePath& device_file_path,
     88     const base::FilePath& profile_path) {
     89   base::FilePath snapshot_file_path;
     90   base::FilePath media_file_system_dir_path =
     91       profile_path.AppendASCII(kDeviceMediaAsyncFileUtilTempDir);
     92   if (!base::CreateDirectory(media_file_system_dir_path) ||
     93       !base::CreateTemporaryFileInDir(media_file_system_dir_path,
     94                                       &snapshot_file_path)) {
     95     LOG(WARNING) << "Could not create media snapshot file "
     96                  << media_file_system_dir_path.value();
     97     snapshot_file_path = base::FilePath();
     98   }
     99   return snapshot_file_path;
    100 }
    101 
    102 // Called after OnDidCreateSnapshotFile finishes media check.
    103 // |callback| is invoked to complete the CreateSnapshotFile request.
    104 void OnDidCheckMediaForCreateSnapshotFile(
    105     const AsyncFileUtil::CreateSnapshotFileCallback& callback,
    106     const base::File::Info& file_info,
    107     scoped_refptr<webkit_blob::ShareableFileReference> platform_file,
    108     base::File::Error error) {
    109   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    110   base::FilePath platform_path(platform_file.get()->path());
    111   if (error != base::File::FILE_OK)
    112     platform_file = NULL;
    113   callback.Run(error, file_info, platform_path, platform_file);
    114 }
    115 
    116 // Called when the snapshot file specified by the |platform_path| is
    117 // successfully created. |file_info| contains the device media file details
    118 // for which the snapshot file is created.
    119 void OnDidCreateSnapshotFile(
    120     const AsyncFileUtil::CreateSnapshotFileCallback& callback,
    121     base::SequencedTaskRunner* media_task_runner,
    122     bool validate_media_files,
    123     const base::File::Info& file_info,
    124     const base::FilePath& platform_path) {
    125   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    126   scoped_refptr<webkit_blob::ShareableFileReference> file =
    127       ShareableFileReference::GetOrCreate(
    128           platform_path,
    129           ShareableFileReference::DELETE_ON_FINAL_RELEASE,
    130           media_task_runner);
    131 
    132   if (validate_media_files) {
    133     base::PostTaskAndReplyWithResult(
    134         media_task_runner,
    135         FROM_HERE,
    136         base::Bind(&NativeMediaFileUtil::IsMediaFile, platform_path),
    137         base::Bind(&OnDidCheckMediaForCreateSnapshotFile,
    138                    callback,
    139                    file_info,
    140                    file));
    141   } else {
    142     OnDidCheckMediaForCreateSnapshotFile(callback, file_info, file,
    143                                          base::File::FILE_OK);
    144   }
    145 }
    146 
    147 // Called when CreateSnapshotFile method call fails. |callback| is invoked to
    148 // notify the caller about the |error|.
    149 void OnCreateSnapshotFileError(
    150     const AsyncFileUtil::CreateSnapshotFileCallback& callback,
    151     base::File::Error error) {
    152   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    153   callback.Run(error, base::File::Info(), base::FilePath(),
    154                scoped_refptr<ShareableFileReference>());
    155 }
    156 
    157 // Called when the snapshot file specified by the |snapshot_file_path| is
    158 // created to hold the contents of the url.path(). If the snapshot
    159 // file is successfully created, |snapshot_file_path| will be an non-empty
    160 // file path. In case of failure, |snapshot_file_path| will be an empty file
    161 // path. Forwards the CreateSnapshot request to the delegate to copy the
    162 // contents of url.path() to |snapshot_file_path|.
    163 void OnSnapshotFileCreatedRunTask(
    164     scoped_ptr<FileSystemOperationContext> context,
    165     const AsyncFileUtil::CreateSnapshotFileCallback& callback,
    166     const FileSystemURL& url,
    167     bool validate_media_files,
    168     const base::FilePath& snapshot_file_path) {
    169   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    170   if (snapshot_file_path.empty()) {
    171     OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_FAILED);
    172     return;
    173   }
    174   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    175   if (!delegate) {
    176     OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_NOT_FOUND);
    177     return;
    178   }
    179   delegate->CreateSnapshotFile(
    180       url.path(),  // device file path
    181       snapshot_file_path,
    182       base::Bind(&OnDidCreateSnapshotFile,
    183                  callback,
    184                  make_scoped_refptr(context->task_runner()),
    185                  validate_media_files),
    186       base::Bind(&OnCreateSnapshotFileError, callback));
    187 }
    188 
    189 }  // namespace
    190 
    191 class DeviceMediaAsyncFileUtil::MediaPathFilterWrapper
    192     : public base::RefCountedThreadSafe<MediaPathFilterWrapper> {
    193  public:
    194   MediaPathFilterWrapper();
    195 
    196   // Check if entries in |file_list| look like media files.
    197   // Append the ones that look like media files to |results|.
    198   // Should run on a media task runner.
    199   AsyncFileUtil::EntryList FilterMediaEntries(
    200       const AsyncFileUtil::EntryList& file_list);
    201 
    202   // Check if |path| looks like a media file.
    203   bool CheckFilePath(const base::FilePath& path);
    204 
    205  private:
    206   friend class base::RefCountedThreadSafe<MediaPathFilterWrapper>;
    207 
    208   virtual ~MediaPathFilterWrapper();
    209 
    210   scoped_ptr<MediaPathFilter> media_path_filter_;
    211 
    212   DISALLOW_COPY_AND_ASSIGN(MediaPathFilterWrapper);
    213 };
    214 
    215 DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::MediaPathFilterWrapper()
    216     : media_path_filter_(new MediaPathFilter) {
    217 }
    218 
    219 DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::~MediaPathFilterWrapper() {
    220 }
    221 
    222 AsyncFileUtil::EntryList
    223 DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::FilterMediaEntries(
    224     const AsyncFileUtil::EntryList& file_list) {
    225   AsyncFileUtil::EntryList results;
    226   for (size_t i = 0; i < file_list.size(); ++i) {
    227     const fileapi::DirectoryEntry& entry = file_list[i];
    228     if (entry.is_directory || CheckFilePath(base::FilePath(entry.name))) {
    229       results.push_back(entry);
    230     }
    231   }
    232   return results;
    233 }
    234 
    235 bool DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::CheckFilePath(
    236     const base::FilePath& path) {
    237   return media_path_filter_->Match(path);
    238 }
    239 
    240 DeviceMediaAsyncFileUtil::~DeviceMediaAsyncFileUtil() {
    241 }
    242 
    243 // static
    244 scoped_ptr<DeviceMediaAsyncFileUtil> DeviceMediaAsyncFileUtil::Create(
    245     const base::FilePath& profile_path,
    246     MediaFileValidationType validation_type) {
    247   DCHECK(!profile_path.empty());
    248   return make_scoped_ptr(
    249       new DeviceMediaAsyncFileUtil(profile_path, validation_type));
    250 }
    251 
    252 bool DeviceMediaAsyncFileUtil::SupportsStreaming(
    253     const fileapi::FileSystemURL& url) {
    254   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    255   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    256   if (!delegate)
    257     return false;
    258   return delegate->IsStreaming();
    259 }
    260 
    261 void DeviceMediaAsyncFileUtil::CreateOrOpen(
    262     scoped_ptr<FileSystemOperationContext> context,
    263     const FileSystemURL& url,
    264     int file_flags,
    265     const CreateOrOpenCallback& callback) {
    266   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    267   // Returns an error if any unsupported flag is found.
    268   if (file_flags & ~(base::File::FLAG_OPEN |
    269                      base::File::FLAG_READ |
    270                      base::File::FLAG_WRITE_ATTRIBUTES)) {
    271     callback.Run(base::File(base::File::FILE_ERROR_SECURITY), base::Closure());
    272     return;
    273   }
    274   CreateSnapshotFile(
    275       context.Pass(),
    276       url,
    277       base::Bind(&NativeMediaFileUtil::CreatedSnapshotFileForCreateOrOpen,
    278                  make_scoped_refptr(context->task_runner()),
    279                  file_flags,
    280                  callback));
    281 }
    282 
    283 void DeviceMediaAsyncFileUtil::EnsureFileExists(
    284     scoped_ptr<FileSystemOperationContext> context,
    285     const FileSystemURL& url,
    286     const EnsureFileExistsCallback& callback) {
    287   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    288   NOTIMPLEMENTED();
    289   callback.Run(base::File::FILE_ERROR_SECURITY, false);
    290 }
    291 
    292 void DeviceMediaAsyncFileUtil::CreateDirectory(
    293     scoped_ptr<FileSystemOperationContext> context,
    294     const FileSystemURL& url,
    295     bool exclusive,
    296     bool recursive,
    297     const StatusCallback& callback) {
    298   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    299   NOTIMPLEMENTED();
    300   callback.Run(base::File::FILE_ERROR_SECURITY);
    301 }
    302 
    303 void DeviceMediaAsyncFileUtil::GetFileInfo(
    304     scoped_ptr<FileSystemOperationContext> context,
    305     const FileSystemURL& url,
    306     const GetFileInfoCallback& callback) {
    307   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    308   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    309   if (!delegate) {
    310     OnGetFileInfoError(callback, base::File::FILE_ERROR_NOT_FOUND);
    311     return;
    312   }
    313   delegate->GetFileInfo(
    314       url.path(),
    315       base::Bind(&DeviceMediaAsyncFileUtil::OnDidGetFileInfo,
    316                  weak_ptr_factory_.GetWeakPtr(),
    317                  base::Passed(&context),
    318                  url.path(),
    319                  callback),
    320       base::Bind(&OnGetFileInfoError, callback));
    321 }
    322 
    323 void DeviceMediaAsyncFileUtil::ReadDirectory(
    324     scoped_ptr<FileSystemOperationContext> context,
    325     const FileSystemURL& url,
    326     const ReadDirectoryCallback& callback) {
    327   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    328   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    329   if (!delegate) {
    330     OnReadDirectoryError(callback, base::File::FILE_ERROR_NOT_FOUND);
    331     return;
    332   }
    333   delegate->ReadDirectory(
    334       url.path(),
    335       base::Bind(&DeviceMediaAsyncFileUtil::OnDidReadDirectory,
    336                  weak_ptr_factory_.GetWeakPtr(),
    337                  base::Passed(&context),
    338                  callback),
    339       base::Bind(&OnReadDirectoryError, callback));
    340 }
    341 
    342 void DeviceMediaAsyncFileUtil::Touch(
    343     scoped_ptr<FileSystemOperationContext> context,
    344     const FileSystemURL& url,
    345     const base::Time& last_access_time,
    346     const base::Time& last_modified_time,
    347     const StatusCallback& callback) {
    348   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    349   NOTIMPLEMENTED();
    350   callback.Run(base::File::FILE_ERROR_SECURITY);
    351 }
    352 
    353 void DeviceMediaAsyncFileUtil::Truncate(
    354     scoped_ptr<FileSystemOperationContext> context,
    355     const FileSystemURL& url,
    356     int64 length,
    357     const StatusCallback& callback) {
    358   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    359   NOTIMPLEMENTED();
    360   callback.Run(base::File::FILE_ERROR_SECURITY);
    361 }
    362 
    363 void DeviceMediaAsyncFileUtil::CopyFileLocal(
    364     scoped_ptr<FileSystemOperationContext> context,
    365     const FileSystemURL& src_url,
    366     const FileSystemURL& dest_url,
    367     CopyOrMoveOption option,
    368     const CopyFileProgressCallback& progress_callback,
    369     const StatusCallback& callback) {
    370   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    371   NOTIMPLEMENTED();
    372   callback.Run(base::File::FILE_ERROR_SECURITY);
    373 }
    374 
    375 void DeviceMediaAsyncFileUtil::MoveFileLocal(
    376     scoped_ptr<FileSystemOperationContext> context,
    377     const FileSystemURL& src_url,
    378     const FileSystemURL& dest_url,
    379     CopyOrMoveOption option,
    380     const StatusCallback& callback) {
    381   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    382   NOTIMPLEMENTED();
    383   callback.Run(base::File::FILE_ERROR_SECURITY);
    384 }
    385 
    386 void DeviceMediaAsyncFileUtil::CopyInForeignFile(
    387     scoped_ptr<FileSystemOperationContext> context,
    388     const base::FilePath& src_file_path,
    389     const FileSystemURL& dest_url,
    390     const StatusCallback& callback) {
    391   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    392   NOTIMPLEMENTED();
    393   callback.Run(base::File::FILE_ERROR_SECURITY);
    394 }
    395 
    396 void DeviceMediaAsyncFileUtil::DeleteFile(
    397     scoped_ptr<FileSystemOperationContext> context,
    398     const FileSystemURL& url,
    399     const StatusCallback& callback) {
    400   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    401   NOTIMPLEMENTED();
    402   callback.Run(base::File::FILE_ERROR_SECURITY);
    403 }
    404 
    405 void DeviceMediaAsyncFileUtil::DeleteDirectory(
    406     scoped_ptr<FileSystemOperationContext> context,
    407     const FileSystemURL& url,
    408     const StatusCallback& callback) {
    409   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    410   NOTIMPLEMENTED();
    411   callback.Run(base::File::FILE_ERROR_SECURITY);
    412 }
    413 
    414 void DeviceMediaAsyncFileUtil::DeleteRecursively(
    415     scoped_ptr<FileSystemOperationContext> context,
    416     const FileSystemURL& url,
    417     const StatusCallback& callback) {
    418   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    419   callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
    420 }
    421 
    422 void DeviceMediaAsyncFileUtil::CreateSnapshotFile(
    423     scoped_ptr<FileSystemOperationContext> context,
    424     const FileSystemURL& url,
    425     const CreateSnapshotFileCallback& callback) {
    426   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    427   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    428   if (!delegate) {
    429     OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_NOT_FOUND);
    430     return;
    431   }
    432 
    433   scoped_refptr<base::SequencedTaskRunner> task_runner(context->task_runner());
    434   base::PostTaskAndReplyWithResult(
    435       task_runner,
    436       FROM_HERE,
    437       base::Bind(&CreateSnapshotFileOnBlockingPool, url.path(), profile_path_),
    438       base::Bind(&OnSnapshotFileCreatedRunTask,
    439                  base::Passed(&context),
    440                  callback,
    441                  url,
    442                  validate_media_files()));
    443 }
    444 
    445 scoped_ptr<webkit_blob::FileStreamReader>
    446 DeviceMediaAsyncFileUtil::GetFileStreamReader(
    447     const FileSystemURL& url,
    448     int64 offset,
    449     const base::Time& expected_modification_time,
    450     fileapi::FileSystemContext* context) {
    451   MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url);
    452   if (!delegate)
    453     return scoped_ptr<webkit_blob::FileStreamReader>();
    454 
    455   DCHECK(delegate->IsStreaming());
    456   return scoped_ptr<webkit_blob::FileStreamReader>(
    457       new ReadaheadFileStreamReader(
    458           new MTPFileStreamReader(context,
    459                                   url,
    460                                   offset,
    461                                   expected_modification_time,
    462                                   validate_media_files())));
    463 }
    464 
    465 DeviceMediaAsyncFileUtil::DeviceMediaAsyncFileUtil(
    466     const base::FilePath& profile_path,
    467     MediaFileValidationType validation_type)
    468     : profile_path_(profile_path),
    469       weak_ptr_factory_(this) {
    470   if (validation_type == APPLY_MEDIA_FILE_VALIDATION) {
    471     media_path_filter_wrapper_ = new MediaPathFilterWrapper;
    472   }
    473 }
    474 
    475 void DeviceMediaAsyncFileUtil::OnDidGetFileInfo(
    476     scoped_ptr<FileSystemOperationContext> context,
    477     const base::FilePath& path,
    478     const AsyncFileUtil::GetFileInfoCallback& callback,
    479     const base::File::Info& file_info) {
    480   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    481   if (file_info.is_directory || !validate_media_files()) {
    482     OnDidCheckMediaForGetFileInfo(callback, file_info, true /* valid */);
    483     return;
    484   }
    485 
    486   base::PostTaskAndReplyWithResult(
    487       context->task_runner(),
    488       FROM_HERE,
    489       base::Bind(&MediaPathFilterWrapper::CheckFilePath,
    490                  media_path_filter_wrapper_,
    491                  path),
    492       base::Bind(&OnDidCheckMediaForGetFileInfo, callback, file_info));
    493 }
    494 
    495 void DeviceMediaAsyncFileUtil::OnDidReadDirectory(
    496     scoped_ptr<fileapi::FileSystemOperationContext> context,
    497     const AsyncFileUtil::ReadDirectoryCallback& callback,
    498     const AsyncFileUtil::EntryList& file_list,
    499     bool has_more) {
    500   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    501   if (!validate_media_files()) {
    502     OnDidCheckMediaForReadDirectory(callback, has_more, file_list);
    503     return;
    504   }
    505 
    506   base::PostTaskAndReplyWithResult(
    507       context->task_runner(),
    508       FROM_HERE,
    509       base::Bind(&MediaPathFilterWrapper::FilterMediaEntries,
    510                  media_path_filter_wrapper_,
    511                  file_list),
    512       base::Bind(&OnDidCheckMediaForReadDirectory, callback, has_more));
    513 }
    514 
    515 bool DeviceMediaAsyncFileUtil::validate_media_files() const {
    516   return media_path_filter_wrapper_.get() != NULL;
    517 }
    518