Upstream version 10.39.225.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/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"
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 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;
118 }
119
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;
132
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;
140   }
141
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);
148   }
149 }
150
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;
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|. |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)
182     return false;
183
184   base::win::ScopedComPtr<IPortableDeviceProperties> properties;
185   HRESULT hr = content->Properties(properties.Receive());
186   if (FAILED(hr))
187     return false;
188
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;
195
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;
204
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;
211
212   *is_directory = IsDirectory(properties_values.get());
213   *name = GetObjectName(properties_values.get());
214   if (name->empty())
215     return false;
216
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;
223   }
224
225   // Try to get the last modified time, but don't fail if we can't.
226   GetLastModifiedTime(properties_values.get(), last_modified_time);
227
228   int64 object_size = GetObjectSize(properties_values.get());
229   if (object_size < 0)
230     return false;
231   *size = object_size;
232   return true;
233 }
234
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);
251   }
252   return entry;
253 }
254
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)
270     return false;
271
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;
292       }
293     }
294     for (DWORD i = 0; i < num_objects_fetched; ++i)
295       CoTaskMemFree(object_ids[i]);
296   }
297   return true;
298 }
299
300 }  // namespace
301
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>();
314
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>();
321 }
322
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;
333
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;
341 }
342
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);
348 }
349
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)
360     return E_FAIL;
361
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);
369 }
370
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(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   if (base::AppendToFile(local_path, buffer.c_str(), data_len) != data_len)
396     return 0U;
397   return data_len;
398 }
399
400 base::string16 GetObjectIdFromName(IPortableDevice* device,
401                                    const base::string16& parent_id,
402                                    const base::string16& object_name) {
403   MTPDeviceObjectEntries object_entries;
404   if (!GetMTPDeviceObjectEntries(device, parent_id, object_name,
405                                  &object_entries) ||
406       object_entries.empty())
407     return base::string16();
408   // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
409   // the same name. Handle the situation gracefully. Refer to crbug.com/169930
410   // for more details.
411   DCHECK_EQ(1U, object_entries.size());
412   return object_entries[0].object_id;
413 }
414
415 }  // namespace media_transfer_protocol