Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / media_galleries / win / mtp_device_operations_util.cc
blobf2c681d756034e416da6d60162ec4ea61576df01
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/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 {
27 namespace {
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();
35 DCHECK(client_info);
36 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues),
37 NULL, CLSCTX_INPROC_SERVER);
38 if (FAILED(hr)) {
39 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
40 return false;
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,
51 GENERIC_READ);
52 return true;
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();
60 DCHECK(device);
61 base::win::ScopedComPtr<IPortableDeviceContent> content;
62 if (SUCCEEDED(device->Content(content.Receive())))
63 return content;
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();
74 DCHECK(device);
75 DCHECK(!parent_id.empty());
76 base::win::ScopedComPtr<IPortableDeviceContent> content =
77 GetDeviceContent(device);
78 if (!content)
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);
92 GUID content_type;
93 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
94 &content_type);
95 if (FAILED(hr))
96 return false;
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,
110 bool is_directory) {
111 DCHECK(properties_values);
112 base::win::ScopedCoMem<base::char16> buffer;
113 REFPROPERTYKEY key =
114 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
115 HRESULT hr = properties_values->GetStringValue(key, &buffer);
116 base::string16 result;
117 if (SUCCEEDED(hr))
118 result.assign(buffer);
119 return result;
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());
132 if (FAILED(hr))
133 return false;
135 bool last_modified_time_set = (last_modified_date.get().vt == VT_DATE);
136 if (last_modified_time_set) {
137 SYSTEMTIME system_time;
138 FILETIME file_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);
142 } else {
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
151 // in |size|.
152 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
153 DCHECK(properties_values);
154 DCHECK(size);
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 if (success)
160 *size = static_cast<int64>(actual_size);
161 return success;
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,
170 bool* is_directory,
171 int64* size,
172 base::Time* last_modified_time) {
173 base::ThreadRestrictions::AssertIOAllowed();
174 DCHECK(device);
175 DCHECK(!object_id.empty());
176 DCHECK(name);
177 DCHECK(is_directory);
178 DCHECK(size);
179 DCHECK(last_modified_time);
180 base::win::ScopedComPtr<IPortableDeviceContent> content =
181 GetDeviceContent(device);
182 if (!content)
183 return false;
185 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
186 HRESULT hr = content->Properties(properties.Receive());
187 if (FAILED(hr))
188 return false;
190 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
191 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
192 NULL,
193 CLSCTX_INPROC_SERVER);
194 if (FAILED(hr))
195 return false;
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)))
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(), *is_directory);
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;
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();
234 DCHECK(device);
235 DCHECK(!object_id.empty());
236 DCHECK(entry);
237 base::string16 name;
238 bool is_directory;
239 int64 size;
240 base::Time last_modified_time;
241 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
242 &last_modified_time))
243 return false;
244 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
245 last_modified_time);
246 return true;
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();
259 DCHECK(device);
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)
265 return false;
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,
275 object_ids.get(),
276 &num_objects_fetched);
277 for (DWORD index = 0; index < num_objects_fetched; ++index) {
278 MTPDeviceObjectEntry entry;
279 if (GetMTPDeviceObjectEntry(device,
280 object_ids[index],
281 &entry)) {
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.
286 break;
290 for (DWORD index = 0; index < num_objects_fetched; ++index)
291 CoTaskMemFree(object_ids[index]);
293 return true;
296 } // namespace
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);
308 if (FAILED(hr))
309 return base::win::ScopedComPtr<IPortableDevice>();
311 hr = device->Open(pnp_device_id.c_str(), client_info.get());
312 if (SUCCEEDED(hr))
313 return device;
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) {
323 DCHECK(device);
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();
351 DCHECK(device);
352 DCHECK(!file_object_id.empty());
353 base::win::ScopedComPtr<IPortableDeviceContent> content =
354 GetDeviceContent(device);
355 if (!content)
356 return E_FAIL;
358 base::win::ScopedComPtr<IPortableDeviceResources> resources;
359 HRESULT hr = content->Transfer(resources.Receive());
360 if (FAILED(hr))
361 return hr;
362 return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
363 STGM_READ, optimal_transfer_size,
364 file_stream);
367 DWORD CopyDataChunkToLocalFile(IStream* stream,
368 const base::FilePath& local_path,
369 size_t optimal_transfer_size) {
370 base::ThreadRestrictions::AssertIOAllowed();
371 DCHECK(stream);
372 DCHECK(!local_path.empty());
373 if (optimal_transfer_size == 0U)
374 return 0U;
375 DWORD bytes_read = 0;
376 std::string buffer;
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
382 // reached.
383 if (FAILED(hr))
384 return 0U;
385 DCHECK_GT(bytes_read, 0U);
386 CHECK_LE(bytes_read, buffer.length());
387 int data_len =
388 base::checked_cast<int>(
389 std::min(bytes_read,
390 base::checked_cast<DWORD>(buffer.length())));
391 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != data_len)
392 return 0U;
393 return 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,
401 &object_entries) ||
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
406 // for more details.
407 DCHECK_EQ(1U, object_entries.size());
408 return object_entries[0].object_id;
411 } // namespace media_transfer_protocol