Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / media_galleries / win / mtp_device_operations_util.cc
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.
4
5 #include "chrome/browser/media_galleries/win/mtp_device_operations_util.h"
6
7 #include <portabledevice.h>
8
9 #include <algorithm>
10
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/common/chrome_constants.h"
22
23 namespace media_transfer_protocol {
24
25 namespace {
26
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;
39   }
40
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;
51 }
52
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>();
63 }
64
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)
77     return base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs>();
78
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>();
84 }
85
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.
98
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);
103 }
104
105 // Returns the friendly name of the object from the property key values
106 // specified by the |properties_values|.
107 base::string16 GetObjectName(IPortableDeviceValues* properties_values,
108                        bool is_directory) {
109   DCHECK(properties_values);
110   base::win::ScopedCoMem<base::char16> buffer;
111   REFPROPERTYKEY key =
112       is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
113   HRESULT hr = properties_values->GetStringValue(key, &buffer);
114   base::string16 result;
115   if (SUCCEEDED(hr))
116     result.assign(buffer);
117   return result;
118 }
119
120 // Gets the last modified time of the object from the property key values
121 // specified by the |properties_values|. On success, returns true and fills in
122 // |last_modified_time|.
123 bool 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 false;
132
133   bool last_modified_time_set = (last_modified_date.get().vt == VT_DATE);
134   if (last_modified_time_set) {
135     SYSTEMTIME system_time;
136     FILETIME file_time;
137     if (VariantTimeToSystemTime(last_modified_date.get().date, &system_time) &&
138         SystemTimeToFileTime(&system_time, &file_time)) {
139       *last_modified_time = base::Time::FromFileTime(file_time);
140     } else {
141       last_modified_time_set = false;
142     }
143   }
144   return last_modified_time_set;
145 }
146
147 // Gets the size of the file object in bytes from the property key values
148 // specified by the |properties_values|. On success, returns true and fills
149 // in |size|.
150 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
151   DCHECK(properties_values);
152   DCHECK(size);
153   ULONGLONG actual_size;
154   HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE,
155                                                                &actual_size);
156   bool success = SUCCEEDED(hr) && (actual_size <= kint64max);
157   if (success)
158     *size = static_cast<int64>(actual_size);
159   return success;
160 }
161
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| and |last_modified_time|.
165 bool GetObjectDetails(IPortableDevice* device,
166                       const base::string16 object_id,
167                       base::string16* name,
168                       bool* is_directory,
169                       int64* size,
170                       base::Time* last_modified_time) {
171   base::ThreadRestrictions::AssertIOAllowed();
172   DCHECK(device);
173   DCHECK(!object_id.empty());
174   DCHECK(name);
175   DCHECK(is_directory);
176   DCHECK(size);
177   DCHECK(last_modified_time);
178   base::win::ScopedComPtr<IPortableDeviceContent> content =
179       GetDeviceContent(device);
180   if (!content)
181     return false;
182
183   base::win::ScopedComPtr<IPortableDeviceProperties> properties;
184   HRESULT hr = content->Properties(properties.Receive());
185   if (FAILED(hr))
186     return false;
187
188   base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
189   hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
190                                          NULL,
191                                          CLSCTX_INPROC_SERVER);
192   if (FAILED(hr))
193     return false;
194
195   if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
196       FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
197       FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
198       FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
199       FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
200       FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
201     return false;
202
203   base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
204   hr = properties->GetValues(object_id.c_str(),
205                              properties_to_read.get(),
206                              properties_values.Receive());
207   if (FAILED(hr))
208     return false;
209
210   *is_directory = IsDirectory(properties_values.get());
211   *name = GetObjectName(properties_values.get(), *is_directory);
212   if (name->empty())
213     return false;
214
215   if (*is_directory) {
216     // Directory entry does not have size and last modified date property key
217     // values.
218     *size = 0;
219     *last_modified_time = base::Time();
220     return true;
221   }
222   return (GetObjectSize(properties_values.get(), size) &&
223       GetLastModifiedTime(properties_values.get(), last_modified_time));
224 }
225
226 // Creates an MTP device object entry for the given |device| and |object_id|.
227 // On success, returns true and fills in |entry|.
228 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
229                              const base::string16& object_id,
230                              MTPDeviceObjectEntry* entry) {
231   base::ThreadRestrictions::AssertIOAllowed();
232   DCHECK(device);
233   DCHECK(!object_id.empty());
234   DCHECK(entry);
235   base::string16 name;
236   bool is_directory;
237   int64 size;
238   base::Time last_modified_time;
239   if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
240                         &last_modified_time))
241     return false;
242   *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
243                                 last_modified_time);
244   return true;
245 }
246
247 // Gets the entries of the directory specified by |directory_object_id| from
248 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
249 // of the directory. To request a specific object entry, put the object name in
250 // |object_name|. Leave |object_name| blank to request all entries. On
251 // success returns true and set |object_entries|.
252 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
253                                const base::string16& directory_object_id,
254                                const base::string16& object_name,
255                                MTPDeviceObjectEntries* object_entries) {
256   base::ThreadRestrictions::AssertIOAllowed();
257   DCHECK(device);
258   DCHECK(!directory_object_id.empty());
259   DCHECK(object_entries);
260   base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids =
261       GetDeviceObjectEnumerator(device, directory_object_id);
262   if (!enum_object_ids)
263     return false;
264
265   // Loop calling Next() while S_OK is being returned.
266   const DWORD num_objects_to_request = 10;
267   const bool get_all_entries = object_name.empty();
268   for (HRESULT hr = S_OK; hr == S_OK;) {
269     DWORD num_objects_fetched = 0;
270     scoped_ptr<base::char16*[]> object_ids(
271         new base::char16*[num_objects_to_request]);
272     hr = enum_object_ids->Next(num_objects_to_request,
273                                object_ids.get(),
274                                &num_objects_fetched);
275     for (DWORD index = 0; index < num_objects_fetched; ++index) {
276       MTPDeviceObjectEntry entry;
277       if (GetMTPDeviceObjectEntry(device,
278                                   object_ids[index],
279                                   &entry)) {
280         if (get_all_entries) {
281           object_entries->push_back(entry);
282         } else if (entry.name == object_name) {
283           object_entries->push_back(entry);  // Object entry found.
284           break;
285         }
286       }
287     }
288     for (DWORD index = 0; index < num_objects_fetched; ++index)
289       CoTaskMemFree(object_ids[index]);
290   }
291   return true;
292 }
293
294 }  // namespace
295
296 base::win::ScopedComPtr<IPortableDevice> OpenDevice(
297     const base::string16& pnp_device_id) {
298   base::ThreadRestrictions::AssertIOAllowed();
299   DCHECK(!pnp_device_id.empty());
300   base::win::ScopedComPtr<IPortableDeviceValues> client_info;
301   if (!GetClientInformation(&client_info))
302     return base::win::ScopedComPtr<IPortableDevice>();
303   base::win::ScopedComPtr<IPortableDevice> device;
304   HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL,
305                                      CLSCTX_INPROC_SERVER);
306   if (FAILED(hr))
307     return base::win::ScopedComPtr<IPortableDevice>();
308
309   hr = device->Open(pnp_device_id.c_str(), client_info.get());
310   if (SUCCEEDED(hr))
311     return device;
312   if (hr == E_ACCESSDENIED)
313     DPLOG(ERROR) << "Access denied to open the device";
314   return base::win::ScopedComPtr<IPortableDevice>();
315 }
316
317 base::File::Error GetFileEntryInfo(
318     IPortableDevice* device,
319     const base::string16& object_id,
320     base::File::Info* file_entry_info) {
321   DCHECK(device);
322   DCHECK(!object_id.empty());
323   DCHECK(file_entry_info);
324   MTPDeviceObjectEntry entry;
325   if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
326     return base::File::FILE_ERROR_NOT_FOUND;
327
328   file_entry_info->size = entry.size;
329   file_entry_info->is_directory = entry.is_directory;
330   file_entry_info->is_symbolic_link = false;
331   file_entry_info->last_modified = entry.last_modified_time;
332   file_entry_info->last_accessed = entry.last_modified_time;
333   file_entry_info->creation_time = base::Time();
334   return base::File::FILE_OK;
335 }
336
337 bool GetDirectoryEntries(IPortableDevice* device,
338                          const base::string16& directory_object_id,
339                          MTPDeviceObjectEntries* object_entries) {
340   return GetMTPDeviceObjectEntries(device, directory_object_id,
341                                    base::string16(), object_entries);
342 }
343
344 HRESULT GetFileStreamForObject(IPortableDevice* device,
345                                const base::string16& file_object_id,
346                                IStream** file_stream,
347                                DWORD* optimal_transfer_size) {
348   base::ThreadRestrictions::AssertIOAllowed();
349   DCHECK(device);
350   DCHECK(!file_object_id.empty());
351   base::win::ScopedComPtr<IPortableDeviceContent> content =
352       GetDeviceContent(device);
353   if (!content)
354     return E_FAIL;
355
356   base::win::ScopedComPtr<IPortableDeviceResources> resources;
357   HRESULT hr = content->Transfer(resources.Receive());
358   if (FAILED(hr))
359     return hr;
360   return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
361                               STGM_READ, optimal_transfer_size,
362                               file_stream);
363 }
364
365 DWORD CopyDataChunkToLocalFile(IStream* stream,
366                                const base::FilePath& local_path,
367                                size_t optimal_transfer_size) {
368   base::ThreadRestrictions::AssertIOAllowed();
369   DCHECK(stream);
370   DCHECK(!local_path.empty());
371   if (optimal_transfer_size == 0U)
372     return 0U;
373   DWORD bytes_read = 0;
374   std::string buffer;
375   HRESULT hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
376                             optimal_transfer_size, &bytes_read);
377   // IStream::Read() returns S_FALSE when the actual number of bytes read from
378   // the stream object is less than the number of bytes requested (aka
379   // |optimal_transfer_size|). This indicates the end of the stream has been
380   // reached.
381   if (FAILED(hr))
382     return 0U;
383   DCHECK_GT(bytes_read, 0U);
384   CHECK_LE(bytes_read, buffer.length());
385   int data_len =
386       base::checked_cast<int>(
387           std::min(bytes_read,
388                    base::checked_cast<DWORD>(buffer.length())));
389   if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != data_len)
390     return 0U;
391   return data_len;
392 }
393
394 base::string16 GetObjectIdFromName(IPortableDevice* device,
395                                    const base::string16& parent_id,
396                                    const base::string16& object_name) {
397   MTPDeviceObjectEntries object_entries;
398   if (!GetMTPDeviceObjectEntries(device, parent_id, object_name,
399                                  &object_entries) ||
400       object_entries.empty())
401     return base::string16();
402   // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
403   // the same name. Handle the situation gracefully. Refer to crbug.com/169930
404   // for more details.
405   DCHECK_EQ(1U, object_entries.size());
406   return object_entries[0].object_id;
407 }
408
409 }  // namespace media_transfer_protocol