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/files/file_path.h"
13 #include "base/files/file_util.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/common/chrome_constants.h"
23 namespace media_transfer_protocol
{
27 // On success, returns true and updates |client_info| with a reference to an
28 // IPortableDeviceValues interface that holds information about the
29 // application that communicates with the device.
30 bool GetClientInformation(
31 base::win::ScopedComPtr
<IPortableDeviceValues
>* client_info
) {
32 base::ThreadRestrictions::AssertIOAllowed();
34 HRESULT hr
= client_info
->CreateInstance(__uuidof(PortableDeviceValues
),
35 NULL
, CLSCTX_INPROC_SERVER
);
37 DPLOG(ERROR
) << "Failed to create an instance of IPortableDeviceValues";
41 (*client_info
)->SetStringValue(WPD_CLIENT_NAME
,
42 chrome::kBrowserProcessExecutableName
);
43 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION
, 0);
44 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION
, 0);
45 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION
, 0);
46 (*client_info
)->SetUnsignedIntegerValue(
47 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE
, SECURITY_IMPERSONATION
);
48 (*client_info
)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS
,
53 // Gets the content interface of the portable |device|. On success, returns
54 // the IPortableDeviceContent interface. On failure, returns NULL.
55 base::win::ScopedComPtr
<IPortableDeviceContent
> GetDeviceContent(
56 IPortableDevice
* device
) {
57 base::ThreadRestrictions::AssertIOAllowed();
59 base::win::ScopedComPtr
<IPortableDeviceContent
> content
;
60 if (SUCCEEDED(device
->Content(content
.Receive())))
62 return base::win::ScopedComPtr
<IPortableDeviceContent
>();
65 // On success, returns IEnumPortableDeviceObjectIDs interface to enumerate
66 // the device objects. On failure, returns NULL.
67 // |parent_id| specifies the parent object identifier.
68 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> GetDeviceObjectEnumerator(
69 IPortableDevice
* device
,
70 const base::string16
& parent_id
) {
71 base::ThreadRestrictions::AssertIOAllowed();
73 DCHECK(!parent_id
.empty());
74 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
75 GetDeviceContent(device
);
77 return base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
>();
79 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> enum_object_ids
;
80 if (SUCCEEDED(content
->EnumObjects(0, parent_id
.c_str(), NULL
,
81 enum_object_ids
.Receive())))
82 return enum_object_ids
;
83 return base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
>();
86 // Returns whether the object is a directory/folder/album. |properties_values|
87 // contains the object property key values.
88 bool IsDirectory(IPortableDeviceValues
* properties_values
) {
89 DCHECK(properties_values
);
91 HRESULT hr
= properties_values
->GetGuidValue(WPD_OBJECT_CONTENT_TYPE
,
95 // TODO(kmadhusu): |content_type| can be an image or audio or video or mixed
96 // album. It is not clear whether an album is a collection of physical objects
97 // or virtual objects. Investigate this in detail.
99 // The root storage object describes its content type as
100 // WPD_CONTENT_FUNCTIONAL_OBJECT.
101 return (content_type
== WPD_CONTENT_TYPE_FOLDER
||
102 content_type
== WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT
);
105 // Returns the name of the object from |properties_values|. If the object has
106 // no filename, try to use a friendly name instead. e.g. with MTP storage roots.
107 base::string16
GetObjectName(IPortableDeviceValues
* properties_values
) {
108 DCHECK(properties_values
);
109 base::string16 result
;
110 base::win::ScopedCoMem
<base::char16
> buffer
;
111 HRESULT hr
= properties_values
->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME
,
114 hr
= properties_values
->GetStringValue(WPD_OBJECT_NAME
, &buffer
);
116 result
.assign(buffer
);
120 // Gets the last modified time of the object from the property key values
121 // specified by the |properties_values|. On success, fills in
122 // |last_modified_time|.
123 void GetLastModifiedTime(IPortableDeviceValues
* properties_values
,
124 base::Time
* last_modified_time
) {
125 DCHECK(properties_values
);
126 DCHECK(last_modified_time
);
127 base::win::ScopedPropVariant last_modified_date
;
128 HRESULT hr
= properties_values
->GetValue(WPD_OBJECT_DATE_MODIFIED
,
129 last_modified_date
.Receive());
133 // Some PTP devices don't provide an mtime. Try using the ctime instead.
134 if (last_modified_date
.get().vt
!= VT_DATE
) {
135 last_modified_date
.Reset();
136 HRESULT hr
= properties_values
->GetValue(WPD_OBJECT_DATE_CREATED
,
137 last_modified_date
.Receive());
142 SYSTEMTIME system_time
;
144 if (last_modified_date
.get().vt
== VT_DATE
&&
145 VariantTimeToSystemTime(last_modified_date
.get().date
, &system_time
) &&
146 SystemTimeToFileTime(&system_time
, &file_time
)) {
147 *last_modified_time
= base::Time::FromFileTime(file_time
);
151 // Gets the size of the file object in bytes from the property key values
152 // specified by the |properties_values|. On failure, return -1.
153 int64
GetObjectSize(IPortableDeviceValues
* properties_values
) {
154 DCHECK(properties_values
);
155 ULONGLONG actual_size
;
156 HRESULT hr
= properties_values
->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE
,
158 bool success
= SUCCEEDED(hr
) && (actual_size
<= kint64max
);
159 return success
? static_cast<int64
>(actual_size
) : -1;
162 // Gets the details of the object specified by the |object_id| given the media
163 // transfer protocol |device|. On success, returns true and fills in |name|,
164 // |is_directory|, |size|. |last_modified_time| will be filled in if possible,
165 // but failure to get it doesn't prevent success.
166 bool GetObjectDetails(IPortableDevice
* device
,
167 const base::string16 object_id
,
168 base::string16
* name
,
171 base::Time
* last_modified_time
) {
172 base::ThreadRestrictions::AssertIOAllowed();
174 DCHECK(!object_id
.empty());
176 DCHECK(is_directory
);
178 DCHECK(last_modified_time
);
179 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
180 GetDeviceContent(device
);
184 base::win::ScopedComPtr
<IPortableDeviceProperties
> properties
;
185 HRESULT hr
= content
->Properties(properties
.Receive());
189 base::win::ScopedComPtr
<IPortableDeviceKeyCollection
> properties_to_read
;
190 hr
= properties_to_read
.CreateInstance(__uuidof(PortableDeviceKeyCollection
),
192 CLSCTX_INPROC_SERVER
);
196 if (FAILED(properties_to_read
->Add(WPD_OBJECT_CONTENT_TYPE
)) ||
197 FAILED(properties_to_read
->Add(WPD_OBJECT_FORMAT
)) ||
198 FAILED(properties_to_read
->Add(WPD_OBJECT_ORIGINAL_FILE_NAME
)) ||
199 FAILED(properties_to_read
->Add(WPD_OBJECT_NAME
)) ||
200 FAILED(properties_to_read
->Add(WPD_OBJECT_DATE_MODIFIED
)) ||
201 FAILED(properties_to_read
->Add(WPD_OBJECT_DATE_CREATED
)) ||
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());
218 // Directory entry does not have size and last modified date property key
221 *last_modified_time
= base::Time();
225 // Try to get the last modified time, but don't fail if we can't.
226 GetLastModifiedTime(properties_values
.get(), last_modified_time
);
228 int64 object_size
= GetObjectSize(properties_values
.get());
235 // Creates an MTP device object entry for the given |device| and |object_id|.
236 // On success, returns true and fills in |entry|.
237 MTPDeviceObjectEntry
GetMTPDeviceObjectEntry(IPortableDevice
* device
,
238 const base::string16
& object_id
) {
239 base::ThreadRestrictions::AssertIOAllowed();
241 DCHECK(!object_id
.empty());
245 base::Time last_modified_time
;
246 MTPDeviceObjectEntry entry
;
247 if (GetObjectDetails(device
, object_id
, &name
, &is_directory
, &size
,
248 &last_modified_time
)) {
249 entry
= MTPDeviceObjectEntry(object_id
, name
, is_directory
, size
,
255 // Gets the entries of the directory specified by |directory_object_id| from
256 // the given MTP |device|. To request a specific object entry, put the object
257 // name in |object_name|. Leave |object_name| blank to request all entries. On
258 // success returns true and set |object_entries|.
259 bool GetMTPDeviceObjectEntries(IPortableDevice
* device
,
260 const base::string16
& directory_object_id
,
261 const base::string16
& object_name
,
262 MTPDeviceObjectEntries
* object_entries
) {
263 base::ThreadRestrictions::AssertIOAllowed();
265 DCHECK(!directory_object_id
.empty());
266 DCHECK(object_entries
);
267 base::win::ScopedComPtr
<IEnumPortableDeviceObjectIDs
> enum_object_ids
=
268 GetDeviceObjectEnumerator(device
, directory_object_id
);
269 if (!enum_object_ids
.get())
272 // Loop calling Next() while S_OK is being returned.
273 const DWORD num_objects_to_request
= 10;
274 const bool get_all_entries
= object_name
.empty();
275 for (HRESULT hr
= S_OK
; hr
== S_OK
;) {
276 DWORD num_objects_fetched
= 0;
277 scoped_ptr
<base::char16
*[]> object_ids(
278 new base::char16
*[num_objects_to_request
]);
279 hr
= enum_object_ids
->Next(num_objects_to_request
,
281 &num_objects_fetched
);
282 for (DWORD i
= 0; i
< num_objects_fetched
; ++i
) {
283 MTPDeviceObjectEntry entry
=
284 GetMTPDeviceObjectEntry(device
, object_ids
[i
]);
285 if (entry
.object_id
.empty())
287 if (get_all_entries
) {
288 object_entries
->push_back(entry
);
289 } else if (entry
.name
== object_name
) {
290 object_entries
->push_back(entry
); // Object entry found.
294 for (DWORD i
= 0; i
< num_objects_fetched
; ++i
)
295 CoTaskMemFree(object_ids
[i
]);
302 base::win::ScopedComPtr
<IPortableDevice
> OpenDevice(
303 const base::string16
& pnp_device_id
) {
304 base::ThreadRestrictions::AssertIOAllowed();
305 DCHECK(!pnp_device_id
.empty());
306 base::win::ScopedComPtr
<IPortableDeviceValues
> client_info
;
307 if (!GetClientInformation(&client_info
))
308 return base::win::ScopedComPtr
<IPortableDevice
>();
309 base::win::ScopedComPtr
<IPortableDevice
> device
;
310 HRESULT hr
= device
.CreateInstance(__uuidof(PortableDevice
), NULL
,
311 CLSCTX_INPROC_SERVER
);
313 return base::win::ScopedComPtr
<IPortableDevice
>();
315 hr
= device
->Open(pnp_device_id
.c_str(), client_info
.get());
318 if (hr
== E_ACCESSDENIED
)
319 DPLOG(ERROR
) << "Access denied to open the device";
320 return base::win::ScopedComPtr
<IPortableDevice
>();
323 base::File::Error
GetFileEntryInfo(
324 IPortableDevice
* device
,
325 const base::string16
& object_id
,
326 base::File::Info
* file_entry_info
) {
328 DCHECK(!object_id
.empty());
329 DCHECK(file_entry_info
);
330 MTPDeviceObjectEntry entry
= GetMTPDeviceObjectEntry(device
, object_id
);
331 if (entry
.object_id
.empty())
332 return base::File::FILE_ERROR_NOT_FOUND
;
334 file_entry_info
->size
= entry
.size
;
335 file_entry_info
->is_directory
= entry
.is_directory
;
336 file_entry_info
->is_symbolic_link
= false;
337 file_entry_info
->last_modified
= entry
.last_modified_time
;
338 file_entry_info
->last_accessed
= entry
.last_modified_time
;
339 file_entry_info
->creation_time
= base::Time();
340 return base::File::FILE_OK
;
343 bool GetDirectoryEntries(IPortableDevice
* device
,
344 const base::string16
& directory_object_id
,
345 MTPDeviceObjectEntries
* object_entries
) {
346 return GetMTPDeviceObjectEntries(device
, directory_object_id
,
347 base::string16(), object_entries
);
350 HRESULT
GetFileStreamForObject(IPortableDevice
* device
,
351 const base::string16
& file_object_id
,
352 IStream
** file_stream
,
353 DWORD
* optimal_transfer_size
) {
354 base::ThreadRestrictions::AssertIOAllowed();
356 DCHECK(!file_object_id
.empty());
357 base::win::ScopedComPtr
<IPortableDeviceContent
> content
=
358 GetDeviceContent(device
);
362 base::win::ScopedComPtr
<IPortableDeviceResources
> resources
;
363 HRESULT hr
= content
->Transfer(resources
.Receive());
366 return resources
->GetStream(file_object_id
.c_str(), WPD_RESOURCE_DEFAULT
,
367 STGM_READ
, optimal_transfer_size
,
371 DWORD
CopyDataChunkToLocalFile(IStream
* stream
,
372 const base::FilePath
& local_path
,
373 size_t optimal_transfer_size
) {
374 base::ThreadRestrictions::AssertIOAllowed();
376 DCHECK(!local_path
.empty());
377 if (optimal_transfer_size
== 0U)
379 DWORD bytes_read
= 0;
381 HRESULT hr
= stream
->Read(base::WriteInto(&buffer
, optimal_transfer_size
+ 1),
382 optimal_transfer_size
, &bytes_read
);
383 // IStream::Read() returns S_FALSE when the actual number of bytes read from
384 // the stream object is less than the number of bytes requested (aka
385 // |optimal_transfer_size|). This indicates the end of the stream has been
389 DCHECK_GT(bytes_read
, 0U);
390 CHECK_LE(bytes_read
, buffer
.length());
392 base::checked_cast
<int>(
394 base::checked_cast
<DWORD
>(buffer
.length())));
395 return base::AppendToFile(local_path
, buffer
.c_str(), data_len
) ? data_len
399 base::string16
GetObjectIdFromName(IPortableDevice
* device
,
400 const base::string16
& parent_id
,
401 const base::string16
& object_name
) {
402 MTPDeviceObjectEntries object_entries
;
403 if (!GetMTPDeviceObjectEntries(device
, parent_id
, object_name
,
405 object_entries
.empty())
406 return base::string16();
407 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
408 // the same name. Handle the situation gracefully. Refer to crbug.com/169930
410 DCHECK_EQ(1U, object_entries
.size());
411 return object_entries
[0].object_id
;
414 } // namespace media_transfer_protocol