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