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 chrome {
35
36namespace {
37
38// Gets the details of the MTP partition storage specified by the
39// |storage_path| on the UI thread. Returns true if the storage details are
40// valid and returns false otherwise.
41bool GetStorageInfoOnUIThread(const string16& storage_path,
42                              string16* pnp_device_id,
43                              string16* storage_object_id) {
44  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
45  DCHECK(!storage_path.empty());
46  DCHECK(pnp_device_id);
47  DCHECK(storage_object_id);
48  string16 storage_device_id;
49  RemoveChars(storage_path, L"\\\\", &storage_device_id);
50  DCHECK(!storage_device_id.empty());
51  // TODO(gbillock): Take the StorageMonitor as an argument.
52  StorageMonitor* monitor = StorageMonitor::GetInstance();
53  DCHECK(monitor);
54  return monitor->GetMTPStorageInfoFromDeviceId(
55      UTF16ToUTF8(storage_device_id), pnp_device_id, storage_object_id);
56}
57
58// Returns the object id of the file object specified by the |file_path|,
59// e.g. if the |file_path| is "\\MTP:StorageSerial:SID-{1001,,192}:125\DCIM"
60// and |device_info.registered_device_path_| is
61// "\\MTP:StorageSerial:SID-{1001,,192}:125", this function returns the
62// identifier of the "DCIM" folder object.
63//
64// Returns an empty string if the device is detached while the request is in
65// progress or when the |file_path| is invalid.
66string16 GetFileObjectIdFromPathOnBlockingPoolThread(
67    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
68    const base::FilePath& file_path) {
69  base::ThreadRestrictions::AssertIOAllowed();
70  DCHECK(!file_path.empty());
71  IPortableDevice* device =
72      PortableDeviceMapService::GetInstance()->GetPortableDevice(
73          device_info.registered_device_path);
74  if (!device)
75    return string16();
76
77  if (device_info.registered_device_path == file_path.value())
78    return device_info.storage_object_id;
79
80  base::FilePath relative_path;
81  if (!base::FilePath(device_info.registered_device_path).AppendRelativePath(
82          file_path, &relative_path))
83    return string16();
84
85  std::vector<string16> path_components;
86  relative_path.GetComponents(&path_components);
87  DCHECK(!path_components.empty());
88  string16 parent_id(device_info.storage_object_id);
89  string16 file_object_id;
90  for (size_t i = 0; i < path_components.size(); ++i) {
91    file_object_id =
92        media_transfer_protocol::GetObjectIdFromName(device, parent_id,
93                                                     path_components[i]);
94    if (file_object_id.empty())
95      break;
96    parent_id = file_object_id;
97  }
98  return file_object_id;
99}
100
101// Returns a pointer to a new instance of AbstractFileEnumerator for the given
102// |root| directory. Called on a blocking pool thread.
103scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator>
104CreateFileEnumeratorOnBlockingPoolThread(
105    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
106    const base::FilePath& root) {
107  base::ThreadRestrictions::AssertIOAllowed();
108  DCHECK(!device_info.registered_device_path.empty());
109  DCHECK(!root.empty());
110  scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator>
111      file_enumerator(new fileapi::FileSystemFileUtil::EmptyFileEnumerator());
112  IPortableDevice* device =
113      PortableDeviceMapService::GetInstance()->GetPortableDevice(
114          device_info.registered_device_path);
115  if (!device)
116    return file_enumerator.Pass();
117
118  string16 object_id = GetFileObjectIdFromPathOnBlockingPoolThread(device_info,
119                                                                   root);
120  if (object_id.empty())
121    return file_enumerator.Pass();
122
123  MTPDeviceObjectEntries entries;
124  if (!media_transfer_protocol::GetDirectoryEntries(device, object_id,
125                                                    &entries) ||
126      entries.empty())
127    return file_enumerator.Pass();
128
129  file_enumerator.reset(new MTPDeviceObjectEnumerator(entries));
130  return file_enumerator.Pass();
131}
132
133// Opens the device for communication on a blocking pool thread.
134// |pnp_device_id| specifies the PnP device id.
135// |registered_device_path| specifies the registered file system root path for
136// the given device.
137bool OpenDeviceOnBlockingPoolThread(const string16& pnp_device_id,
138                                    const string16& registered_device_path) {
139  base::ThreadRestrictions::AssertIOAllowed();
140  DCHECK(!pnp_device_id.empty());
141  DCHECK(!registered_device_path.empty());
142  base::win::ScopedComPtr<IPortableDevice> device =
143      media_transfer_protocol::OpenDevice(pnp_device_id);
144  bool init_succeeded = device.get() != NULL;
145  if (init_succeeded) {
146    PortableDeviceMapService::GetInstance()->AddPortableDevice(
147        registered_device_path, device.get());
148  }
149  return init_succeeded;
150}
151
152// Gets the |file_path| details from the MTP device specified by the
153// |device_info.registered_device_path|. On success, |error| is set to
154// base::PLATFORM_FILE_OK and fills in |file_info|. On failure, |error| is set
155// to corresponding platform file error and |file_info| is not set.
156base::PlatformFileError GetFileInfoOnBlockingPoolThread(
157    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
158    const base::FilePath& file_path,
159    base::PlatformFileInfo* file_info) {
160  base::ThreadRestrictions::AssertIOAllowed();
161  DCHECK(!device_info.registered_device_path.empty());
162  DCHECK(!file_path.empty());
163  DCHECK(file_info);
164  IPortableDevice* device =
165      PortableDeviceMapService::GetInstance()->GetPortableDevice(
166          device_info.registered_device_path);
167  if (!device)
168    return base::PLATFORM_FILE_ERROR_FAILED;
169
170  string16 object_id = GetFileObjectIdFromPathOnBlockingPoolThread(device_info,
171                                                                   file_path);
172  if (object_id.empty())
173    return base::PLATFORM_FILE_ERROR_FAILED;
174  return media_transfer_protocol::GetFileEntryInfo(device, object_id,
175                                                   file_info);
176}
177
178// Reads the |root| directory file entries on a blocking pool thread. On
179// success, |error| is set to base::PLATFORM_FILE_OK and |entries| contains the
180// directory file entries. On failure, |error| is set to platform file error
181// and |entries| is not set.
182base::PlatformFileError ReadDirectoryOnBlockingPoolThread(
183    const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
184    const base::FilePath& root,
185    fileapi::AsyncFileUtil::EntryList* entries) {
186  base::ThreadRestrictions::AssertIOAllowed();
187  DCHECK(!root.empty());
188  DCHECK(entries);
189  base::PlatformFileInfo file_info;
190  base::PlatformFileError error = GetFileInfoOnBlockingPoolThread(device_info,
191                                                                  root,
192                                                                  &file_info);
193  if (error != base::PLATFORM_FILE_OK)
194    return error;
195
196  if (!file_info.is_directory)
197    return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
198
199  base::FilePath current;
200  scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> file_enum =
201      CreateFileEnumeratorOnBlockingPoolThread(device_info, root);
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  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 string16& registered_device_path) {
291  base::ThreadRestrictions::AssertIOAllowed();
292  PortableDeviceMapService::GetInstance()->RemovePortableDevice(
293      registered_device_path);
294}
295
296
297}  // namespace
298
299// Used by CreateMTPDeviceAsyncDelegate() to create the MTP device
300// delegate on the IO thread.
301void OnGetStorageInfoCreateDelegate(
302    const string16& device_location,
303    const CreateMTPDeviceAsyncDelegateCallback& callback,
304    string16* pnp_device_id,
305    string16* storage_object_id,
306    bool succeeded) {
307  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
308  DCHECK(pnp_device_id);
309  DCHECK(storage_object_id);
310  if (!succeeded)
311    return;
312  callback.Run(new MTPDeviceDelegateImplWin(device_location,
313                                            *pnp_device_id,
314                                            *storage_object_id));
315}
316
317void CreateMTPDeviceAsyncDelegate(
318    const string16& device_location,
319    const CreateMTPDeviceAsyncDelegateCallback& callback) {
320  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
321  DCHECK(!device_location.empty());
322  string16* pnp_device_id = new string16;
323  string16* storage_object_id = new string16;
324  content::BrowserThread::PostTaskAndReplyWithResult<bool>(
325      content::BrowserThread::UI,
326      FROM_HERE,
327      base::Bind(&GetStorageInfoOnUIThread,
328                 device_location,
329                 base::Unretained(pnp_device_id),
330                 base::Unretained(storage_object_id)),
331      base::Bind(&OnGetStorageInfoCreateDelegate,
332                 device_location,
333                 callback,
334                 base::Owned(pnp_device_id),
335                 base::Owned(storage_object_id)));
336}
337
338// MTPDeviceDelegateImplWin ---------------------------------------------------
339
340MTPDeviceDelegateImplWin::StorageDeviceInfo::StorageDeviceInfo(
341    const string16& pnp_device_id,
342    const string16& registered_device_path,
343    const string16& storage_object_id)
344    : pnp_device_id(pnp_device_id),
345      registered_device_path(registered_device_path),
346      storage_object_id(storage_object_id) {
347  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
348}
349
350MTPDeviceDelegateImplWin::PendingTaskInfo::PendingTaskInfo(
351    const tracked_objects::Location& location,
352    const base::Callback<base::PlatformFileError(void)>& task,
353    const base::Callback<void(base::PlatformFileError)>& reply)
354    : location(location),
355      task(task),
356      reply(reply) {
357}
358
359MTPDeviceDelegateImplWin::MTPDeviceDelegateImplWin(
360    const string16& registered_device_path,
361    const string16& pnp_device_id,
362    const string16& storage_object_id)
363    : storage_device_info_(pnp_device_id, registered_device_path,
364                           storage_object_id),
365      init_state_(UNINITIALIZED),
366      media_task_runner_(MediaFileSystemBackend::MediaTaskRunner()),
367      task_in_progress_(false),
368      weak_ptr_factory_(this) {
369  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
370  DCHECK(!registered_device_path.empty());
371  DCHECK(!pnp_device_id.empty());
372  DCHECK(!storage_object_id.empty());
373  DCHECK(media_task_runner_.get());
374}
375
376MTPDeviceDelegateImplWin::~MTPDeviceDelegateImplWin() {
377  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
378}
379
380void MTPDeviceDelegateImplWin::GetFileInfo(
381    const base::FilePath& file_path,
382    const GetFileInfoSuccessCallback& success_callback,
383    const ErrorCallback& error_callback) {
384  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
385  DCHECK(!file_path.empty());
386  base::PlatformFileInfo* file_info = new base::PlatformFileInfo;
387  EnsureInitAndRunTask(
388      PendingTaskInfo(FROM_HERE,
389                      base::Bind(&GetFileInfoOnBlockingPoolThread,
390                                 storage_device_info_,
391                                 file_path,
392                                 base::Unretained(file_info)),
393                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileInfo,
394                                 weak_ptr_factory_.GetWeakPtr(),
395                                 success_callback,
396                                 error_callback,
397                                 base::Owned(file_info))));
398}
399
400void MTPDeviceDelegateImplWin::ReadDirectory(
401    const base::FilePath& root,
402    const ReadDirectorySuccessCallback& success_callback,
403    const ErrorCallback& error_callback) {
404  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
405  DCHECK(!root.empty());
406  fileapi::AsyncFileUtil::EntryList* entries =
407      new fileapi::AsyncFileUtil::EntryList;
408  EnsureInitAndRunTask(
409      PendingTaskInfo(FROM_HERE,
410                      base::Bind(&ReadDirectoryOnBlockingPoolThread,
411                                 storage_device_info_,
412                                 root,
413                                 base::Unretained(entries)),
414                      base::Bind(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
415                                 weak_ptr_factory_.GetWeakPtr(),
416                                 success_callback,
417                                 error_callback,
418                                 base::Owned(entries))));
419}
420
421void MTPDeviceDelegateImplWin::CreateSnapshotFile(
422    const base::FilePath& device_file_path,
423    const base::FilePath& snapshot_file_path,
424    const CreateSnapshotFileSuccessCallback& success_callback,
425    const ErrorCallback& error_callback) {
426  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
427  DCHECK(!device_file_path.empty());
428  DCHECK(!snapshot_file_path.empty());
429  scoped_ptr<SnapshotFileDetails> file_details(
430      new SnapshotFileDetails(SnapshotRequestInfo(device_file_path,
431                                                  snapshot_file_path,
432                                                  success_callback,
433                                                  error_callback)));
434  // Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
435  // it is owned by |file_details| in the reply callback.
436  EnsureInitAndRunTask(
437      PendingTaskInfo(FROM_HERE,
438                      base::Bind(&GetFileStreamOnBlockingPoolThread,
439                                 storage_device_info_,
440                                 file_details.get()),
441                      base::Bind(&MTPDeviceDelegateImplWin::OnGetFileStream,
442                                 weak_ptr_factory_.GetWeakPtr(),
443                                 base::Passed(&file_details))));
444}
445
446void MTPDeviceDelegateImplWin::CancelPendingTasksAndDeleteDelegate() {
447  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
448  PortableDeviceMapService::GetInstance()->MarkPortableDeviceForDeletion(
449      storage_device_info_.registered_device_path);
450  media_task_runner_->PostTask(
451      FROM_HERE,
452      base::Bind(&DeletePortableDeviceOnBlockingPoolThread,
453                 storage_device_info_.registered_device_path));
454  while (!pending_tasks_.empty())
455    pending_tasks_.pop();
456  delete this;
457}
458
459void MTPDeviceDelegateImplWin::EnsureInitAndRunTask(
460    const PendingTaskInfo& task_info) {
461  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
462  if ((init_state_ == INITIALIZED) && !task_in_progress_) {
463    DCHECK(pending_tasks_.empty());
464    DCHECK(!current_snapshot_details_.get());
465    base::PostTaskAndReplyWithResult(media_task_runner_,
466                                     task_info.location,
467                                     task_info.task,
468                                     task_info.reply);
469    task_in_progress_ = true;
470    return;
471  }
472
473  pending_tasks_.push(task_info);
474  if (init_state_ == UNINITIALIZED) {
475    init_state_ = PENDING_INIT;
476    base::PostTaskAndReplyWithResult(
477        media_task_runner_,
478        FROM_HERE,
479        base::Bind(&OpenDeviceOnBlockingPoolThread,
480                   storage_device_info_.pnp_device_id,
481                   storage_device_info_.registered_device_path),
482        base::Bind(&MTPDeviceDelegateImplWin::OnInitCompleted,
483                   weak_ptr_factory_.GetWeakPtr()));
484    task_in_progress_ = true;
485  }
486}
487
488void MTPDeviceDelegateImplWin::WriteDataChunkIntoSnapshotFile() {
489  DCHECK(current_snapshot_details_.get());
490  base::PostTaskAndReplyWithResult(
491      media_task_runner_,
492      FROM_HERE,
493      base::Bind(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
494                 *current_snapshot_details_),
495      base::Bind(&MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
496                 weak_ptr_factory_.GetWeakPtr(),
497                 current_snapshot_details_->request_info().snapshot_file_path));
498}
499
500void MTPDeviceDelegateImplWin::ProcessNextPendingRequest() {
501  DCHECK(!task_in_progress_);
502  if (pending_tasks_.empty())
503    return;
504  const PendingTaskInfo& task_info = pending_tasks_.front();
505  task_in_progress_ = true;
506  base::PostTaskAndReplyWithResult(media_task_runner_,
507                                   task_info.location,
508                                   task_info.task,
509                                   task_info.reply);
510  pending_tasks_.pop();
511}
512
513void MTPDeviceDelegateImplWin::OnInitCompleted(bool succeeded) {
514  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
515  init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
516  task_in_progress_ = false;
517  ProcessNextPendingRequest();
518}
519
520void MTPDeviceDelegateImplWin::OnGetFileInfo(
521    const GetFileInfoSuccessCallback& success_callback,
522    const ErrorCallback& error_callback,
523    base::PlatformFileInfo* file_info,
524    base::PlatformFileError error) {
525  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
526  DCHECK(file_info);
527  if (error == base::PLATFORM_FILE_OK)
528    success_callback.Run(*file_info);
529  else
530    error_callback.Run(error);
531  task_in_progress_ = false;
532  ProcessNextPendingRequest();
533}
534
535void MTPDeviceDelegateImplWin::OnDidReadDirectory(
536    const ReadDirectorySuccessCallback& success_callback,
537    const ErrorCallback& error_callback,
538    fileapi::AsyncFileUtil::EntryList* file_list,
539    base::PlatformFileError error) {
540  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
541  DCHECK(file_list);
542  if (error == base::PLATFORM_FILE_OK)
543    success_callback.Run(*file_list, false /*no more entries*/);
544  else
545    error_callback.Run(error);
546  task_in_progress_ = false;
547  ProcessNextPendingRequest();
548}
549
550void MTPDeviceDelegateImplWin::OnGetFileStream(
551    scoped_ptr<SnapshotFileDetails> file_details,
552    base::PlatformFileError error) {
553  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
554  DCHECK(file_details);
555  DCHECK(!file_details->request_info().device_file_path.empty());
556  DCHECK(!file_details->request_info().snapshot_file_path.empty());
557  DCHECK(!current_snapshot_details_.get());
558  if (error != base::PLATFORM_FILE_OK) {
559    file_details->request_info().error_callback.Run(error);
560    task_in_progress_ = false;
561    ProcessNextPendingRequest();
562    return;
563  }
564  DCHECK(file_details->file_info().size == 0 ||
565         file_details->device_file_stream());
566  current_snapshot_details_.reset(file_details.release());
567  WriteDataChunkIntoSnapshotFile();
568}
569
570void MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile(
571    const base::FilePath& snapshot_file_path,
572    DWORD bytes_written) {
573  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
574  DCHECK(!snapshot_file_path.empty());
575  if (!current_snapshot_details_.get())
576    return;
577  DCHECK_EQ(
578      current_snapshot_details_->request_info().snapshot_file_path.value(),
579      snapshot_file_path.value());
580
581  bool succeeded = false;
582  bool should_continue = false;
583  if (current_snapshot_details_->file_info().size > 0) {
584    if (current_snapshot_details_->AddBytesWritten(bytes_written)) {
585      if (current_snapshot_details_->IsSnapshotFileWriteComplete()) {
586        succeeded = true;
587      } else {
588        should_continue = true;
589      }
590    }
591  } else {
592    // Handle empty files.
593    DCHECK_EQ(0U, bytes_written);
594    succeeded = true;
595  }
596
597  if (should_continue) {
598    WriteDataChunkIntoSnapshotFile();
599    return;
600  }
601  if (succeeded) {
602    current_snapshot_details_->request_info().success_callback.Run(
603        current_snapshot_details_->file_info(),
604        current_snapshot_details_->request_info().snapshot_file_path);
605  } else {
606    current_snapshot_details_->request_info().error_callback.Run(
607        base::PLATFORM_FILE_ERROR_FAILED);
608  }
609  task_in_progress_ = false;
610  current_snapshot_details_.reset();
611  ProcessNextPendingRequest();
612}
613
614}  // namespace chrome
615