Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / media_galleries / win / mtp_device_operations_util.cc
blob33559c2d064f650fb798abcf2efc0cfef821b19a
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>
9 #include <algorithm>
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 {
25 namespace {
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();
33 DCHECK(client_info);
34 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues),
35 NULL, CLSCTX_INPROC_SERVER);
36 if (FAILED(hr)) {
37 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
38 return false;
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,
49 GENERIC_READ);
50 return true;
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();
58 DCHECK(device);
59 base::win::ScopedComPtr<IPortableDeviceContent> content;
60 if (SUCCEEDED(device->Content(content.Receive())))
61 return content;
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();
72 DCHECK(device);
73 DCHECK(!parent_id.empty());
74 base::win::ScopedComPtr<IPortableDeviceContent> content =
75 GetDeviceContent(device);
76 if (!content.get())
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);
90 GUID content_type;
91 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
92 &content_type);
93 if (FAILED(hr))
94 return false;
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,
112 &buffer);
113 if (FAILED(hr))
114 hr = properties_values->GetStringValue(WPD_OBJECT_NAME, &buffer);
115 if (SUCCEEDED(hr))
116 result.assign(buffer);
117 return result;
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());
130 if (FAILED(hr))
131 return;
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());
138 if (FAILED(hr))
139 return;
142 SYSTEMTIME system_time;
143 FILETIME file_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,
157 &actual_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,
169 bool* is_directory,
170 int64* size,
171 base::Time* last_modified_time) {
172 base::ThreadRestrictions::AssertIOAllowed();
173 DCHECK(device);
174 DCHECK(!object_id.empty());
175 DCHECK(name);
176 DCHECK(is_directory);
177 DCHECK(size);
178 DCHECK(last_modified_time);
179 base::win::ScopedComPtr<IPortableDeviceContent> content =
180 GetDeviceContent(device);
181 if (!content.get())
182 return false;
184 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
185 HRESULT hr = content->Properties(properties.Receive());
186 if (FAILED(hr))
187 return false;
189 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
190 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
191 NULL,
192 CLSCTX_INPROC_SERVER);
193 if (FAILED(hr))
194 return false;
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)))
203 return false;
205 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
206 hr = properties->GetValues(object_id.c_str(),
207 properties_to_read.get(),
208 properties_values.Receive());
209 if (FAILED(hr))
210 return false;
212 *is_directory = IsDirectory(properties_values.get());
213 *name = GetObjectName(properties_values.get());
214 if (name->empty())
215 return false;
217 if (*is_directory) {
218 // Directory entry does not have size and last modified date property key
219 // values.
220 *size = 0;
221 *last_modified_time = base::Time();
222 return true;
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());
229 if (object_size < 0)
230 return false;
231 *size = object_size;
232 return true;
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();
240 DCHECK(device);
241 DCHECK(!object_id.empty());
242 base::string16 name;
243 bool is_directory;
244 int64 size;
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,
250 last_modified_time);
252 return entry;
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();
264 DCHECK(device);
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())
270 return false;
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,
280 object_ids.get(),
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())
286 continue;
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.
291 break;
294 for (DWORD i = 0; i < num_objects_fetched; ++i)
295 CoTaskMemFree(object_ids[i]);
297 return true;
300 } // namespace
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);
312 if (FAILED(hr))
313 return base::win::ScopedComPtr<IPortableDevice>();
315 hr = device->Open(pnp_device_id.c_str(), client_info.get());
316 if (SUCCEEDED(hr))
317 return device;
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) {
327 DCHECK(device);
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();
355 DCHECK(device);
356 DCHECK(!file_object_id.empty());
357 base::win::ScopedComPtr<IPortableDeviceContent> content =
358 GetDeviceContent(device);
359 if (!content.get())
360 return E_FAIL;
362 base::win::ScopedComPtr<IPortableDeviceResources> resources;
363 HRESULT hr = content->Transfer(resources.Receive());
364 if (FAILED(hr))
365 return hr;
366 return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
367 STGM_READ, optimal_transfer_size,
368 file_stream);
371 DWORD CopyDataChunkToLocalFile(IStream* stream,
372 const base::FilePath& local_path,
373 size_t optimal_transfer_size) {
374 base::ThreadRestrictions::AssertIOAllowed();
375 DCHECK(stream);
376 DCHECK(!local_path.empty());
377 if (optimal_transfer_size == 0U)
378 return 0U;
379 DWORD bytes_read = 0;
380 std::string buffer;
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
386 // reached.
387 if (FAILED(hr))
388 return 0U;
389 DCHECK_GT(bytes_read, 0U);
390 CHECK_LE(bytes_read, buffer.length());
391 int data_len =
392 base::checked_cast<int>(
393 std::min(bytes_read,
394 base::checked_cast<DWORD>(buffer.length())));
395 return base::AppendToFile(local_path, buffer.c_str(), data_len) ? data_len
396 : 0;
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,
404 &object_entries) ||
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
409 // for more details.
410 DCHECK_EQ(1U, object_entries.size());
411 return object_entries[0].object_id;
414 } // namespace media_transfer_protocol