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/files/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 "storage/browser/blob/file_stream_reader.h" 19#include "storage/browser/fileapi/file_system_context.h" 20#include "storage/browser/fileapi/file_system_operation_context.h" 21#include "storage/browser/fileapi/file_system_url.h" 22#include "storage/browser/fileapi/native_file_util.h" 23#include "storage/common/blob/shareable_file_reference.h" 24 25using storage::AsyncFileUtil; 26using storage::FileSystemOperationContext; 27using storage::FileSystemURL; 28using storage::ShareableFileReference; 29 30namespace { 31 32const char kDeviceMediaAsyncFileUtilTempDir[] = "DeviceMediaFileSystem"; 33 34MTPDeviceAsyncDelegate* 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|. 43void 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. 51void 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. 65void 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. 76void 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. 86base::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. 104void OnDidCheckMediaForCreateSnapshotFile( 105 const AsyncFileUtil::CreateSnapshotFileCallback& callback, 106 const base::File::Info& file_info, 107 scoped_refptr<storage::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. 119void 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<storage::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|. 149void 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|. 163void 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 191class 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 215DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::MediaPathFilterWrapper() 216 : media_path_filter_(new MediaPathFilter) { 217} 218 219DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::~MediaPathFilterWrapper() { 220} 221 222AsyncFileUtil::EntryList 223DeviceMediaAsyncFileUtil::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 storage::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 235bool DeviceMediaAsyncFileUtil::MediaPathFilterWrapper::CheckFilePath( 236 const base::FilePath& path) { 237 return media_path_filter_->Match(path); 238} 239 240DeviceMediaAsyncFileUtil::~DeviceMediaAsyncFileUtil() { 241} 242 243// static 244scoped_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 252bool DeviceMediaAsyncFileUtil::SupportsStreaming( 253 const storage::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 261void 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 283void 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 292void 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 303void 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 make_scoped_refptr(context->task_runner()), 318 url.path(), 319 callback), 320 base::Bind(&OnGetFileInfoError, callback)); 321} 322 323void 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 make_scoped_refptr(context->task_runner()), 338 callback), 339 base::Bind(&OnReadDirectoryError, callback)); 340} 341 342void 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 353void 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 363void 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 375void 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 386void 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 396void 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 405void 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 414void 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 422void 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.get(), 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 445scoped_ptr<storage::FileStreamReader> 446DeviceMediaAsyncFileUtil::GetFileStreamReader( 447 const FileSystemURL& url, 448 int64 offset, 449 const base::Time& expected_modification_time, 450 storage::FileSystemContext* context) { 451 MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(url); 452 if (!delegate) 453 return scoped_ptr<storage::FileStreamReader>(); 454 455 DCHECK(delegate->IsStreaming()); 456 return scoped_ptr<storage::FileStreamReader>(new ReadaheadFileStreamReader( 457 new MTPFileStreamReader(context, 458 url, 459 offset, 460 expected_modification_time, 461 validate_media_files()))); 462} 463 464DeviceMediaAsyncFileUtil::DeviceMediaAsyncFileUtil( 465 const base::FilePath& profile_path, 466 MediaFileValidationType validation_type) 467 : profile_path_(profile_path), 468 weak_ptr_factory_(this) { 469 if (validation_type == APPLY_MEDIA_FILE_VALIDATION) { 470 media_path_filter_wrapper_ = new MediaPathFilterWrapper; 471 } 472} 473 474void DeviceMediaAsyncFileUtil::OnDidGetFileInfo( 475 base::SequencedTaskRunner* task_runner, 476 const base::FilePath& path, 477 const AsyncFileUtil::GetFileInfoCallback& callback, 478 const base::File::Info& file_info) { 479 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 480 if (file_info.is_directory || !validate_media_files()) { 481 OnDidCheckMediaForGetFileInfo(callback, file_info, true /* valid */); 482 return; 483 } 484 485 base::PostTaskAndReplyWithResult( 486 task_runner, 487 FROM_HERE, 488 base::Bind(&MediaPathFilterWrapper::CheckFilePath, 489 media_path_filter_wrapper_, 490 path), 491 base::Bind(&OnDidCheckMediaForGetFileInfo, callback, file_info)); 492} 493 494void DeviceMediaAsyncFileUtil::OnDidReadDirectory( 495 base::SequencedTaskRunner* task_runner, 496 const AsyncFileUtil::ReadDirectoryCallback& callback, 497 const AsyncFileUtil::EntryList& file_list, 498 bool has_more) { 499 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 500 if (!validate_media_files()) { 501 OnDidCheckMediaForReadDirectory(callback, has_more, file_list); 502 return; 503 } 504 505 base::PostTaskAndReplyWithResult( 506 task_runner, 507 FROM_HERE, 508 base::Bind(&MediaPathFilterWrapper::FilterMediaEntries, 509 media_path_filter_wrapper_, 510 file_list), 511 base::Bind(&OnDidCheckMediaForReadDirectory, callback, has_more)); 512} 513 514bool DeviceMediaAsyncFileUtil::validate_media_files() const { 515 return media_path_filter_wrapper_.get() != NULL; 516} 517