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 #include "chrome/browser/media_galleries/win/mtp_device_operations_util.h"
7 #include <portabledevice.h>
11 #include "base/basictypes.h"
12 #include "base/file_util.h"
13 #include "base/files/file_path.h"
14 #include "base/logging.h"
15 #include "base/numerics/safe_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "base/time/time.h"
19 #include "base/win/scoped_co_mem.h"
20 #include "base/win/scoped_propvariant.h"
21 #include "chrome/browser/storage_monitor/removable_device_constants.h"
22 #include "chrome/common/chrome_constants.h"
23 #include "content/public/browser/browser_thread.h"
25 namespace media_transfer_protocol
{
29 // On success, returns true and updates |client_info| with a reference to an
30 // IPortableDeviceValues interface that holds information about the
31 // application that communicates with the device.
32 bool GetClientInformation(
33 base::win::ScopedComPtr
<IPortableDeviceValues
>* client_info
) {
34 base::ThreadRestrictions::AssertIOAllowed();
36 HRESULT hr
= client_info
->CreateInstance(__uuidof(PortableDeviceValues
),
37 NULL
, CLSCTX_INPROC_SERVER
);
39 DPLOG(ERROR
) << "Failed to create an instance of IPortableDeviceValues";
43 (*client_info
)->SetStringValue(WPD_CLIENT_NAME
,
44 chrome::kBrowserProcessExecutableName
);
45 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION
, 0);
46 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION
, 0);
47 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION
, 0);
48 (*client_info
)->SetUnsignedIntegerValue(
49 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE
, SECURITY_IMPERSONATION
);
50 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS
,
55 // Gets the content interface of the portable |device|. On success, returns
56 // the IPortableDeviceContent interface. On failure, returns NULL.
57 base::win::ScopedComPtr
<IPortableDeviceContent
> GetDeviceContent(
58 IPortableDevice
* device
) {
59 base::ThreadRestrictions::AssertIOAllowed();
61 base::win::ScopedComPtr
<IPortableDeviceContent
> content
;
62 if (SUCCEEDED(device
->Content(content
.Receive())))
64 return base::win::ScopedComPtr
<IPortableDeviceContent
>();
67 // On success, returns IEnumPortableDeviceObjectIDs interface to enumerate
68 // the device objects. On failure, returns NULL.
69 // |parent_id| specifies the parent object identifier.
70 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> GetDeviceObjectEnumerator(
71 IPortableDevice
* device
,
72 const base::string16
& parent_id
) {
73 base::ThreadRestrictions::AssertIOAllowed();
75 DCHECK(!parent_id
.empty());
76 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
77 GetDeviceContent(device
);
79 return base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
>();
81 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> enum_object_ids
;
82 if (SUCCEEDED(content
->EnumObjects(0, parent_id
.c_str(), NULL
,
83 enum_object_ids
.Receive())))
84 return enum_object_ids
;
85 return base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
>();
88 // Returns whether the object is a directory/folder/album. |properties_values|
89 // contains the object property key values.
90 bool IsDirectory(IPortableDeviceValues
* properties_values
) {
91 DCHECK(properties_values
);
93 HRESULT hr
= properties_values
->GetGuidValue(WPD_OBJECT_CONTENT_TYPE
,
97 // TODO(kmadhusu): |content_type| can be an image or audio or video or mixed
98 // album. It is not clear whether an album is a collection of physical objects
99 // or virtual objects. Investigate this in detail.
101 // The root storage object describes its content type as
102 // WPD_CONTENT_FUNCTIONAL_OBJECT.
103 return (content_type
== WPD_CONTENT_TYPE_FOLDER
||
104 content_type
== WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT
);
107 // Returns the friendly name of the object from the property key values
108 // specified by the |properties_values|.
109 base::string16
GetObjectName(IPortableDeviceValues
* properties_values
,
111 DCHECK(properties_values
);
112 base::win::ScopedCoMem
<base::char16
> buffer
;
114 is_directory
? WPD_OBJECT_NAME
: WPD_OBJECT_ORIGINAL_FILE_NAME
;
115 HRESULT hr
= properties_values
->GetStringValue(key
, &buffer
);
116 base::string16 result
;
118 result
.assign(buffer
);
122 // Gets the last modified time of the object from the property key values
123 // specified by the |properties_values|. On success, returns true and fills in
124 // |last_modified_time|.
125 bool GetLastModifiedTime(IPortableDeviceValues
* properties_values
,
126 base::Time
* last_modified_time
) {
127 DCHECK(properties_values
);
128 DCHECK(last_modified_time
);
129 base::win::ScopedPropVariant last_modified_date
;
130 HRESULT hr
= properties_values
->GetValue(WPD_OBJECT_DATE_MODIFIED
,
131 last_modified_date
.Receive());
135 bool last_modified_time_set
= (last_modified_date
.get().vt
== VT_DATE
);
136 if (last_modified_time_set
) {
137 SYSTEMTIME system_time
;
139 if (VariantTimeToSystemTime(last_modified_date
.get().date
, &system_time
) &&
140 SystemTimeToFileTime(&system_time
, &file_time
)) {
141 *last_modified_time
= base::Time::FromFileTime(file_time
);
143 last_modified_time_set
= false;
146 return last_modified_time_set
;
149 // Gets the size of the file object in bytes from the property key values
150 // specified by the |properties_values|. On success, returns true and fills
152 bool GetObjectSize(IPortableDeviceValues
* properties_values
, int64
* size
) {
153 DCHECK(properties_values
);
155 ULONGLONG actual_size
;
156 HRESULT hr
= properties_values
->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE
,
158 bool success
= SUCCEEDED(hr
) && (actual_size
<= kint64max
);
160 *size
= static_cast<int64
>(actual_size
);
164 // Gets the details of the object specified by the |object_id| given the media
165 // transfer protocol |device|. On success, returns true and fills in |name|,
166 // |is_directory|, |size| and |last_modified_time|.
167 bool GetObjectDetails(IPortableDevice
* device
,
168 const base::string16 object_id
,
169 base::string16
* name
,
172 base::Time
* last_modified_time
) {
173 base::ThreadRestrictions::AssertIOAllowed();
175 DCHECK(!object_id
.empty());
177 DCHECK(is_directory
);
179 DCHECK(last_modified_time
);
180 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
181 GetDeviceContent(device
);
185 base::win::ScopedComPtr
<IPortableDeviceProperties
> properties
;
186 HRESULT hr
= content
->Properties(properties
.Receive());
190 base::win::ScopedComPtr
<IPortableDeviceKeyCollection
> properties_to_read
;
191 hr
= properties_to_read
.CreateInstance(__uuidof(PortableDeviceKeyCollection
),
193 CLSCTX_INPROC_SERVER
);
197 if (FAILED(properties_to_read
->Add(WPD_OBJECT_CONTENT_TYPE
)) ||
198 FAILED(properties_to_read
->Add(WPD_OBJECT_FORMAT
)) ||
199 FAILED(properties_to_read
->Add(WPD_OBJECT_ORIGINAL_FILE_NAME
)) ||
200 FAILED(properties_to_read
->Add(WPD_OBJECT_NAME
)) ||
201 FAILED(properties_to_read
->Add(WPD_OBJECT_DATE_MODIFIED
)) ||
202 FAILED(properties_to_read
->Add(WPD_OBJECT_SIZE
)))
205 base::win::ScopedComPtr
<IPortableDeviceValues
> properties_values
;
206 hr
= properties
->GetValues(object_id
.c_str(),
207 properties_to_read
.get(),
208 properties_values
.Receive());
212 *is_directory
= IsDirectory(properties_values
.get());
213 *name
= GetObjectName(properties_values
.get(), *is_directory
);
218 // Directory entry does not have size and last modified date property key
221 *last_modified_time
= base::Time();
224 return (GetObjectSize(properties_values
.get(), size
) &&
225 GetLastModifiedTime(properties_values
.get(), last_modified_time
));
228 // Creates an MTP device object entry for the given |device| and |object_id|.
229 // On success, returns true and fills in |entry|.
230 bool GetMTPDeviceObjectEntry(IPortableDevice
* device
,
231 const base::string16
& object_id
,
232 MTPDeviceObjectEntry
* entry
) {
233 base::ThreadRestrictions::AssertIOAllowed();
235 DCHECK(!object_id
.empty());
240 base::Time last_modified_time
;
241 if (!GetObjectDetails(device
, object_id
, &name
, &is_directory
, &size
,
242 &last_modified_time
))
244 *entry
= MTPDeviceObjectEntry(object_id
, name
, is_directory
, size
,
249 // Gets the entries of the directory specified by |directory_object_id| from
250 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
251 // of the directory. To request a specific object entry, put the object name in
252 // |object_name|. Leave |object_name| blank to request all entries. On
253 // success returns true and set |object_entries|.
254 bool GetMTPDeviceObjectEntries(IPortableDevice
* device
,
255 const base::string16
& directory_object_id
,
256 const base::string16
& object_name
,
257 MTPDeviceObjectEntries
* object_entries
) {
258 base::ThreadRestrictions::AssertIOAllowed();
260 DCHECK(!directory_object_id
.empty());
261 DCHECK(object_entries
);
262 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> enum_object_ids
=
263 GetDeviceObjectEnumerator(device
, directory_object_id
);
264 if (!enum_object_ids
)
267 // Loop calling Next() while S_OK is being returned.
268 const DWORD num_objects_to_request
= 10;
269 const bool get_all_entries
= object_name
.empty();
270 for (HRESULT hr
= S_OK
; hr
== S_OK
;) {
271 DWORD num_objects_fetched
= 0;
272 scoped_ptr
<base::char16
*[]> object_ids(
273 new base::char16
*[num_objects_to_request
]);
274 hr
= enum_object_ids
->Next(num_objects_to_request
,
276 &num_objects_fetched
);
277 for (DWORD index
= 0; index
< num_objects_fetched
; ++index
) {
278 MTPDeviceObjectEntry entry
;
279 if (GetMTPDeviceObjectEntry(device
,
282 if (get_all_entries
) {
283 object_entries
->push_back(entry
);
284 } else if (entry
.name
== object_name
) {
285 object_entries
->push_back(entry
); // Object entry found.
290 for (DWORD index
= 0; index
< num_objects_fetched
; ++index
)
291 CoTaskMemFree(object_ids
[index
]);
298 base::win::ScopedComPtr
<IPortableDevice
> OpenDevice(
299 const base::string16
& pnp_device_id
) {
300 base::ThreadRestrictions::AssertIOAllowed();
301 DCHECK(!pnp_device_id
.empty());
302 base::win::ScopedComPtr
<IPortableDeviceValues
> client_info
;
303 if (!GetClientInformation(&client_info
))
304 return base::win::ScopedComPtr
<IPortableDevice
>();
305 base::win::ScopedComPtr
<IPortableDevice
> device
;
306 HRESULT hr
= device
.CreateInstance(__uuidof(PortableDevice
), NULL
,
307 CLSCTX_INPROC_SERVER
);
309 return base::win::ScopedComPtr
<IPortableDevice
>();
311 hr
= device
->Open(pnp_device_id
.c_str(), client_info
.get());
314 if (hr
== E_ACCESSDENIED
)
315 DPLOG(ERROR
) << "Access denied to open the device";
316 return base::win::ScopedComPtr
<IPortableDevice
>();
319 base::PlatformFileError
GetFileEntryInfo(
320 IPortableDevice
* device
,
321 const base::string16
& object_id
,
322 base::PlatformFileInfo
* file_entry_info
) {
324 DCHECK(!object_id
.empty());
325 DCHECK(file_entry_info
);
326 MTPDeviceObjectEntry entry
;
327 if (!GetMTPDeviceObjectEntry(device
, object_id
, &entry
))
328 return base::PLATFORM_FILE_ERROR_NOT_FOUND
;
330 file_entry_info
->size
= entry
.size
;
331 file_entry_info
->is_directory
= entry
.is_directory
;
332 file_entry_info
->is_symbolic_link
= false;
333 file_entry_info
->last_modified
= entry
.last_modified_time
;
334 file_entry_info
->last_accessed
= entry
.last_modified_time
;
335 file_entry_info
->creation_time
= base::Time();
336 return base::PLATFORM_FILE_OK
;
339 bool GetDirectoryEntries(IPortableDevice
* device
,
340 const base::string16
& directory_object_id
,
341 MTPDeviceObjectEntries
* object_entries
) {
342 return GetMTPDeviceObjectEntries(device
, directory_object_id
,
343 base::string16(), object_entries
);
346 HRESULT
GetFileStreamForObject(IPortableDevice
* device
,
347 const base::string16
& file_object_id
,
348 IStream
** file_stream
,
349 DWORD
* optimal_transfer_size
) {
350 base::ThreadRestrictions::AssertIOAllowed();
352 DCHECK(!file_object_id
.empty());
353 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
354 GetDeviceContent(device
);
358 base::win::ScopedComPtr
<IPortableDeviceResources
> resources
;
359 HRESULT hr
= content
->Transfer(resources
.Receive());
362 return resources
->GetStream(file_object_id
.c_str(), WPD_RESOURCE_DEFAULT
,
363 STGM_READ
, optimal_transfer_size
,
367 DWORD
CopyDataChunkToLocalFile(IStream
* stream
,
368 const base::FilePath
& local_path
,
369 size_t optimal_transfer_size
) {
370 base::ThreadRestrictions::AssertIOAllowed();
372 DCHECK(!local_path
.empty());
373 if (optimal_transfer_size
== 0U)
375 DWORD bytes_read
= 0;
377 HRESULT hr
= stream
->Read(WriteInto(&buffer
, optimal_transfer_size
+ 1),
378 optimal_transfer_size
, &bytes_read
);
379 // IStream::Read() returns S_FALSE when the actual number of bytes read from
380 // the stream object is less than the number of bytes requested (aka
381 // |optimal_transfer_size|). This indicates the end of the stream has been
385 DCHECK_GT(bytes_read
, 0U);
386 CHECK_LE(bytes_read
, buffer
.length());
388 base::checked_cast
<int>(
390 base::checked_cast
<DWORD
>(buffer
.length())));
391 if (file_util::AppendToFile(local_path
, buffer
.c_str(), data_len
) != data_len
)
396 base::string16
GetObjectIdFromName(IPortableDevice
* device
,
397 const base::string16
& parent_id
,
398 const base::string16
& object_name
) {
399 MTPDeviceObjectEntries object_entries
;
400 if (!GetMTPDeviceObjectEntries(device
, parent_id
, object_name
,
402 object_entries
.empty())
403 return base::string16();
404 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
405 // the same name. Handle the situation gracefully. Refer to crbug.com/169930
407 DCHECK_EQ(1U, object_entries
.size());
408 return object_entries
[0].object_id
;
411 } // namespace media_transfer_protocol