Chromium Code Reviews| Index: chrome/browser/media_gallery/win/mtp_device_operations_util.cc |
| diff --git a/chrome/browser/media_gallery/win/mtp_device_operations_util.cc b/chrome/browser/media_gallery/win/mtp_device_operations_util.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4c93efd4eac2faf2ef7b87c9805760f7dd98834c |
| --- /dev/null |
| +++ b/chrome/browser/media_gallery/win/mtp_device_operations_util.cc |
| @@ -0,0 +1,413 @@ |
| +// Copyright (c) 2012 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. |
| + |
| +#include "chrome/browser/media_gallery/win/mtp_device_operations_util.h" |
| + |
| +#include <portabledevice.h> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/file_path.h" |
| +#include "base/file_util.h" |
| +#include "base/logging.h" |
| +#include "base/string_util.h" |
| +#include "base/threading/thread_restrictions.h" |
| +#include "base/time.h" |
| +#include "base/win/scoped_co_mem.h" |
| +#include "chrome/browser/system_monitor/removable_device_constants.h" |
| +#include "chrome/common/chrome_constants.h" |
| +#include "content/public/browser/browser_thread.h" |
| + |
| +namespace chrome { |
| + |
| +namespace media_transfer_protocol { |
| + |
| +namespace { |
| + |
| +// On success, returns true and updates |client_info| with a reference to an |
| +// IPortableDeviceValues interface that holds information about the |
| +// application that communicates with the device. |
| +bool GetClientInformation( |
| + base::win::ScopedComPtr<IPortableDeviceValues>* client_info) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(client_info); |
| + HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues), |
| + NULL, CLSCTX_INPROC_SERVER); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues"; |
| + return false; |
| + } |
| + |
| + (*client_info)->SetStringValue(WPD_CLIENT_NAME, |
| + chrome::kBrowserProcessExecutableName); |
| + (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0); |
| + (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); |
| + (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0); |
| + (*client_info)->SetUnsignedIntegerValue( |
| + WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION); |
| + (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, |
| + GENERIC_READ); |
| + return true; |
| +} |
| + |
| +// Gets the content interface of the portable |device|. On success, returns |
| +// true and fills in |content| with the given portable |device| content. |
| +bool GetDeviceContent(IPortableDevice* device, |
| + IPortableDeviceContent** content) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(content); |
| + return SUCCEEDED(device->Content(content)); |
| +} |
| + |
| +// Gets the portable |device| enumerator interface to enumerate the objects. |
| +// |parent_id| specifies the parent object identifier. On success, returns |
| +// true and fills in |enum_object_ids|. |
| +bool GetDeviceObjectEnumerator(IPortableDevice* device, |
|
Lei Zhang
2013/01/14 23:25:30
This can also just return an IEnumPortableDeviceOb
kmadhusu
2013/01/15 19:08:17
Done and also fixed GetDeviceContent.
|
| + const string16& parent_id, |
| + IEnumPortableDeviceObjectIDs** enum_object_ids) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(!parent_id.empty()); |
| + DCHECK(enum_object_ids); |
| + base::win::ScopedComPtr<IPortableDeviceContent> content; |
| + if (!GetDeviceContent(device, content.Receive())) |
| + return false; |
| + |
| + HRESULT hr = content->EnumObjects(0, parent_id.c_str(), NULL, |
| + enum_object_ids); |
| + return SUCCEEDED(hr); |
| +} |
| + |
| +// Writes data from |stream| to the file specified by |local_path|. On success, |
| +// returns true and the stream contents are appended to the file. |
| +// TODO(kmadhusu) Deprecate this function after fixing crbug.com/110119. |
| +bool WriteStreamContentsToFile(IStream* stream, |
| + size_t optimal_transfer_size, |
| + const FilePath& local_path) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(stream); |
| + DCHECK_GT(optimal_transfer_size, 0u); |
|
Lei Zhang
2013/01/14 23:25:30
I don't see any guarantee from IPortableDeviceReso
kmadhusu
2013/01/15 19:08:17
If the optimal_transfer_size is equal to 0, this f
|
| + DCHECK(!local_path.empty()); |
| + DWORD read = 0; |
|
Lei Zhang
2013/01/14 23:25:30
Doesn't IStream::Read return a ULONG? Is a ULONG a
kmadhusu
2013/01/15 19:08:17
As per http://msdn.microsoft.com/en-us/library/win
|
| + HRESULT hr = S_OK; |
| + std::string buffer; |
| + do { |
| + hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1), |
| + optimal_transfer_size, &read); |
| + // IStream::Read() returns S_FALSE when the actual number of bytes read from |
| + // the stream object is less than the number of bytes requested (aka |
| + // |optimal_transfer_size|). This indicates the end of the stream has been |
| + // reached. Therefore, it is fine to return true when Read() returns |
| + // S_FALSE. |
| + if ((hr != S_OK) && (hr != S_FALSE)) |
| + return false; |
| + if (read) { |
| + buffer.erase(read); |
|
Lei Zhang
2013/01/14 23:25:30
Can't you avoid manipulating the buffer, and inste
kmadhusu
2013/01/15 19:08:17
Done.
|
| + DCHECK_EQ(read, buffer.length()); |
| + int data_len = static_cast<int>(buffer.length()); |
| + if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != |
| + data_len) |
| + return false; |
| + } |
| + } while ((read > 0) && SUCCEEDED(hr)); |
|
Lei Zhang
2013/01/14 23:25:30
Why do you need the SUCCEEDED() part? You've alrea
kmadhusu
2013/01/15 19:08:17
If |hr| is S_FALSE, SUCCEEDED(hr) will be false. P
Lei Zhang
2013/01/15 21:00:13
No, SUCCEEDED(S_FALSE) returns true. S_ means succ
kmadhusu
2013/01/15 23:41:58
Removed SUCCEEDED(hr).
|
| + return true; |
| +} |
| + |
| +// Returns whether the object is a directory/folder/album. |properties_values| |
| +// contains the object property key values. |
| +bool IsDirectory(IPortableDeviceValues* properties_values) { |
| + DCHECK(properties_values); |
| + GUID content_type; |
| + HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, |
| + &content_type); |
| + if (FAILED(hr)) |
| + return false; |
| + return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) || |
|
Lei Zhang
2013/01/14 23:25:30
I'm not sure albums (aka playlists??) are director
kmadhusu
2013/01/15 19:08:17
As per http://msdn.microsoft.com/en-us/library/win
Lei Zhang
2013/01/15 21:00:13
Based on my understanding of MTP, I don't believe
kmadhusu
2013/01/15 23:41:58
As discussed, removed the album types and added a
|
| + (content_type == WPD_CONTENT_TYPE_FOLDER) || |
| + (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) || |
| + (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) || |
| + (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) || |
| + (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM)); |
| +} |
| + |
| +// Returns the friendly name of the object from the property key values |
| +// specified by the |properties_values|. |
| +string16 GetObjectName(IPortableDeviceValues* properties_values, |
| + bool is_directory) { |
| + DCHECK(properties_values); |
| + base::win::ScopedCoMem<char16> buffer; |
| + REFPROPERTYKEY key = |
| + is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME; |
| + HRESULT hr = properties_values->GetStringValue(key, &buffer); |
| + string16 result; |
| + if (SUCCEEDED(hr)) |
| + result.assign(buffer); |
| + return result; |
| +} |
| + |
| +// Gets the last modified time of the object from the property key values |
| +// specified by the |properties_values|. On success, returns true and fills in |
| +// |last_modified_time|. |
| +bool GetLastModifiedTime(IPortableDeviceValues* properties_values, |
| + base::Time* last_modified_time) { |
| + DCHECK(properties_values); |
| + DCHECK(last_modified_time); |
| + PROPVARIANT last_modified_date = {0}; |
| + PropVariantInit(&last_modified_date); |
| + HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED, |
| + &last_modified_date); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + bool last_modified_time_set = (last_modified_date.vt == VT_DATE); |
| + if (last_modified_time_set) { |
| + SYSTEMTIME system_time; |
| + FILETIME file_time; |
| + if (VariantTimeToSystemTime(last_modified_date.date, &system_time) && |
| + SystemTimeToFileTime(&system_time, &file_time)) |
| + *last_modified_time = base::Time::FromFileTime(file_time); |
| + else |
| + last_modified_time_set = false; |
| + } |
| + PropVariantClear(&last_modified_date); |
| + return last_modified_time_set; |
| +} |
| + |
| +// Gets the size of the file object in bytes from the property key values |
| +// specified by the |properties_values|. On success, returns true and fills |
| +// in |size|. |
| +bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) { |
| + DCHECK(properties_values); |
| + DCHECK(size); |
| + ULONGLONG actual_size; |
| + HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, |
| + &actual_size); |
| + bool success = SUCCEEDED(hr) && (actual_size <= kint64max); |
| + if (success) |
| + *size = static_cast<int64>(actual_size); |
| + return success; |
| +} |
| + |
| +// Gets the details of the object specified by the |object_id| given the media |
| +// transfer protocol |device|. On success, returns true and fills in |name|, |
| +// |is_directory|, |size| and |last_modified_time|. |
| +bool GetObjectDetails(IPortableDevice* device, |
| + const string16 object_id, |
| + string16* name, |
| + bool* is_directory, |
| + int64* size, |
| + base::Time* last_modified_time) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(!object_id.empty()); |
| + DCHECK(name); |
| + DCHECK(is_directory); |
| + DCHECK(size); |
| + DCHECK(last_modified_time); |
| + base::win::ScopedComPtr<IPortableDeviceContent> content; |
| + if (!GetDeviceContent(device, content.Receive())) |
| + return false; |
| + |
| + base::win::ScopedComPtr<IPortableDeviceProperties> properties; |
| + HRESULT hr = content->Properties(properties.Receive()); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; |
| + hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection), |
| + NULL, |
| + CLSCTX_INPROC_SERVER); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) || |
| + FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) || |
| + FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) || |
| + FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) || |
| + FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) || |
| + FAILED(properties_to_read->Add(WPD_OBJECT_SIZE))) |
| + return false; |
| + |
| + base::win::ScopedComPtr<IPortableDeviceValues> properties_values; |
| + hr = properties->GetValues(object_id.c_str(), |
| + properties_to_read.get(), |
| + properties_values.Receive()); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + *is_directory = IsDirectory(properties_values.get()); |
| + *name = GetObjectName(properties_values.get(), *is_directory); |
| + if (name->empty()) |
| + return false; |
| + |
| + if (*is_directory) { |
| + // Directory entry does not have size and last modified date property key |
| + // values. |
| + *size = 0; |
| + *last_modified_time = base::Time(); |
| + return true; |
| + } |
| + return (GetObjectSize(properties_values.get(), size) && |
| + GetLastModifiedTime(properties_values.get(), last_modified_time)); |
| +} |
| + |
| +// Creates an MTP device object entry for the given |device| and |object_id|. |
| +// On success, returns true and fills in |entry|. |
| +bool GetMTPDeviceObjectEntry(IPortableDevice* device, |
| + const string16& object_id, |
| + MTPDeviceObjectEntry* entry) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(!object_id.empty()); |
| + DCHECK(entry); |
| + string16 name; |
| + bool is_directory; |
| + int64 size; |
| + base::Time last_modified_time; |
| + if (!GetObjectDetails(device, object_id, &name, &is_directory, &size, |
| + &last_modified_time)) |
| + return false; |
| + *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size, |
| + last_modified_time); |
| + return true; |
| +} |
| + |
| +// Gets the entries of the directory specified by |directory_object_id| from |
| +// the given MTP |device|. Set |get_all_entries| to true to get all the entries |
| +// of the directory. To request a specific object entry, set |get_all_entries| |
| +// to false and set |object_name| to the name of the required object. On |
| +// success returns true and set |object_entries|. |
| +bool GetMTPDeviceObjectEntries(IPortableDevice* device, |
| + const string16& directory_object_id, |
| + bool get_all_entries, |
|
Lei Zhang
2013/01/14 23:25:30
You can just indicate this with a non empty |objec
kmadhusu
2013/01/15 19:08:17
This is more clearer than the suggested solution.
Lei Zhang
2013/01/15 21:00:13
Not particularly. It's not hard to understand "emp
kmadhusu
2013/01/15 23:41:58
Done.
|
| + const string16& object_name, |
| + MTPDeviceObjectEntries* object_entries) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(!directory_object_id.empty()); |
| + DCHECK(object_entries); |
| + base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids; |
| + if (!GetDeviceObjectEnumerator(device, directory_object_id, |
| + enum_object_ids.Receive())) |
| + return false; |
| + |
| + // Loop calling Next() while S_OK is being returned. |
| + DWORD num_objects_to_request = 10; |
|
Lei Zhang
2013/01/14 23:25:30
const?
kmadhusu
2013/01/15 19:08:17
Done.
|
| + for (HRESULT hr = S_OK; hr == S_OK;) { |
|
Lei Zhang
2013/01/14 23:25:30
Isn't this just do { } while (hr == S_OK) ?
kmadhusu
2013/01/15 19:08:17
Yes. Leaving it as it is.
|
| + DWORD num_objects_fetched = 0; |
| + scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]); |
| + hr = enum_object_ids->Next(num_objects_to_request, |
| + object_ids.get(), |
| + &num_objects_fetched); |
| + for (DWORD index = 0; index < num_objects_fetched; ++index) { |
| + MTPDeviceObjectEntry entry; |
| + if (GetMTPDeviceObjectEntry(device, |
| + object_ids[index], |
| + &entry)) { |
| + if (get_all_entries) { |
| + object_entries->push_back(entry); |
| + } else if (entry.name == object_name) { |
| + object_entries->push_back(entry); // Object entry found. |
| + break; |
| + } |
| + } |
| + } |
| + for (DWORD index = 0; index < num_objects_fetched; ++index) |
| + CoTaskMemFree(object_ids[index]); |
| + } |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +bool OpenDevice(const string16& pnp_device_id, |
| + base::win::ScopedComPtr<IPortableDevice>* device) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(!pnp_device_id.empty()); |
| + DCHECK(device); |
| + base::win::ScopedComPtr<IPortableDeviceValues> client_info; |
| + if (!GetClientInformation(&client_info)) |
| + return false; |
| + HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL, |
| + CLSCTX_INPROC_SERVER); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + hr = (*device)->Open(pnp_device_id.c_str(), client_info.get()); |
| + if (SUCCEEDED(hr)) |
| + return true; |
| + if (hr == E_ACCESSDENIED) |
| + DPLOG(ERROR) << "Access denied to open the device"; |
| + return false; |
| +} |
| + |
| +base::PlatformFileError GetFileEntryInfo( |
| + IPortableDevice* device, |
| + const string16& object_id, |
| + base::PlatformFileInfo* file_entry_info) { |
| + DCHECK(device); |
| + DCHECK(!object_id.empty()); |
| + DCHECK(file_entry_info); |
| + MTPDeviceObjectEntry entry; |
| + if (!GetMTPDeviceObjectEntry(device, object_id, &entry)) |
| + return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| + |
| + file_entry_info->size = entry.size; |
| + file_entry_info->is_directory = entry.is_directory; |
| + file_entry_info->is_symbolic_link = false; |
| + file_entry_info->last_modified = entry.last_modified_time; |
| + file_entry_info->last_accessed = entry.last_modified_time; |
| + file_entry_info->creation_time = base::Time(); |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +bool GetDirectoryEntries(IPortableDevice* device, |
| + const string16& directory_object_id, |
| + MTPDeviceObjectEntries* object_entries) { |
| + return GetMTPDeviceObjectEntries(device, directory_object_id, true, |
| + string16(), object_entries); |
| +} |
| + |
| +bool WriteFileObjectData(IPortableDevice* device, |
| + const string16& file_object_id, |
| + const FilePath& local_path) { |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + DCHECK(device); |
| + DCHECK(!file_object_id.empty()); |
| + DCHECK(!local_path.value().empty()); |
|
Lei Zhang
2013/01/14 23:25:30
!local_path.empty() ?
kmadhusu
2013/01/15 19:08:17
Done.
|
| + base::win::ScopedComPtr<IPortableDeviceContent> content; |
| + if (!GetDeviceContent(device, content.Receive())) |
| + return false; |
| + |
| + base::win::ScopedComPtr<IPortableDeviceResources> resources; |
| + HRESULT hr = content->Transfer(resources.Receive()); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + base::win::ScopedComPtr<IStream> file_stream; |
| + DWORD optimal_transfer_size = 0; |
| + hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT, |
| + STGM_READ, &optimal_transfer_size, |
| + file_stream.Receive()); |
| + if (FAILED(hr)) |
| + return false; |
| + return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size, |
| + local_path); |
| +} |
| + |
| +string16 GetObjectIdFromName(IPortableDevice* device, |
| + const string16& parent_id, |
| + const string16& object_name) { |
| + MTPDeviceObjectEntries object_entries; |
| + if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name, |
| + &object_entries) || |
| + object_entries.empty()) |
| + return string16(); |
| + DCHECK_EQ(1U, object_entries.size()); |
|
Lei Zhang
2013/01/14 23:25:30
FWIW, this DCHECK can fail. In MTP, it is perfectl
kmadhusu
2013/01/15 19:08:17
Done.
|
| + return object_entries[0].object_id; |
| +} |
| + |
| +} // namespace media_transfer_protocol |
| + |
| +} // namespace chrome |