root/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetStorageInfoOnUIThread
  2. GetFileObjectIdFromPathOnBlockingPoolThread
  3. CreateFileEnumeratorOnBlockingPoolThread
  4. OpenDeviceOnBlockingPoolThread
  5. GetFileInfoOnBlockingPoolThread
  6. ReadDirectoryOnBlockingPoolThread
  7. GetFileStreamOnBlockingPoolThread
  8. WriteDataChunkIntoSnapshotFileOnBlockingPoolThread
  9. DeletePortableDeviceOnBlockingPoolThread
  10. OnGetStorageInfoCreateDelegate
  11. CreateMTPDeviceAsyncDelegate
  12. storage_object_id
  13. reply
  14. weak_ptr_factory_
  15. GetFileInfo
  16. ReadDirectory
  17. CreateSnapshotFile
  18. IsStreaming
  19. ReadBytes
  20. CancelPendingTasksAndDeleteDelegate
  21. EnsureInitAndRunTask
  22. WriteDataChunkIntoSnapshotFile
  23. ProcessNextPendingRequest
  24. OnInitCompleted
  25. OnGetFileInfo
  26. OnDidReadDirectory
  27. OnGetFileStream
  28. OnWroteDataChunkIntoSnapshotFile

// 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.
//
// MTPDeviceDelegateImplWin implementation.

#include "chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h"

#include <portabledevice.h>

#include <vector>

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
#include "chrome/browser/media_galleries/win/mtp_device_object_entry.h"
#include "chrome/browser/media_galleries/win/mtp_device_object_enumerator.h"
#include "chrome/browser/media_galleries/win/mtp_device_operations_util.h"
#include "chrome/browser/media_galleries/win/portable_device_map_service.h"
#include "chrome/browser/media_galleries/win/snapshot_file_details.h"
#include "components/storage_monitor/storage_monitor.h"
#include "content/public/browser/browser_thread.h"
#include "webkit/common/fileapi/file_system_util.h"

namespace {

// Gets the details of the MTP partition storage specified by the
// |storage_path| on the UI thread. Returns true if the storage details are
// valid and returns false otherwise.
bool GetStorageInfoOnUIThread(const base::string16& storage_path,
                              base::string16* pnp_device_id,
                              base::string16* storage_object_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(!storage_path.empty());
  DCHECK(pnp_device_id);
  DCHECK(storage_object_id);
  base::string16 storage_device_id;
  base::RemoveChars(storage_path, L"\\\\", &storage_device_id);
  DCHECK(!storage_device_id.empty());
  // TODO(gbillock): Take the StorageMonitor as an argument.
  storage_monitor::StorageMonitor* monitor =
      storage_monitor::StorageMonitor::GetInstance();
  DCHECK(monitor);
  return monitor->GetMTPStorageInfoFromDeviceId(
      base::UTF16ToUTF8(storage_device_id), pnp_device_id, storage_object_id);
}

// Returns the object id of the file object specified by the |file_path|,
// e.g. if the |file_path| is "\\MTP:StorageSerial:SID-{1001,,192}:125\DCIM"
// and |device_info.registered_device_path_| is
// "\\MTP:StorageSerial:SID-{1001,,192}:125", this function returns the
// identifier of the "DCIM" folder object.
//
// Returns an empty string if the device is detached while the request is in
// progress or when the |file_path| is invalid.
base::string16 GetFileObjectIdFromPathOnBlockingPoolThread(
    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
    const base::FilePath& file_path) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(!file_path.empty());
  IPortableDevice* device =
      PortableDeviceMapService::GetInstance()->GetPortableDevice(
          device_info.registered_device_path);
  if (!device)
    return base::string16();

  if (device_info.registered_device_path == file_path.value())
    return device_info.storage_object_id;

  base::FilePath relative_path;
  if (!base::FilePath(device_info.registered_device_path).AppendRelativePath(
          file_path, &relative_path))
    return base::string16();

  std::vector<base::string16> path_components;
  relative_path.GetComponents(&path_components);
  DCHECK(!path_components.empty());
  base::string16 parent_id(device_info.storage_object_id);
  base::string16 file_object_id;
  for (size_t i = 0; i < path_components.size(); ++i) {
    file_object_id =
        media_transfer_protocol::GetObjectIdFromName(device, parent_id,
                                                     path_components[i]);
    if (file_object_id.empty())
      break;
    parent_id = file_object_id;
  }
  return file_object_id;
}

// Returns a pointer to a new instance of AbstractFileEnumerator for the given
// |root| directory. Called on a blocking pool thread.
scoped_ptr<MTPDeviceObjectEnumerator>
CreateFileEnumeratorOnBlockingPoolThread(
    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
    const base::FilePath& root) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(!device_info.registered_device_path.empty());
  DCHECK(!root.empty());
  IPortableDevice* device =
      PortableDeviceMapService::GetInstance()->GetPortableDevice(
          device_info.registered_device_path);
  if (!device)
    return scoped_ptr<MTPDeviceObjectEnumerator>();

  base::string16 object_id =
      GetFileObjectIdFromPathOnBlockingPoolThread(device_info, root);
  if (object_id.empty())
    return scoped_ptr<MTPDeviceObjectEnumerator>();

  MTPDeviceObjectEntries entries;
  if (!media_transfer_protocol::GetDirectoryEntries(device, object_id,
                                                    &entries) ||
      entries.empty())
    return scoped_ptr<MTPDeviceObjectEnumerator>();

  return scoped_ptr<MTPDeviceObjectEnumerator>(
      new MTPDeviceObjectEnumerator(entries));
}

// Opens the device for communication on a blocking pool thread.
// |pnp_device_id| specifies the PnP device id.
// |registered_device_path| specifies the registered file system root path for
// the given device.
bool OpenDeviceOnBlockingPoolThread(
    const base::string16& pnp_device_id,
    const base::string16& registered_device_path) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(!pnp_device_id.empty());
  DCHECK(!registered_device_path.empty());
  base::win::ScopedComPtr<IPortableDevice> device =
      media_transfer_protocol::OpenDevice(pnp_device_id);
  bool init_succeeded = device.get() != NULL;
  if (init_succeeded) {
    PortableDeviceMapService::GetInstance()->AddPortableDevice(
        registered_device_path, device.get());
  }
  return init_succeeded;
}

// Gets the |file_path| details from the MTP device specified by the
// |device_info.registered_device_path|. On success, |error| is set to
// base::File::FILE_OK and fills in |file_info|. On failure, |error| is set
// to corresponding platform file error and |file_info| is not set.
base::File::Error GetFileInfoOnBlockingPoolThread(
    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
    const base::FilePath& file_path,
    base::File::Info* file_info) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(!device_info.registered_device_path.empty());
  DCHECK(!file_path.empty());
  DCHECK(file_info);
  IPortableDevice* device =
      PortableDeviceMapService::GetInstance()->GetPortableDevice(
          device_info.registered_device_path);
  if (!device)
    return base::File::FILE_ERROR_FAILED;

  base::string16 object_id =
      GetFileObjectIdFromPathOnBlockingPoolThread(device_info, file_path);
  if (object_id.empty())
    return base::File::FILE_ERROR_FAILED;
  return media_transfer_protocol::GetFileEntryInfo(device, object_id,
                                                   file_info);
}

// Reads the |root| directory file entries on a blocking pool thread. On
// success, |error| is set to base::File::FILE_OK and |entries| contains the
// directory file entries. On failure, |error| is set to platform file error
// and |entries| is not set.
base::File::Error ReadDirectoryOnBlockingPoolThread(
    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
    const base::FilePath& root,
    fileapi::AsyncFileUtil::EntryList* entries) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(!root.empty());
  DCHECK(entries);
  base::File::Info file_info;
  base::File::Error error = GetFileInfoOnBlockingPoolThread(device_info, root,
                                                            &file_info);
  if (error != base::File::FILE_OK)
    return error;

  if (!file_info.is_directory)
    return base::File::FILE_ERROR_NOT_A_DIRECTORY;

  base::FilePath current;
  scoped_ptr<MTPDeviceObjectEnumerator> file_enum =
      CreateFileEnumeratorOnBlockingPoolThread(device_info, root);
  if (!file_enum)
    return error;

  while (!(current = file_enum->Next()).empty()) {
    fileapi::DirectoryEntry entry;
    entry.is_directory = file_enum->IsDirectory();
    entry.name = fileapi::VirtualPath::BaseName(current).value();
    entry.size = file_enum->Size();
    entry.last_modified_time = file_enum->LastModifiedTime();
    entries->push_back(entry);
  }
  return error;
}

// Gets the device file stream object on a blocking pool thread.
// |device_info| contains the device storage partition details.
// On success, returns base::File::FILE_OK and file stream details are set in
// |file_details|. On failure, returns a platform file error and file stream
// details are not set in |file_details|.
base::File::Error GetFileStreamOnBlockingPoolThread(
    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
    SnapshotFileDetails* file_details) {
  base::ThreadRestrictions::AssertIOAllowed();
  DCHECK(file_details);
  DCHECK(!file_details->request_info().device_file_path.empty());
  DCHECK(!file_details->request_info().snapshot_file_path.empty());
  IPortableDevice* device =
      PortableDeviceMapService::GetInstance()->GetPortableDevice(
          device_info.registered_device_path);
  if (!device)
    return base::File::FILE_ERROR_FAILED;

  base::string16 file_object_id =
      GetFileObjectIdFromPathOnBlockingPoolThread(
          device_info, file_details->request_info().device_file_path);
  if (file_object_id.empty())
    return base::File::FILE_ERROR_FAILED;

  base::File::Info file_info;
  base::File::Error error =
      GetFileInfoOnBlockingPoolThread(
          device_info,
          file_details->request_info().device_file_path,
          &file_info);
  if (error != base::File::FILE_OK)
    return error;

  DWORD optimal_transfer_size = 0;
  base::win::ScopedComPtr<IStream> file_stream;
  if (file_info.size > 0) {
    HRESULT hr = media_transfer_protocol::GetFileStreamForObject(
        device,
        file_object_id,
        file_stream.Receive(),
        &optimal_transfer_size);
    if (hr != S_OK)
      return base::File::FILE_ERROR_FAILED;
  }

  // LocalFileStreamReader is used to read the contents of the snapshot file.
  // Snapshot file modification time does not match the last modified time
  // of the original media file. Therefore, set the last modified time to null
  // in order to avoid the verification in LocalFileStreamReader.
  //
  // Users will use HTML5 FileSystem Entry getMetadata() interface to get the
  // actual last modified time of the media file.
  file_info.last_modified = base::Time();

  DCHECK(file_info.size == 0 || optimal_transfer_size > 0U);
  file_details->set_file_info(file_info);
  file_details->set_device_file_stream(file_stream);
  file_details->set_optimal_transfer_size(optimal_transfer_size);
  return error;
}

// Copies the data chunk from device file to the snapshot file based on the
// parameters specified by |file_details|.
// Returns the total number of bytes written to the snapshot file for non-empty
// files, or 0 on failure. For empty files, just return 0.
DWORD WriteDataChunkIntoSnapshotFileOnBlockingPoolThread(
    const SnapshotFileDetails& file_details) {
  base::ThreadRestrictions::AssertIOAllowed();
  if (file_details.file_info().size == 0)
    return 0;
  return media_transfer_protocol::CopyDataChunkToLocalFile(
      file_details.device_file_stream(),
      file_details.request_info().snapshot_file_path,
      file_details.optimal_transfer_size());
}

void DeletePortableDeviceOnBlockingPoolThread(
    const base::string16& registered_device_path) {
  base::ThreadRestrictions::AssertIOAllowed();
  PortableDeviceMapService::GetInstance()->RemovePortableDevice(
      registered_device_path);
}

}  // namespace

// Used by CreateMTPDeviceAsyncDelegate() to create the MTP device
// delegate on the IO thread.
void OnGetStorageInfoCreateDelegate(
    const base::string16& device_location,
    const CreateMTPDeviceAsyncDelegateCallback& callback,
    base::string16* pnp_device_id,
    base::string16* storage_object_id,
    bool succeeded) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(pnp_device_id);
  DCHECK(storage_object_id);
  if (!succeeded)
    return;
  callback.Run(new MTPDeviceDelegateImplWin(device_location,
                                            *pnp_device_id,
                                            *storage_object_id));
}

void CreateMTPDeviceAsyncDelegate(
    const base::string16& device_location,
    const CreateMTPDeviceAsyncDelegateCallback& callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!device_location.empty());
  base::string16* pnp_device_id = new base::string16;
  base::string16* storage_object_id = new base::string16;
  content::BrowserThread::PostTaskAndReplyWithResult<bool>(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(&GetStorageInfoOnUIThread,
                 device_location,
                 base::Unretained(pnp_device_id),
                 base::Unretained(storage_object_id)),
      base::Bind(&OnGetStorageInfoCreateDelegate,
                 device_location,
                 callback,
                 base::Owned(pnp_device_id),
                 base::Owned(storage_object_id)));
}

// MTPDeviceDelegateImplWin ---------------------------------------------------

MTPDeviceDelegateImplWin::StorageDeviceInfo::StorageDeviceInfo(
    const base::string16& pnp_device_id,
    const base::string16& registered_device_path,
    const base::string16& storage_object_id)
    : pnp_device_id(pnp_device_id),
      registered_device_path(registered_device_path),
      storage_object_id(storage_object_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}

MTPDeviceDelegateImplWin::PendingTaskInfo::PendingTaskInfo(
    const tracked_objects::Location& location,
    const base::Callback<base::File::Error(void)>& task,
    const base::Callback<void(base::File::Error)>& reply)
    : location(location),
      task(task),
      reply(reply) {
}

MTPDeviceDelegateImplWin::MTPDeviceDelegateImplWin(
    const base::string16& registered_device_path,
    const base::string16& pnp_device_id,
    const base::string16& storage_object_id)
    : storage_device_info_(pnp_device_id, registered_device_path,
                           storage_object_id),
      init_state_(UNINITIALIZED),
      media_task_runner_(MediaFileSystemBackend::MediaTaskRunner()),
      task_in_progress_(false),
      weak_ptr_factory_(this) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!registered_device_path.empty());
  DCHECK(!pnp_device_id.empty());
  DCHECK(!storage_object_id.empty());
  DCHECK(media_task_runner_.get());
}

MTPDeviceDelegateImplWin::~MTPDeviceDelegateImplWin() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}

void MTPDeviceDelegateImplWin::GetFileInfo(
    const base::FilePath& file_path,
    const GetFileInfoSuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!file_path.empty());
  base::File::Info* file_info = new base::File::Info;
  EnsureInitAndRunTask(
      PendingTaskInfo(FROM_HERE,
                      base::Bind(&GetFileInfoOnBlockingPoolThread,
                                 storage_device_info_,
                                 file_path,
                                 base::Unretained(file_info)),
                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileInfo,
                                 weak_ptr_factory_.GetWeakPtr(),
                                 success_callback,
                                 error_callback,
                                 base::Owned(file_info))));
}

void MTPDeviceDelegateImplWin::ReadDirectory(
    const base::FilePath& root,
    const ReadDirectorySuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!root.empty());
  fileapi::AsyncFileUtil::EntryList* entries =
      new fileapi::AsyncFileUtil::EntryList;
  EnsureInitAndRunTask(
      PendingTaskInfo(FROM_HERE,
                      base::Bind(&ReadDirectoryOnBlockingPoolThread,
                                 storage_device_info_,
                                 root,
                                 base::Unretained(entries)),
                      base::Bind(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
                                 weak_ptr_factory_.GetWeakPtr(),
                                 success_callback,
                                 error_callback,
                                 base::Owned(entries))));
}

void MTPDeviceDelegateImplWin::CreateSnapshotFile(
    const base::FilePath& device_file_path,
    const base::FilePath& snapshot_file_path,
    const CreateSnapshotFileSuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!device_file_path.empty());
  DCHECK(!snapshot_file_path.empty());
  scoped_ptr<SnapshotFileDetails> file_details(
      new SnapshotFileDetails(SnapshotRequestInfo(device_file_path,
                                                  snapshot_file_path,
                                                  success_callback,
                                                  error_callback)));
  // Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
  // it is owned by |file_details| in the reply callback.
  EnsureInitAndRunTask(
      PendingTaskInfo(FROM_HERE,
                      base::Bind(&GetFileStreamOnBlockingPoolThread,
                                 storage_device_info_,
                                 file_details.get()),
                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileStream,
                                 weak_ptr_factory_.GetWeakPtr(),
                                 base::Passed(&file_details))));
}

bool MTPDeviceDelegateImplWin::IsStreaming() {
  return false;
}

void MTPDeviceDelegateImplWin::ReadBytes(
    const base::FilePath& device_file_path,
    net::IOBuffer* buf, int64 offset, int buf_len,
    const ReadBytesSuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  NOTREACHED();
}

void MTPDeviceDelegateImplWin::CancelPendingTasksAndDeleteDelegate() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  PortableDeviceMapService::GetInstance()->MarkPortableDeviceForDeletion(
      storage_device_info_.registered_device_path);
  media_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&DeletePortableDeviceOnBlockingPoolThread,
                 storage_device_info_.registered_device_path));
  while (!pending_tasks_.empty())
    pending_tasks_.pop();
  delete this;
}

void MTPDeviceDelegateImplWin::EnsureInitAndRunTask(
    const PendingTaskInfo& task_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  if ((init_state_ == INITIALIZED) && !task_in_progress_) {
    DCHECK(pending_tasks_.empty());
    DCHECK(!current_snapshot_details_.get());
    base::PostTaskAndReplyWithResult(media_task_runner_,
                                     task_info.location,
                                     task_info.task,
                                     task_info.reply);
    task_in_progress_ = true;
    return;
  }

  pending_tasks_.push(task_info);
  if (init_state_ == UNINITIALIZED) {
    init_state_ = PENDING_INIT;
    base::PostTaskAndReplyWithResult(
        media_task_runner_,
        FROM_HERE,
        base::Bind(&OpenDeviceOnBlockingPoolThread,
                   storage_device_info_.pnp_device_id,
                   storage_device_info_.registered_device_path),
        base::Bind(&MTPDeviceDelegateImplWin::OnInitCompleted,
                   weak_ptr_factory_.GetWeakPtr()));
    task_in_progress_ = true;
  }
}

void MTPDeviceDelegateImplWin::WriteDataChunkIntoSnapshotFile() {
  DCHECK(current_snapshot_details_.get());
  base::PostTaskAndReplyWithResult(
      media_task_runner_,
      FROM_HERE,
      base::Bind(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
                 *current_snapshot_details_),
      base::Bind(&MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
                 weak_ptr_factory_.GetWeakPtr(),
                 current_snapshot_details_->request_info().snapshot_file_path));
}

void MTPDeviceDelegateImplWin::ProcessNextPendingRequest() {
  DCHECK(!task_in_progress_);
  if (pending_tasks_.empty())
    return;
  const PendingTaskInfo& task_info = pending_tasks_.front();
  task_in_progress_ = true;
  base::PostTaskAndReplyWithResult(media_task_runner_,
                                   task_info.location,
                                   task_info.task,
                                   task_info.reply);
  pending_tasks_.pop();
}

void MTPDeviceDelegateImplWin::OnInitCompleted(bool succeeded) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
  task_in_progress_ = false;
  ProcessNextPendingRequest();
}

void MTPDeviceDelegateImplWin::OnGetFileInfo(
    const GetFileInfoSuccessCallback& success_callback,
    const ErrorCallback& error_callback,
    base::File::Info* file_info,
    base::File::Error error) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(file_info);
  if (error == base::File::FILE_OK)
    success_callback.Run(*file_info);
  else
    error_callback.Run(error);
  task_in_progress_ = false;
  ProcessNextPendingRequest();
}

void MTPDeviceDelegateImplWin::OnDidReadDirectory(
    const ReadDirectorySuccessCallback& success_callback,
    const ErrorCallback& error_callback,
    fileapi::AsyncFileUtil::EntryList* file_list,
    base::File::Error error) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(file_list);
  if (error == base::File::FILE_OK)
    success_callback.Run(*file_list, false /*no more entries*/);
  else
    error_callback.Run(error);
  task_in_progress_ = false;
  ProcessNextPendingRequest();
}

void MTPDeviceDelegateImplWin::OnGetFileStream(
    scoped_ptr<SnapshotFileDetails> file_details,
    base::File::Error error) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(file_details);
  DCHECK(!file_details->request_info().device_file_path.empty());
  DCHECK(!file_details->request_info().snapshot_file_path.empty());
  DCHECK(!current_snapshot_details_.get());
  if (error != base::File::FILE_OK) {
    file_details->request_info().error_callback.Run(error);
    task_in_progress_ = false;
    ProcessNextPendingRequest();
    return;
  }
  DCHECK(file_details->file_info().size == 0 ||
         file_details->device_file_stream());
  current_snapshot_details_.reset(file_details.release());
  WriteDataChunkIntoSnapshotFile();
}

void MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile(
    const base::FilePath& snapshot_file_path,
    DWORD bytes_written) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(!snapshot_file_path.empty());
  if (!current_snapshot_details_.get())
    return;
  DCHECK_EQ(
      current_snapshot_details_->request_info().snapshot_file_path.value(),
      snapshot_file_path.value());

  bool succeeded = false;
  bool should_continue = false;
  if (current_snapshot_details_->file_info().size > 0) {
    if (current_snapshot_details_->AddBytesWritten(bytes_written)) {
      if (current_snapshot_details_->IsSnapshotFileWriteComplete()) {
        succeeded = true;
      } else {
        should_continue = true;
      }
    }
  } else {
    // Handle empty files.
    DCHECK_EQ(0U, bytes_written);
    succeeded = true;
  }

  if (should_continue) {
    WriteDataChunkIntoSnapshotFile();
    return;
  }
  if (succeeded) {
    current_snapshot_details_->request_info().success_callback.Run(
        current_snapshot_details_->file_info(),
        current_snapshot_details_->request_info().snapshot_file_path);
  } else {
    current_snapshot_details_->request_info().error_callback.Run(
        base::File::FILE_ERROR_FAILED);
  }
  task_in_progress_ = false;
  current_snapshot_details_.reset();
  ProcessNextPendingRequest();
}

/* [<][>][^][v][top][bottom][index][help] */