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