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// MTPDeviceDelegateImplWin implementation.
6
7#include "chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h"
8
9#include <portabledevice.h>
10
11#include <vector>
12
13#include "base/bind.h"
14#include "base/files/file_path.h"
15#include "base/logging.h"
16#include "base/memory/ref_counted.h"
17#include "base/sequenced_task_runner.h"
18#include "base/stl_util.h"
19#include "base/strings/string_split.h"
20#include "base/strings/string_util.h"
21#include "base/strings/utf_string_conversions.h"
22#include "base/task_runner_util.h"
23#include "base/threading/thread_restrictions.h"
24#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
25#include "chrome/browser/media_galleries/win/mtp_device_object_entry.h"
26#include "chrome/browser/media_galleries/win/mtp_device_object_enumerator.h"
27#include "chrome/browser/media_galleries/win/mtp_device_operations_util.h"
28#include "chrome/browser/media_galleries/win/portable_device_map_service.h"
29#include "chrome/browser/media_galleries/win/snapshot_file_details.h"
30#include "chrome/browser/storage_monitor/storage_monitor.h"
31#include "content/public/browser/browser_thread.h"
32#include "webkit/common/fileapi/file_system_util.h"
33
34namespace {
35
36// Gets the details of the MTP partition storage specified by the
37// |storage_path| on the UI thread. Returns true if the storage details are
38// valid and returns false otherwise.
39bool GetStorageInfoOnUIThread(const base::string16& storage_path,
40                              base::string16* pnp_device_id,
41                              base::string16* storage_object_id) {
42  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
43  DCHECK(!storage_path.empty());
44  DCHECK(pnp_device_id);
45  DCHECK(storage_object_id);
46  base::string16 storage_device_id;
47  base::RemoveChars(storage_path, L"\\\\", &storage_device_id);
48  DCHECK(!storage_device_id.empty());
49  // TODO(gbillock): Take the StorageMonitor as an argument.
50  StorageMonitor* monitor = StorageMonitor::GetInstance();
51  DCHECK(monitor);
52  return monitor->GetMTPStorageInfoFromDeviceId(
53      UTF16ToUTF8(storage_device_id), pnp_device_id, storage_object_id);
54}
55
56// Returns the object id of the file object specified by the |file_path|,
57// e.g. if the |file_path| is "\\MTP:StorageSerial:SID-{1001,,192}:125\DCIM"
58// and |device_info.registered_device_path_| is
59// "\\MTP:StorageSerial:SID-{1001,,192}:125", this function returns the
60// identifier of the "DCIM" folder object.
61//
62// Returns an empty string if the device is detached while the request is in
63// progress or when the |file_path| is invalid.
64base::string16 GetFileObjectIdFromPathOnBlockingPoolThread(
65    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
66    const base::FilePath& file_path) {
67  base::ThreadRestrictions::AssertIOAllowed();
68  DCHECK(!file_path.empty());
69  IPortableDevice* device =
70      PortableDeviceMapService::GetInstance()->GetPortableDevice(
71          device_info.registered_device_path);
72  if (!device)
73    return base::string16();
74
75  if (device_info.registered_device_path == file_path.value())
76    return device_info.storage_object_id;
77
78  base::FilePath relative_path;
79  if (!base::FilePath(device_info.registered_device_path).AppendRelativePath(
80          file_path, &relative_path))
81    return base::string16();
82
83  std::vector<base::string16> path_components;
84  relative_path.GetComponents(&path_components);
85  DCHECK(!path_components.empty());
86  base::string16 parent_id(device_info.storage_object_id);
87  base::string16 file_object_id;
88  for (size_t i = 0; i < path_components.size(); ++i) {
89    file_object_id =
90        media_transfer_protocol::GetObjectIdFromName(device, parent_id,
91                                                     path_components[i]);
92    if (file_object_id.empty())
93      break;
94    parent_id = file_object_id;
95  }
96  return file_object_id;
97}
98
99// Returns a pointer to a new instance of AbstractFileEnumerator for the given
100// |root| directory. Called on a blocking pool thread.
101scoped_ptr<MTPDeviceObjectEnumerator>
102CreateFileEnumeratorOnBlockingPoolThread(
103    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
104    const base::FilePath& root) {
105  base::ThreadRestrictions::AssertIOAllowed();
106  DCHECK(!device_info.registered_device_path.empty());
107  DCHECK(!root.empty());
108  IPortableDevice* device =
109      PortableDeviceMapService::GetInstance()->GetPortableDevice(
110          device_info.registered_device_path);
111  if (!device)
112    return scoped_ptr<MTPDeviceObjectEnumerator>();
113
114  base::string16 object_id =
115      GetFileObjectIdFromPathOnBlockingPoolThread(device_info, root);
116  if (object_id.empty())
117    return scoped_ptr<MTPDeviceObjectEnumerator>();
118
119  MTPDeviceObjectEntries entries;
120  if (!media_transfer_protocol::GetDirectoryEntries(device, object_id,
121                                                    &entries) ||
122      entries.empty())
123    return scoped_ptr<MTPDeviceObjectEnumerator>();
124
125  return scoped_ptr<MTPDeviceObjectEnumerator>(
126      new MTPDeviceObjectEnumerator(entries));
127}
128
129// Opens the device for communication on a blocking pool thread.
130// |pnp_device_id| specifies the PnP device id.
131// |registered_device_path| specifies the registered file system root path for
132// the given device.
133bool OpenDeviceOnBlockingPoolThread(
134    const base::string16& pnp_device_id,
135    const base::string16& registered_device_path) {
136  base::ThreadRestrictions::AssertIOAllowed();
137  DCHECK(!pnp_device_id.empty());
138  DCHECK(!registered_device_path.empty());
139  base::win::ScopedComPtr<IPortableDevice> device =
140      media_transfer_protocol::OpenDevice(pnp_device_id);
141  bool init_succeeded = device.get() != NULL;
142  if (init_succeeded) {
143    PortableDeviceMapService::GetInstance()->AddPortableDevice(
144        registered_device_path, device.get());
145  }
146  return init_succeeded;
147}
148
149// Gets the |file_path| details from the MTP device specified by the
150// |device_info.registered_device_path|. On success, |error| is set to
151// base::PLATFORM_FILE_OK and fills in |file_info|. On failure, |error| is set
152// to corresponding platform file error and |file_info| is not set.
153base::PlatformFileError GetFileInfoOnBlockingPoolThread(
154    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
155    const base::FilePath& file_path,
156    base::PlatformFileInfo* file_info) {
157  base::ThreadRestrictions::AssertIOAllowed();
158  DCHECK(!device_info.registered_device_path.empty());
159  DCHECK(!file_path.empty());
160  DCHECK(file_info);
161  IPortableDevice* device =
162      PortableDeviceMapService::GetInstance()->GetPortableDevice(
163          device_info.registered_device_path);
164  if (!device)
165    return base::PLATFORM_FILE_ERROR_FAILED;
166
167  base::string16 object_id =
168      GetFileObjectIdFromPathOnBlockingPoolThread(device_info, file_path);
169  if (object_id.empty())
170    return base::PLATFORM_FILE_ERROR_FAILED;
171  return media_transfer_protocol::GetFileEntryInfo(device, object_id,
172                                                   file_info);
173}
174
175// Reads the |root| directory file entries on a blocking pool thread. On
176// success, |error| is set to base::PLATFORM_FILE_OK and |entries| contains the
177// directory file entries. On failure, |error| is set to platform file error
178// and |entries| is not set.
179base::PlatformFileError ReadDirectoryOnBlockingPoolThread(
180    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
181    const base::FilePath& root,
182    fileapi::AsyncFileUtil::EntryList* entries) {
183  base::ThreadRestrictions::AssertIOAllowed();
184  DCHECK(!root.empty());
185  DCHECK(entries);
186  base::PlatformFileInfo file_info;
187  base::PlatformFileError error = GetFileInfoOnBlockingPoolThread(device_info,
188                                                                  root,
189                                                                  &file_info);
190  if (error != base::PLATFORM_FILE_OK)
191    return error;
192
193  if (!file_info.is_directory)
194    return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
195
196  base::FilePath current;
197  scoped_ptr<MTPDeviceObjectEnumerator> file_enum =
198      CreateFileEnumeratorOnBlockingPoolThread(device_info, root);
199  if (!file_enum)
200    return error;
201
202  while (!(current = file_enum->Next()).empty()) {
203    fileapi::DirectoryEntry entry;
204    entry.is_directory = file_enum->IsDirectory();
205    entry.name = fileapi::VirtualPath::BaseName(current).value();
206    entry.size = file_enum->Size();
207    entry.last_modified_time = file_enum->LastModifiedTime();
208    entries->push_back(entry);
209  }
210  return error;
211}
212
213// Gets the device file stream object on a blocking pool thread.
214// |device_info| contains the device storage partition details.
215// On success, returns base::PLATFORM_FILE_OK and file stream details are set in
216// |file_details|. On failure, returns a platform file error and file stream
217// details are not set in |file_details|.
218base::PlatformFileError GetFileStreamOnBlockingPoolThread(
219    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
220    SnapshotFileDetails* file_details) {
221  base::ThreadRestrictions::AssertIOAllowed();
222  DCHECK(file_details);
223  DCHECK(!file_details->request_info().device_file_path.empty());
224  DCHECK(!file_details->request_info().snapshot_file_path.empty());
225  IPortableDevice* device =
226      PortableDeviceMapService::GetInstance()->GetPortableDevice(
227          device_info.registered_device_path);
228  if (!device)
229    return base::PLATFORM_FILE_ERROR_FAILED;
230
231  base::string16 file_object_id =
232      GetFileObjectIdFromPathOnBlockingPoolThread(
233          device_info, file_details->request_info().device_file_path);
234  if (file_object_id.empty())
235    return base::PLATFORM_FILE_ERROR_FAILED;
236
237  base::PlatformFileInfo file_info;
238  base::PlatformFileError error =
239      GetFileInfoOnBlockingPoolThread(
240          device_info,
241          file_details->request_info().device_file_path,
242          &file_info);
243  if (error != base::PLATFORM_FILE_OK)
244    return error;
245
246  DWORD optimal_transfer_size = 0;
247  base::win::ScopedComPtr<IStream> file_stream;
248  if (file_info.size > 0) {
249    HRESULT hr = media_transfer_protocol::GetFileStreamForObject(
250        device,
251        file_object_id,
252        file_stream.Receive(),
253        &optimal_transfer_size);
254    if (hr != S_OK)
255      return base::PLATFORM_FILE_ERROR_FAILED;
256  }
257
258  // LocalFileStreamReader is used to read the contents of the snapshot file.
259  // Snapshot file modification time does not match the last modified time
260  // of the original media file. Therefore, set the last modified time to null
261  // in order to avoid the verification in LocalFileStreamReader.
262  //
263  // Users will use HTML5 FileSystem Entry getMetadata() interface to get the
264  // actual last modified time of the media file.
265  file_info.last_modified = base::Time();
266
267  DCHECK(file_info.size == 0 || optimal_transfer_size > 0U);
268  file_details->set_file_info(file_info);
269  file_details->set_device_file_stream(file_stream);
270  file_details->set_optimal_transfer_size(optimal_transfer_size);
271  return error;
272}
273
274// Copies the data chunk from device file to the snapshot file based on the
275// parameters specified by |file_details|.
276// Returns the total number of bytes written to the snapshot file for non-empty
277// files, or 0 on failure. For empty files, just return 0.
278DWORD WriteDataChunkIntoSnapshotFileOnBlockingPoolThread(
279    const SnapshotFileDetails& file_details) {
280  base::ThreadRestrictions::AssertIOAllowed();
281  if (file_details.file_info().size == 0)
282    return 0;
283  return media_transfer_protocol::CopyDataChunkToLocalFile(
284      file_details.device_file_stream(),
285      file_details.request_info().snapshot_file_path,
286      file_details.optimal_transfer_size());
287}
288
289void DeletePortableDeviceOnBlockingPoolThread(
290    const base::string16& registered_device_path) {
291  base::ThreadRestrictions::AssertIOAllowed();
292  PortableDeviceMapService::GetInstance()->RemovePortableDevice(
293      registered_device_path);
294}
295
296}  // namespace
297
298// Used by CreateMTPDeviceAsyncDelegate() to create the MTP device
299// delegate on the IO thread.
300void OnGetStorageInfoCreateDelegate(
301    const base::string16& device_location,
302    const CreateMTPDeviceAsyncDelegateCallback& callback,
303    base::string16* pnp_device_id,
304    base::string16* storage_object_id,
305    bool succeeded) {
306  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
307  DCHECK(pnp_device_id);
308  DCHECK(storage_object_id);
309  if (!succeeded)
310    return;
311  callback.Run(new MTPDeviceDelegateImplWin(device_location,
312                                            *pnp_device_id,
313                                            *storage_object_id));
314}
315
316void CreateMTPDeviceAsyncDelegate(
317    const base::string16& device_location,
318    const CreateMTPDeviceAsyncDelegateCallback& callback) {
319  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
320  DCHECK(!device_location.empty());
321  base::string16* pnp_device_id = new base::string16;
322  base::string16* storage_object_id = new base::string16;
323  content::BrowserThread::PostTaskAndReplyWithResult<bool>(
324      content::BrowserThread::UI,
325      FROM_HERE,
326      base::Bind(&GetStorageInfoOnUIThread,
327                 device_location,
328                 base::Unretained(pnp_device_id),
329                 base::Unretained(storage_object_id)),
330      base::Bind(&OnGetStorageInfoCreateDelegate,
331                 device_location,
332                 callback,
333                 base::Owned(pnp_device_id),
334                 base::Owned(storage_object_id)));
335}
336
337// MTPDeviceDelegateImplWin ---------------------------------------------------
338
339MTPDeviceDelegateImplWin::StorageDeviceInfo::StorageDeviceInfo(
340    const base::string16& pnp_device_id,
341    const base::string16& registered_device_path,
342    const base::string16& storage_object_id)
343    : pnp_device_id(pnp_device_id),
344      registered_device_path(registered_device_path),
345      storage_object_id(storage_object_id) {
346  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
347}
348
349MTPDeviceDelegateImplWin::PendingTaskInfo::PendingTaskInfo(
350    const tracked_objects::Location& location,
351    const base::Callback<base::PlatformFileError(void)>& task,
352    const base::Callback<void(base::PlatformFileError)>& reply)
353    : location(location),
354      task(task),
355      reply(reply) {
356}
357
358MTPDeviceDelegateImplWin::MTPDeviceDelegateImplWin(
359    const base::string16& registered_device_path,
360    const base::string16& pnp_device_id,
361    const base::string16& storage_object_id)
362    : storage_device_info_(pnp_device_id, registered_device_path,
363                           storage_object_id),
364      init_state_(UNINITIALIZED),
365      media_task_runner_(MediaFileSystemBackend::MediaTaskRunner()),
366      task_in_progress_(false),
367      weak_ptr_factory_(this) {
368  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
369  DCHECK(!registered_device_path.empty());
370  DCHECK(!pnp_device_id.empty());
371  DCHECK(!storage_object_id.empty());
372  DCHECK(media_task_runner_.get());
373}
374
375MTPDeviceDelegateImplWin::~MTPDeviceDelegateImplWin() {
376  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
377}
378
379void MTPDeviceDelegateImplWin::GetFileInfo(
380    const base::FilePath& file_path,
381    const GetFileInfoSuccessCallback& success_callback,
382    const ErrorCallback& error_callback) {
383  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
384  DCHECK(!file_path.empty());
385  base::PlatformFileInfo* file_info = new base::PlatformFileInfo;
386  EnsureInitAndRunTask(
387      PendingTaskInfo(FROM_HERE,
388                      base::Bind(&GetFileInfoOnBlockingPoolThread,
389                                 storage_device_info_,
390                                 file_path,
391                                 base::Unretained(file_info)),
392                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileInfo,
393                                 weak_ptr_factory_.GetWeakPtr(),
394                                 success_callback,
395                                 error_callback,
396                                 base::Owned(file_info))));
397}
398
399void MTPDeviceDelegateImplWin::ReadDirectory(
400    const base::FilePath& root,
401    const ReadDirectorySuccessCallback& success_callback,
402    const ErrorCallback& error_callback) {
403  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
404  DCHECK(!root.empty());
405  fileapi::AsyncFileUtil::EntryList* entries =
406      new fileapi::AsyncFileUtil::EntryList;
407  EnsureInitAndRunTask(
408      PendingTaskInfo(FROM_HERE,
409                      base::Bind(&ReadDirectoryOnBlockingPoolThread,
410                                 storage_device_info_,
411                                 root,
412                                 base::Unretained(entries)),
413                      base::Bind(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
414                                 weak_ptr_factory_.GetWeakPtr(),
415                                 success_callback,
416                                 error_callback,
417                                 base::Owned(entries))));
418}
419
420void MTPDeviceDelegateImplWin::CreateSnapshotFile(
421    const base::FilePath& device_file_path,
422    const base::FilePath& snapshot_file_path,
423    const CreateSnapshotFileSuccessCallback& success_callback,
424    const ErrorCallback& error_callback) {
425  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
426  DCHECK(!device_file_path.empty());
427  DCHECK(!snapshot_file_path.empty());
428  scoped_ptr<SnapshotFileDetails> file_details(
429      new SnapshotFileDetails(SnapshotRequestInfo(device_file_path,
430                                                  snapshot_file_path,
431                                                  success_callback,
432                                                  error_callback)));
433  // Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
434  // it is owned by |file_details| in the reply callback.
435  EnsureInitAndRunTask(
436      PendingTaskInfo(FROM_HERE,
437                      base::Bind(&GetFileStreamOnBlockingPoolThread,
438                                 storage_device_info_,
439                                 file_details.get()),
440                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileStream,
441                                 weak_ptr_factory_.GetWeakPtr(),
442                                 base::Passed(&file_details))));
443}
444
445void MTPDeviceDelegateImplWin::CancelPendingTasksAndDeleteDelegate() {
446  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
447  PortableDeviceMapService::GetInstance()->MarkPortableDeviceForDeletion(
448      storage_device_info_.registered_device_path);
449  media_task_runner_->PostTask(
450      FROM_HERE,
451      base::Bind(&DeletePortableDeviceOnBlockingPoolThread,
452                 storage_device_info_.registered_device_path));
453  while (!pending_tasks_.empty())
454    pending_tasks_.pop();
455  delete this;
456}
457
458void MTPDeviceDelegateImplWin::EnsureInitAndRunTask(
459    const PendingTaskInfo& task_info) {
460  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
461  if ((init_state_ == INITIALIZED) && !task_in_progress_) {
462    DCHECK(pending_tasks_.empty());
463    DCHECK(!current_snapshot_details_.get());
464    base::PostTaskAndReplyWithResult(media_task_runner_,
465                                     task_info.location,
466                                     task_info.task,
467                                     task_info.reply);
468    task_in_progress_ = true;
469    return;
470  }
471
472  pending_tasks_.push(task_info);
473  if (init_state_ == UNINITIALIZED) {
474    init_state_ = PENDING_INIT;
475    base::PostTaskAndReplyWithResult(
476        media_task_runner_,
477        FROM_HERE,
478        base::Bind(&OpenDeviceOnBlockingPoolThread,
479                   storage_device_info_.pnp_device_id,
480                   storage_device_info_.registered_device_path),
481        base::Bind(&MTPDeviceDelegateImplWin::OnInitCompleted,
482                   weak_ptr_factory_.GetWeakPtr()));
483    task_in_progress_ = true;
484  }
485}
486
487void MTPDeviceDelegateImplWin::WriteDataChunkIntoSnapshotFile() {
488  DCHECK(current_snapshot_details_.get());
489  base::PostTaskAndReplyWithResult(
490      media_task_runner_,
491      FROM_HERE,
492      base::Bind(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
493                 *current_snapshot_details_),
494      base::Bind(&MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
495                 weak_ptr_factory_.GetWeakPtr(),
496                 current_snapshot_details_->request_info().snapshot_file_path));
497}
498
499void MTPDeviceDelegateImplWin::ProcessNextPendingRequest() {
500  DCHECK(!task_in_progress_);
501  if (pending_tasks_.empty())
502    return;
503  const PendingTaskInfo& task_info = pending_tasks_.front();
504  task_in_progress_ = true;
505  base::PostTaskAndReplyWithResult(media_task_runner_,
506                                   task_info.location,
507                                   task_info.task,
508                                   task_info.reply);
509  pending_tasks_.pop();
510}
511
512void MTPDeviceDelegateImplWin::OnInitCompleted(bool succeeded) {
513  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
514  init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
515  task_in_progress_ = false;
516  ProcessNextPendingRequest();
517}
518
519void MTPDeviceDelegateImplWin::OnGetFileInfo(
520    const GetFileInfoSuccessCallback& success_callback,
521    const ErrorCallback& error_callback,
522    base::PlatformFileInfo* file_info,
523    base::PlatformFileError error) {
524  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
525  DCHECK(file_info);
526  if (error == base::PLATFORM_FILE_OK)
527    success_callback.Run(*file_info);
528  else
529    error_callback.Run(error);
530  task_in_progress_ = false;
531  ProcessNextPendingRequest();
532}
533
534void MTPDeviceDelegateImplWin::OnDidReadDirectory(
535    const ReadDirectorySuccessCallback& success_callback,
536    const ErrorCallback& error_callback,
537    fileapi::AsyncFileUtil::EntryList* file_list,
538    base::PlatformFileError error) {
539  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
540  DCHECK(file_list);
541  if (error == base::PLATFORM_FILE_OK)
542    success_callback.Run(*file_list, false /*no more entries*/);
543  else
544    error_callback.Run(error);
545  task_in_progress_ = false;
546  ProcessNextPendingRequest();
547}
548
549void MTPDeviceDelegateImplWin::OnGetFileStream(
550    scoped_ptr<SnapshotFileDetails> file_details,
551    base::PlatformFileError error) {
552  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
553  DCHECK(file_details);
554  DCHECK(!file_details->request_info().device_file_path.empty());
555  DCHECK(!file_details->request_info().snapshot_file_path.empty());
556  DCHECK(!current_snapshot_details_.get());
557  if (error != base::PLATFORM_FILE_OK) {
558    file_details->request_info().error_callback.Run(error);
559    task_in_progress_ = false;
560    ProcessNextPendingRequest();
561    return;
562  }
563  DCHECK(file_details->file_info().size == 0 ||
564         file_details->device_file_stream());
565  current_snapshot_details_.reset(file_details.release());
566  WriteDataChunkIntoSnapshotFile();
567}
568
569void MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile(
570    const base::FilePath& snapshot_file_path,
571    DWORD bytes_written) {
572  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
573  DCHECK(!snapshot_file_path.empty());
574  if (!current_snapshot_details_.get())
575    return;
576  DCHECK_EQ(
577      current_snapshot_details_->request_info().snapshot_file_path.value(),
578      snapshot_file_path.value());
579
580  bool succeeded = false;
581  bool should_continue = false;
582  if (current_snapshot_details_->file_info().size > 0) {
583    if (current_snapshot_details_->AddBytesWritten(bytes_written)) {
584      if (current_snapshot_details_->IsSnapshotFileWriteComplete()) {
585        succeeded = true;
586      } else {
587        should_continue = true;
588      }
589    }
590  } else {
591    // Handle empty files.
592    DCHECK_EQ(0U, bytes_written);
593    succeeded = true;
594  }
595
596  if (should_continue) {
597    WriteDataChunkIntoSnapshotFile();
598    return;
599  }
600  if (succeeded) {
601    current_snapshot_details_->request_info().success_callback.Run(
602        current_snapshot_details_->file_info(),
603        current_snapshot_details_->request_info().snapshot_file_path);
604  } else {
605    current_snapshot_details_->request_info().error_callback.Run(
606        base::PLATFORM_FILE_ERROR_FAILED);
607  }
608  task_in_progress_ = false;
609  current_snapshot_details_.reset();
610  ProcessNextPendingRequest();
611}
612