| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/media_galleries/fileapi/device_media_async_file_util.h" |
| |
| #include "base/callback.h" |
| #include "base/files/file_util.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/task_runner_util.h" |
| #include "chrome/browser/media_galleries/fileapi/media_path_filter.h" |
| #include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" |
| #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h" |
| #include "chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h" |
| #include "chrome/browser/media_galleries/fileapi/native_media_file_util.h" |
| #include "chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "storage/browser/blob/file_stream_reader.h" |
| #include "storage/browser/blob/shareable_file_reference.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/file_system_operation_context.h" |
| #include "storage/browser/fileapi/file_system_url.h" |
| #include "storage/browser/fileapi/native_file_util.h" |
| |
| using storage::AsyncFileUtil; |
| using storage::FileSystemOperationContext; |
| using storage::FileSystemURL; |
| using storage::ShareableFileReference; |
| |
| namespace { |
| |
| const char kDeviceMediaAsyncFileUtilTempDir[] = "DeviceMediaFileSystem"; |
| |
| MTPDeviceAsyncDelegate* GetMTPDeviceDelegate(const FileSystemURL& url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| return MTPDeviceMapService::GetInstance()->GetMTPDeviceAsyncDelegate( |
| url.filesystem_id()); |
| } |
| |
| // Called when GetFileInfo method call failed to get the details of file |
| // specified by the requested url. |callback| is invoked to notify the |
| // caller about the file |error|. |
| void OnGetFileInfoError(const AsyncFileUtil::GetFileInfoCallback& callback, |
| base::File::Error error) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| callback.Run(error, base::File::Info()); |
| } |
| |
| // Called after OnDidGetFileInfo finishes media check. |
| // |callback| is invoked to complete the GetFileInfo request. |
| void OnDidCheckMediaForGetFileInfo( |
| const AsyncFileUtil::GetFileInfoCallback& callback, |
| const base::File::Info& file_info, |
| bool is_valid_file) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| if (!is_valid_file) { |
| OnGetFileInfoError(callback, base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| callback.Run(base::File::FILE_OK, file_info); |
| } |
| |
| // Called after OnDidReadDirectory finishes media check. |
| // |callback| is invoked to complete the ReadDirectory request. |
| void OnDidCheckMediaForReadDirectory( |
| const AsyncFileUtil::ReadDirectoryCallback& callback, |
| bool has_more, |
| const AsyncFileUtil::EntryList& file_list) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| callback.Run(base::File::FILE_OK, file_list, has_more); |
| } |
| |
| // Called when ReadDirectory method call failed to enumerate the directory |
| // objects. |callback| is invoked to notify the caller about the |error| |
| // that occured while reading the directory objects. |
| void OnReadDirectoryError(const AsyncFileUtil::ReadDirectoryCallback& callback, |
| base::File::Error error) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| callback.Run(error, AsyncFileUtil::EntryList(), false /*no more*/); |
| } |
| |
| // Called on a blocking pool thread to create a snapshot file to hold the |
| // contents of |device_file_path|. The snapshot file is created in the |
| // "profile_path/kDeviceMediaAsyncFileUtilTempDir" directory. Return the |
| // snapshot file path or an empty path on failure. |
| base::FilePath CreateSnapshotFileOnBlockingPool( |
| const base::FilePath& device_file_path, |
| const base::FilePath& profile_path) { |
| base::FilePath snapshot_file_path; |
| base::FilePath media_file_system_dir_path = |
| profile_path.AppendASCII(kDeviceMediaAsyncFileUtilTempDir); |
| if (!base::CreateDirectory(media_file_system_dir_path) || |
| !base::CreateTemporaryFileInDir(media_file_system_dir_path, |
| &snapshot_file_path)) { |
| LOG(WARNING) << "Could not create media snapshot file " |
| << media_file_system_dir_path.value(); |
| snapshot_file_path = base::FilePath(); |
| } |
| return snapshot_file_path; |
| } |
| |
| // Called after OnDidCreateSnapshotFile finishes media check. |
| // |callback| is invoked to complete the CreateSnapshotFile request. |
| void OnDidCheckMediaForCreateSnapshotFile( |
| const AsyncFileUtil::CreateSnapshotFileCallback& callback, |
| const base::File::Info& file_info, |
| scoped_refptr<storage::ShareableFileReference> platform_file, |
| base::File::Error error) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| base::FilePath platform_path(platform_file.get()->path()); |
| if (error != base::File::FILE_OK) |
| platform_file = NULL; |
| callback.Run(error, file_info, platform_path, platform_file); |
| } |
| |
| // Called when the snapshot file specified by the |platform_path| is |
| // successfully created. |file_info| contains the device media file details |
| // for which the snapshot file is created. |
| void OnDidCreateSnapshotFile( |
| const AsyncFileUtil::CreateSnapshotFileCallback& callback, |
| base::SequencedTaskRunner* media_task_runner, |
| bool validate_media_files, |
| const base::File::Info& file_info, |
| const base::FilePath& platform_path) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| scoped_refptr<storage::ShareableFileReference> file = |
| ShareableFileReference::GetOrCreate( |
| platform_path, |
| ShareableFileReference::DELETE_ON_FINAL_RELEASE, |
| media_task_runner); |
| |
| if (validate_media_files) { |
| base::PostTaskAndReplyWithResult( |
| media_task_runner, |
| FROM_HERE, |
| base::Bind(&NativeMediaFileUtil::IsMediaFile, platform_path), |
| base::Bind(&OnDidCheckMediaForCreateSnapshotFile, |
| callback, |
| file_info, |
| file)); |
| } else { |
| OnDidCheckMediaForCreateSnapshotFile(callback, file_info, file, |
| base::File::FILE_OK); |
| } |
| } |
| |
| // Called when CreateSnapshotFile method call fails. |callback| is invoked to |
| // notify the caller about the |error|. |
| void OnCreateSnapshotFileError( |
| const AsyncFileUtil::CreateSnapshotFileCallback& callback, |
| base::File::Error error) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| callback.Run(error, base::File::Info(), base::FilePath(), |
| scoped_refptr<ShareableFileReference>()); |
| } |
| |
| // Called when the snapshot file specified by the |snapshot_file_path| is |
| // created to hold the contents of the url.path(). If the snapshot |
| // file is successfully created, |snapshot_file_path| will be an non-empty |
| // file path. In case of failure, |snapshot_file_path| will be an empty file |
| // path. Forwards the CreateSnapshot request to the delegate to copy the |
| // contents of url.path() to |snapshot_file_path|. |
| void OnSnapshotFileCreatedRunTask( |
| scoped_ptr<FileSystemOperationContext> context, |
| const AsyncFileUtil::CreateSnapshotFileCallback& callback, |
| const FileSystemURL& url, |
| bool validate_media_files, |
| const base::FilePath& snapshot_file_path) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| if (snapshot_file_path.empty()) { |
| OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) { |
| OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| delegate->CreateSnapshotFile( |
| url.path(), // device file path |
| snapshot_file_path, |
| base::Bind(&OnDidCreateSnapshotFile, |
| callback, |
| make_scoped_refptr(context->task_runner()), |
| validate_media_files), |
| base::Bind(&OnCreateSnapshotFileError, callback)); |
| } |
| |
| } // namespace |
| |
| class DeviceMediaAsyncFileUtil::MediaPathFilterWrapper |
| : public base::RefCountedThreadSafe<MediaPathFilterWrapper> { |
| public: |
| MediaPathFilterWrapper(); |
| |
| // Check if entries in |file_list| look like media files. |
| // Append the ones that look like media files to |results|. |
| // Should run on a media task runner. |
| AsyncFileUtil::EntryList FilterMediaEntries( |
| const AsyncFileUtil::EntryList& file_list); |
| |
| // Check if |path| looks like a media file. |
| bool CheckFilePath(const base::FilePath& path); |
| |
| private: |
| friend class base::RefCountedThreadSafe<MediaPathFilterWrapper>; |
| |
| virtual ~MediaPathFilterWrapper(); |
| |
| scoped_ptr<MediaPathFilter> media_path_filter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MediaPathFilterWrapper); |
| }; |
| |
| DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::MediaPathFilterWrapper() |
| : media_path_filter_(new MediaPathFilter) { |
| } |
| |
| DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::~MediaPathFilterWrapper() { |
| } |
| |
| AsyncFileUtil::EntryList |
| DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::FilterMediaEntries( |
| const AsyncFileUtil::EntryList& file_list) { |
| AsyncFileUtil::EntryList results; |
| for (size_t i = 0; i < file_list.size(); ++i) { |
| const storage::DirectoryEntry& entry = file_list[i]; |
| if (entry.is_directory || CheckFilePath(base::FilePath(entry.name))) { |
| results.push_back(entry); |
| } |
| } |
| return results; |
| } |
| |
| bool DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::CheckFilePath( |
| const base::FilePath& path) { |
| return media_path_filter_->Match(path); |
| } |
| |
| DeviceMediaAsyncFileUtil::~DeviceMediaAsyncFileUtil() { |
| } |
| |
| // static |
| scoped_ptr<DeviceMediaAsyncFileUtil> DeviceMediaAsyncFileUtil::Create( |
| const base::FilePath& profile_path, |
| MediaFileValidationType validation_type) { |
| DCHECK(!profile_path.empty()); |
| return make_scoped_ptr( |
| new DeviceMediaAsyncFileUtil(profile_path, validation_type)); |
| } |
| |
| bool DeviceMediaAsyncFileUtil::SupportsStreaming( |
| const storage::FileSystemURL& url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) |
| return false; |
| return delegate->IsStreaming(); |
| } |
| |
| void DeviceMediaAsyncFileUtil::CreateOrOpen( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| int file_flags, |
| const CreateOrOpenCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| // Returns an error if any unsupported flag is found. |
| if (file_flags & ~(base::File::FLAG_OPEN | |
| base::File::FLAG_READ | |
| base::File::FLAG_WRITE_ATTRIBUTES)) { |
| callback.Run(base::File(base::File::FILE_ERROR_SECURITY), base::Closure()); |
| return; |
| } |
| CreateSnapshotFile( |
| context.Pass(), |
| url, |
| base::Bind(&NativeMediaFileUtil::CreatedSnapshotFileForCreateOrOpen, |
| make_scoped_refptr(context->task_runner()), |
| file_flags, |
| callback)); |
| } |
| |
| void DeviceMediaAsyncFileUtil::EnsureFileExists( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const EnsureFileExistsCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY, false); |
| } |
| |
| void DeviceMediaAsyncFileUtil::CreateDirectory( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| bool exclusive, |
| bool recursive, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::GetFileInfo( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const GetFileInfoCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) { |
| OnGetFileInfoError(callback, base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| delegate->GetFileInfo( |
| url.path(), |
| base::Bind(&DeviceMediaAsyncFileUtil::OnDidGetFileInfo, |
| weak_ptr_factory_.GetWeakPtr(), |
| make_scoped_refptr(context->task_runner()), |
| url.path(), |
| callback), |
| base::Bind(&OnGetFileInfoError, callback)); |
| } |
| |
| void DeviceMediaAsyncFileUtil::ReadDirectory( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const ReadDirectoryCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) { |
| OnReadDirectoryError(callback, base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| delegate->ReadDirectory( |
| url.path(), |
| base::Bind(&DeviceMediaAsyncFileUtil::OnDidReadDirectory, |
| weak_ptr_factory_.GetWeakPtr(), |
| make_scoped_refptr(context->task_runner()), |
| callback), |
| base::Bind(&OnReadDirectoryError, callback)); |
| } |
| |
| void DeviceMediaAsyncFileUtil::Touch( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const base::Time& last_access_time, |
| const base::Time& last_modified_time, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::Truncate( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| int64 length, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::CopyFileLocal( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| CopyOrMoveOption option, |
| const CopyFileProgressCallback& progress_callback, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::MoveFileLocal( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| CopyOrMoveOption option, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::CopyInForeignFile( |
| scoped_ptr<FileSystemOperationContext> context, |
| const base::FilePath& src_file_path, |
| const FileSystemURL& dest_url, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::DeleteFile( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::DeleteDirectory( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| NOTIMPLEMENTED(); |
| callback.Run(base::File::FILE_ERROR_SECURITY); |
| } |
| |
| void DeviceMediaAsyncFileUtil::DeleteRecursively( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const StatusCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); |
| } |
| |
| void DeviceMediaAsyncFileUtil::CreateSnapshotFile( |
| scoped_ptr<FileSystemOperationContext> context, |
| const FileSystemURL& url, |
| const CreateSnapshotFileCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) { |
| OnCreateSnapshotFileError(callback, base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| |
| scoped_refptr<base::SequencedTaskRunner> task_runner(context->task_runner()); |
| base::PostTaskAndReplyWithResult( |
| task_runner.get(), |
| FROM_HERE, |
| base::Bind(&CreateSnapshotFileOnBlockingPool, url.path(), profile_path_), |
| base::Bind(&OnSnapshotFileCreatedRunTask, |
| base::Passed(&context), |
| callback, |
| url, |
| validate_media_files())); |
| } |
| |
| scoped_ptr<storage::FileStreamReader> |
| DeviceMediaAsyncFileUtil::GetFileStreamReader( |
| const FileSystemURL& url, |
| int64 offset, |
| const base::Time& expected_modification_time, |
| storage::FileSystemContext* context) { |
| MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); |
| if (!delegate) |
| return scoped_ptr<storage::FileStreamReader>(); |
| |
| DCHECK(delegate->IsStreaming()); |
| return scoped_ptr<storage::FileStreamReader>(new ReadaheadFileStreamReader( |
| new MTPFileStreamReader(context, |
| url, |
| offset, |
| expected_modification_time, |
| validate_media_files()))); |
| } |
| |
| DeviceMediaAsyncFileUtil::DeviceMediaAsyncFileUtil( |
| const base::FilePath& profile_path, |
| MediaFileValidationType validation_type) |
| : profile_path_(profile_path), |
| weak_ptr_factory_(this) { |
| if (validation_type == APPLY_MEDIA_FILE_VALIDATION) { |
| media_path_filter_wrapper_ = new MediaPathFilterWrapper; |
| } |
| } |
| |
| void DeviceMediaAsyncFileUtil::OnDidGetFileInfo( |
| base::SequencedTaskRunner* task_runner, |
| const base::FilePath& path, |
| const AsyncFileUtil::GetFileInfoCallback& callback, |
| const base::File::Info& file_info) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| if (file_info.is_directory || !validate_media_files()) { |
| OnDidCheckMediaForGetFileInfo(callback, file_info, true /* valid */); |
| return; |
| } |
| |
| base::PostTaskAndReplyWithResult( |
| task_runner, |
| FROM_HERE, |
| base::Bind(&MediaPathFilterWrapper::CheckFilePath, |
| media_path_filter_wrapper_, |
| path), |
| base::Bind(&OnDidCheckMediaForGetFileInfo, callback, file_info)); |
| } |
| |
| void DeviceMediaAsyncFileUtil::OnDidReadDirectory( |
| base::SequencedTaskRunner* task_runner, |
| const AsyncFileUtil::ReadDirectoryCallback& callback, |
| const AsyncFileUtil::EntryList& file_list, |
| bool has_more) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| if (!validate_media_files()) { |
| OnDidCheckMediaForReadDirectory(callback, has_more, file_list); |
| return; |
| } |
| |
| base::PostTaskAndReplyWithResult( |
| task_runner, |
| FROM_HERE, |
| base::Bind(&MediaPathFilterWrapper::FilterMediaEntries, |
| media_path_filter_wrapper_, |
| file_list), |
| base::Bind(&OnDidCheckMediaForReadDirectory, callback, has_more)); |
| } |
| |
| bool DeviceMediaAsyncFileUtil::validate_media_files() const { |
| return media_path_filter_wrapper_.get() != NULL; |
| } |