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.
5 // MTPDeviceDelegateImplWin implementation.
7 #include "chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h"
9 #include <portabledevice.h>
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 "components/storage_monitor/storage_monitor.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "webkit/common/fileapi/file_system_util.h"
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.
39 bool 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();
52 return monitor->GetMTPStorageInfoFromDeviceId(
53 base::UTF16ToUTF8(storage_device_id), pnp_device_id, storage_object_id);
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.
62 // Returns an empty string if the device is detached while the request is in
63 // progress or when the |file_path| is invalid.
64 base::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);
73 return base::string16();
75 if (device_info.registered_device_path == file_path.value())
76 return device_info.storage_object_id;
78 base::FilePath relative_path;
79 if (!base::FilePath(device_info.registered_device_path).AppendRelativePath(
80 file_path, &relative_path))
81 return base::string16();
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) {
90 media_transfer_protocol::GetObjectIdFromName(device, parent_id,
92 if (file_object_id.empty())
94 parent_id = file_object_id;
96 return file_object_id;
99 // Returns a pointer to a new instance of AbstractFileEnumerator for the given
100 // |root| directory. Called on a blocking pool thread.
101 scoped_ptr<MTPDeviceObjectEnumerator>
102 CreateFileEnumeratorOnBlockingPoolThread(
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);
112 return scoped_ptr<MTPDeviceObjectEnumerator>();
114 base::string16 object_id =
115 GetFileObjectIdFromPathOnBlockingPoolThread(device_info, root);
116 if (object_id.empty())
117 return scoped_ptr<MTPDeviceObjectEnumerator>();
119 MTPDeviceObjectEntries entries;
120 if (!media_transfer_protocol::GetDirectoryEntries(device, object_id,
123 return scoped_ptr<MTPDeviceObjectEnumerator>();
125 return scoped_ptr<MTPDeviceObjectEnumerator>(
126 new MTPDeviceObjectEnumerator(entries));
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
133 bool 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());
146 return init_succeeded;
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::File::FILE_OK and fills in |file_info|. On failure, |error| is set
152 // to corresponding platform file error and |file_info| is not set.
153 base::File::Error GetFileInfoOnBlockingPoolThread(
154 const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
155 const base::FilePath& file_path,
156 base::File::Info* file_info) {
157 base::ThreadRestrictions::AssertIOAllowed();
158 DCHECK(!device_info.registered_device_path.empty());
159 DCHECK(!file_path.empty());
161 IPortableDevice* device =
162 PortableDeviceMapService::GetInstance()->GetPortableDevice(
163 device_info.registered_device_path);
165 return base::File::FILE_ERROR_FAILED;
167 base::string16 object_id =
168 GetFileObjectIdFromPathOnBlockingPoolThread(device_info, file_path);
169 if (object_id.empty())
170 return base::File::FILE_ERROR_FAILED;
171 return media_transfer_protocol::GetFileEntryInfo(device, object_id,
175 // Reads the |root| directory file entries on a blocking pool thread. On
176 // success, |error| is set to base::File::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.
179 base::File::Error 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());
186 base::File::Info file_info;
187 base::File::Error error = GetFileInfoOnBlockingPoolThread(device_info, root,
189 if (error != base::File::FILE_OK)
192 if (!file_info.is_directory)
193 return base::File::FILE_ERROR_NOT_A_DIRECTORY;
195 base::FilePath current;
196 scoped_ptr<MTPDeviceObjectEnumerator> file_enum =
197 CreateFileEnumeratorOnBlockingPoolThread(device_info, root);
201 while (!(current = file_enum->Next()).empty()) {
202 fileapi::DirectoryEntry entry;
203 entry.is_directory = file_enum->IsDirectory();
204 entry.name = fileapi::VirtualPath::BaseName(current).value();
205 entry.size = file_enum->Size();
206 entry.last_modified_time = file_enum->LastModifiedTime();
207 entries->push_back(entry);
212 // Gets the device file stream object on a blocking pool thread.
213 // |device_info| contains the device storage partition details.
214 // On success, returns base::File::FILE_OK and file stream details are set in
215 // |file_details|. On failure, returns a platform file error and file stream
216 // details are not set in |file_details|.
217 base::File::Error GetFileStreamOnBlockingPoolThread(
218 const MTPDeviceDelegateImplWin::StorageDeviceInfo& device_info,
219 SnapshotFileDetails* file_details) {
220 base::ThreadRestrictions::AssertIOAllowed();
221 DCHECK(file_details);
222 DCHECK(!file_details->request_info().device_file_path.empty());
223 DCHECK(!file_details->request_info().snapshot_file_path.empty());
224 IPortableDevice* device =
225 PortableDeviceMapService::GetInstance()->GetPortableDevice(
226 device_info.registered_device_path);
228 return base::File::FILE_ERROR_FAILED;
230 base::string16 file_object_id =
231 GetFileObjectIdFromPathOnBlockingPoolThread(
232 device_info, file_details->request_info().device_file_path);
233 if (file_object_id.empty())
234 return base::File::FILE_ERROR_FAILED;
236 base::File::Info file_info;
237 base::File::Error error =
238 GetFileInfoOnBlockingPoolThread(
240 file_details->request_info().device_file_path,
242 if (error != base::File::FILE_OK)
245 DWORD optimal_transfer_size = 0;
246 base::win::ScopedComPtr<IStream> file_stream;
247 if (file_info.size > 0) {
248 HRESULT hr = media_transfer_protocol::GetFileStreamForObject(
251 file_stream.Receive(),
252 &optimal_transfer_size);
254 return base::File::FILE_ERROR_FAILED;
257 // LocalFileStreamReader is used to read the contents of the snapshot file.
258 // Snapshot file modification time does not match the last modified time
259 // of the original media file. Therefore, set the last modified time to null
260 // in order to avoid the verification in LocalFileStreamReader.
262 // Users will use HTML5 FileSystem Entry getMetadata() interface to get the
263 // actual last modified time of the media file.
264 file_info.last_modified = base::Time();
266 DCHECK(file_info.size == 0 || optimal_transfer_size > 0U);
267 file_details->set_file_info(file_info);
268 file_details->set_device_file_stream(file_stream);
269 file_details->set_optimal_transfer_size(optimal_transfer_size);
273 // Copies the data chunk from device file to the snapshot file based on the
274 // parameters specified by |file_details|.
275 // Returns the total number of bytes written to the snapshot file for non-empty
276 // files, or 0 on failure. For empty files, just return 0.
277 DWORD WriteDataChunkIntoSnapshotFileOnBlockingPoolThread(
278 const SnapshotFileDetails& file_details) {
279 base::ThreadRestrictions::AssertIOAllowed();
280 if (file_details.file_info().size == 0)
282 return media_transfer_protocol::CopyDataChunkToLocalFile(
283 file_details.device_file_stream(),
284 file_details.request_info().snapshot_file_path,
285 file_details.optimal_transfer_size());
288 void DeletePortableDeviceOnBlockingPoolThread(
289 const base::string16& registered_device_path) {
290 base::ThreadRestrictions::AssertIOAllowed();
291 PortableDeviceMapService::GetInstance()->RemovePortableDevice(
292 registered_device_path);
297 // Used by CreateMTPDeviceAsyncDelegate() to create the MTP device
298 // delegate on the IO thread.
299 void OnGetStorageInfoCreateDelegate(
300 const base::string16& device_location,
301 const CreateMTPDeviceAsyncDelegateCallback& callback,
302 base::string16* pnp_device_id,
303 base::string16* storage_object_id,
305 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
306 DCHECK(pnp_device_id);
307 DCHECK(storage_object_id);
310 callback.Run(new MTPDeviceDelegateImplWin(device_location,
312 *storage_object_id));
315 void CreateMTPDeviceAsyncDelegate(
316 const base::string16& device_location,
317 const CreateMTPDeviceAsyncDelegateCallback& callback) {
318 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
319 DCHECK(!device_location.empty());
320 base::string16* pnp_device_id = new base::string16;
321 base::string16* storage_object_id = new base::string16;
322 content::BrowserThread::PostTaskAndReplyWithResult<bool>(
323 content::BrowserThread::UI,
325 base::Bind(&GetStorageInfoOnUIThread,
327 base::Unretained(pnp_device_id),
328 base::Unretained(storage_object_id)),
329 base::Bind(&OnGetStorageInfoCreateDelegate,
332 base::Owned(pnp_device_id),
333 base::Owned(storage_object_id)));
336 // MTPDeviceDelegateImplWin ---------------------------------------------------
338 MTPDeviceDelegateImplWin::StorageDeviceInfo::StorageDeviceInfo(
339 const base::string16& pnp_device_id,
340 const base::string16& registered_device_path,
341 const base::string16& storage_object_id)
342 : pnp_device_id(pnp_device_id),
343 registered_device_path(registered_device_path),
344 storage_object_id(storage_object_id) {
345 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
348 MTPDeviceDelegateImplWin::PendingTaskInfo::PendingTaskInfo(
349 const tracked_objects::Location& location,
350 const base::Callback<base::File::Error(void)>& task,
351 const base::Callback<void(base::File::Error)>& reply)
352 : location(location),
357 MTPDeviceDelegateImplWin::MTPDeviceDelegateImplWin(
358 const base::string16& registered_device_path,
359 const base::string16& pnp_device_id,
360 const base::string16& storage_object_id)
361 : storage_device_info_(pnp_device_id, registered_device_path,
363 init_state_(UNINITIALIZED),
364 media_task_runner_(MediaFileSystemBackend::MediaTaskRunner()),
365 task_in_progress_(false),
366 weak_ptr_factory_(this) {
367 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
368 DCHECK(!registered_device_path.empty());
369 DCHECK(!pnp_device_id.empty());
370 DCHECK(!storage_object_id.empty());
371 DCHECK(media_task_runner_.get());
374 MTPDeviceDelegateImplWin::~MTPDeviceDelegateImplWin() {
375 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
378 void MTPDeviceDelegateImplWin::GetFileInfo(
379 const base::FilePath& file_path,
380 const GetFileInfoSuccessCallback& success_callback,
381 const ErrorCallback& error_callback) {
382 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
383 DCHECK(!file_path.empty());
384 base::File::Info* file_info = new base::File::Info;
385 EnsureInitAndRunTask(
386 PendingTaskInfo(FROM_HERE,
387 base::Bind(&GetFileInfoOnBlockingPoolThread,
388 storage_device_info_,
390 base::Unretained(file_info)),
391 base::Bind(&MTPDeviceDelegateImplWin::OnGetFileInfo,
392 weak_ptr_factory_.GetWeakPtr(),
395 base::Owned(file_info))));
398 void MTPDeviceDelegateImplWin::ReadDirectory(
399 const base::FilePath& root,
400 const ReadDirectorySuccessCallback& success_callback,
401 const ErrorCallback& error_callback) {
402 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
403 DCHECK(!root.empty());
404 fileapi::AsyncFileUtil::EntryList* entries =
405 new fileapi::AsyncFileUtil::EntryList;
406 EnsureInitAndRunTask(
407 PendingTaskInfo(FROM_HERE,
408 base::Bind(&ReadDirectoryOnBlockingPoolThread,
409 storage_device_info_,
411 base::Unretained(entries)),
412 base::Bind(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
413 weak_ptr_factory_.GetWeakPtr(),
416 base::Owned(entries))));
419 void MTPDeviceDelegateImplWin::CreateSnapshotFile(
420 const base::FilePath& device_file_path,
421 const base::FilePath& snapshot_file_path,
422 const CreateSnapshotFileSuccessCallback& success_callback,
423 const ErrorCallback& error_callback) {
424 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
425 DCHECK(!device_file_path.empty());
426 DCHECK(!snapshot_file_path.empty());
427 scoped_ptr<SnapshotFileDetails> file_details(
428 new SnapshotFileDetails(SnapshotRequestInfo(device_file_path,
432 // Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
433 // it is owned by |file_details| in the reply callback.
434 EnsureInitAndRunTask(
435 PendingTaskInfo(FROM_HERE,
436 base::Bind(&GetFileStreamOnBlockingPoolThread,
437 storage_device_info_,
439 base::Bind(&MTPDeviceDelegateImplWin::OnGetFileStream,
440 weak_ptr_factory_.GetWeakPtr(),
441 base::Passed(&file_details))));
444 bool MTPDeviceDelegateImplWin::IsStreaming() {
448 void MTPDeviceDelegateImplWin::ReadBytes(
449 const base::FilePath& device_file_path,
450 net::IOBuffer* buf, int64 offset, int buf_len,
451 const ReadBytesSuccessCallback& success_callback,
452 const ErrorCallback& error_callback) {
456 void MTPDeviceDelegateImplWin::CancelPendingTasksAndDeleteDelegate() {
457 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
458 PortableDeviceMapService::GetInstance()->MarkPortableDeviceForDeletion(
459 storage_device_info_.registered_device_path);
460 media_task_runner_->PostTask(
462 base::Bind(&DeletePortableDeviceOnBlockingPoolThread,
463 storage_device_info_.registered_device_path));
464 while (!pending_tasks_.empty())
465 pending_tasks_.pop();
469 void MTPDeviceDelegateImplWin::EnsureInitAndRunTask(
470 const PendingTaskInfo& task_info) {
471 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
472 if ((init_state_ == INITIALIZED) && !task_in_progress_) {
473 DCHECK(pending_tasks_.empty());
474 DCHECK(!current_snapshot_details_.get());
475 base::PostTaskAndReplyWithResult(media_task_runner_,
479 task_in_progress_ = true;
483 pending_tasks_.push(task_info);
484 if (init_state_ == UNINITIALIZED) {
485 init_state_ = PENDING_INIT;
486 base::PostTaskAndReplyWithResult(
489 base::Bind(&OpenDeviceOnBlockingPoolThread,
490 storage_device_info_.pnp_device_id,
491 storage_device_info_.registered_device_path),
492 base::Bind(&MTPDeviceDelegateImplWin::OnInitCompleted,
493 weak_ptr_factory_.GetWeakPtr()));
494 task_in_progress_ = true;
498 void MTPDeviceDelegateImplWin::WriteDataChunkIntoSnapshotFile() {
499 DCHECK(current_snapshot_details_.get());
500 base::PostTaskAndReplyWithResult(
503 base::Bind(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
504 *current_snapshot_details_),
505 base::Bind(&MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
506 weak_ptr_factory_.GetWeakPtr(),
507 current_snapshot_details_->request_info().snapshot_file_path));
510 void MTPDeviceDelegateImplWin::ProcessNextPendingRequest() {
511 DCHECK(!task_in_progress_);
512 if (pending_tasks_.empty())
514 const PendingTaskInfo& task_info = pending_tasks_.front();
515 task_in_progress_ = true;
516 base::PostTaskAndReplyWithResult(media_task_runner_,
520 pending_tasks_.pop();
523 void MTPDeviceDelegateImplWin::OnInitCompleted(bool succeeded) {
524 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
525 init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
526 task_in_progress_ = false;
527 ProcessNextPendingRequest();
530 void MTPDeviceDelegateImplWin::OnGetFileInfo(
531 const GetFileInfoSuccessCallback& success_callback,
532 const ErrorCallback& error_callback,
533 base::File::Info* file_info,
534 base::File::Error error) {
535 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
537 if (error == base::File::FILE_OK)
538 success_callback.Run(*file_info);
540 error_callback.Run(error);
541 task_in_progress_ = false;
542 ProcessNextPendingRequest();
545 void MTPDeviceDelegateImplWin::OnDidReadDirectory(
546 const ReadDirectorySuccessCallback& success_callback,
547 const ErrorCallback& error_callback,
548 fileapi::AsyncFileUtil::EntryList* file_list,
549 base::File::Error error) {
550 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
552 if (error == base::File::FILE_OK)
553 success_callback.Run(*file_list, false /*no more entries*/);
555 error_callback.Run(error);
556 task_in_progress_ = false;
557 ProcessNextPendingRequest();
560 void MTPDeviceDelegateImplWin::OnGetFileStream(
561 scoped_ptr<SnapshotFileDetails> file_details,
562 base::File::Error error) {
563 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
564 DCHECK(file_details);
565 DCHECK(!file_details->request_info().device_file_path.empty());
566 DCHECK(!file_details->request_info().snapshot_file_path.empty());
567 DCHECK(!current_snapshot_details_.get());
568 if (error != base::File::FILE_OK) {
569 file_details->request_info().error_callback.Run(error);
570 task_in_progress_ = false;
571 ProcessNextPendingRequest();
574 DCHECK(file_details->file_info().size == 0 ||
575 file_details->device_file_stream());
576 current_snapshot_details_.reset(file_details.release());
577 WriteDataChunkIntoSnapshotFile();
580 void MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile(
581 const base::FilePath& snapshot_file_path,
582 DWORD bytes_written) {
583 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
584 DCHECK(!snapshot_file_path.empty());
585 if (!current_snapshot_details_.get())
588 current_snapshot_details_->request_info().snapshot_file_path.value(),
589 snapshot_file_path.value());
591 bool succeeded = false;
592 bool should_continue = false;
593 if (current_snapshot_details_->file_info().size > 0) {
594 if (current_snapshot_details_->AddBytesWritten(bytes_written)) {
595 if (current_snapshot_details_->IsSnapshotFileWriteComplete()) {
598 should_continue = true;
602 // Handle empty files.
603 DCHECK_EQ(0U, bytes_written);
607 if (should_continue) {
608 WriteDataChunkIntoSnapshotFile();
612 current_snapshot_details_->request_info().success_callback.Run(
613 current_snapshot_details_->file_info(),
614 current_snapshot_details_->request_info().snapshot_file_path);
616 current_snapshot_details_->request_info().error_callback.Run(
617 base::File::FILE_ERROR_FAILED);
619 task_in_progress_ = false;
620 current_snapshot_details_.reset();
621 ProcessNextPendingRequest();