- add sources.
[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/safe_numerics.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"
24
25 namespace media_transfer_protocol {
26
27 namespace {
28
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;
41   }
42
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;
53 }
54
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>();
65 }
66
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 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>();
80
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>();
86 }
87
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.
100
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);
105 }
106
107 // Returns the friendly name of the object from the property key values
108 // specified by the |properties_values|.
109 string16 GetObjectName(IPortableDeviceValues* properties_values,
110                        bool is_directory) {
111   DCHECK(properties_values);
112   base::win::ScopedCoMem<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   string16 result;
117   if (SUCCEEDED(hr))
118     result.assign(buffer);
119   return result;
120 }
121
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;
134
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;
144     }
145   }
146   return last_modified_time_set;
147 }
148
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;
162 }
163
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 string16 object_id,
169                       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;
184
185   base::win::ScopedComPtr<IPortableDeviceProperties> properties;
186   HRESULT hr = content->Properties(properties.Receive());
187   if (FAILED(hr))
188     return false;
189
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;
196
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;
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(), *is_directory);
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   return (GetObjectSize(properties_values.get(), size) &&
225       GetLastModifiedTime(properties_values.get(), last_modified_time));
226 }
227
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 string16& object_id,
232                              MTPDeviceObjectEntry* entry) {
233   base::ThreadRestrictions::AssertIOAllowed();
234   DCHECK(device);
235   DCHECK(!object_id.empty());
236   DCHECK(entry);
237   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;
247 }
248
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 string16& directory_object_id,
256                                const 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;
266
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<char16*[]> object_ids(new char16*[num_objects_to_request]);
273     hr = enum_object_ids->Next(num_objects_to_request,
274                                object_ids.get(),
275                                &num_objects_fetched);
276     for (DWORD index = 0; index < num_objects_fetched; ++index) {
277       MTPDeviceObjectEntry entry;
278       if (GetMTPDeviceObjectEntry(device,
279                                   object_ids[index],
280                                   &entry)) {
281         if (get_all_entries) {
282           object_entries->push_back(entry);
283         } else if (entry.name == object_name) {
284           object_entries->push_back(entry);  // Object entry found.
285           break;
286         }
287       }
288     }
289     for (DWORD index = 0; index < num_objects_fetched; ++index)
290       CoTaskMemFree(object_ids[index]);
291   }
292   return true;
293 }
294
295 }  // namespace
296
297 base::win::ScopedComPtr<IPortableDevice> OpenDevice(
298     const string16& pnp_device_id) {
299   base::ThreadRestrictions::AssertIOAllowed();
300   DCHECK(!pnp_device_id.empty());
301   base::win::ScopedComPtr<IPortableDeviceValues> client_info;
302   if (!GetClientInformation(&client_info))
303     return base::win::ScopedComPtr<IPortableDevice>();
304   base::win::ScopedComPtr<IPortableDevice> device;
305   HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL,
306                                      CLSCTX_INPROC_SERVER);
307   if (FAILED(hr))
308     return base::win::ScopedComPtr<IPortableDevice>();
309
310   hr = device->Open(pnp_device_id.c_str(), client_info.get());
311   if (SUCCEEDED(hr))
312     return device;
313   if (hr == E_ACCESSDENIED)
314     DPLOG(ERROR) << "Access denied to open the device";
315   return base::win::ScopedComPtr<IPortableDevice>();
316 }
317
318 base::PlatformFileError GetFileEntryInfo(
319     IPortableDevice* device,
320     const string16& object_id,
321     base::PlatformFileInfo* file_entry_info) {
322   DCHECK(device);
323   DCHECK(!object_id.empty());
324   DCHECK(file_entry_info);
325   MTPDeviceObjectEntry entry;
326   if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
327     return base::PLATFORM_FILE_ERROR_NOT_FOUND;
328
329   file_entry_info->size = entry.size;
330   file_entry_info->is_directory = entry.is_directory;
331   file_entry_info->is_symbolic_link = false;
332   file_entry_info->last_modified = entry.last_modified_time;
333   file_entry_info->last_accessed = entry.last_modified_time;
334   file_entry_info->creation_time = base::Time();
335   return base::PLATFORM_FILE_OK;
336 }
337
338 bool GetDirectoryEntries(IPortableDevice* device,
339                          const string16& directory_object_id,
340                          MTPDeviceObjectEntries* object_entries) {
341   return GetMTPDeviceObjectEntries(device, directory_object_id, string16(),
342                                    object_entries);
343 }
344
345 HRESULT GetFileStreamForObject(IPortableDevice* device,
346                                const string16& file_object_id,
347                                IStream** file_stream,
348                                DWORD* optimal_transfer_size) {
349   base::ThreadRestrictions::AssertIOAllowed();
350   DCHECK(device);
351   DCHECK(!file_object_id.empty());
352   base::win::ScopedComPtr<IPortableDeviceContent> content =
353       GetDeviceContent(device);
354   if (!content)
355     return E_FAIL;
356
357   base::win::ScopedComPtr<IPortableDeviceResources> resources;
358   HRESULT hr = content->Transfer(resources.Receive());
359   if (FAILED(hr))
360     return hr;
361   return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
362                               STGM_READ, optimal_transfer_size,
363                               file_stream);
364 }
365
366 DWORD CopyDataChunkToLocalFile(IStream* stream,
367                                const base::FilePath& local_path,
368                                size_t optimal_transfer_size) {
369   base::ThreadRestrictions::AssertIOAllowed();
370   DCHECK(stream);
371   DCHECK(!local_path.empty());
372   if (optimal_transfer_size == 0U)
373     return 0U;
374   DWORD bytes_read = 0;
375   std::string buffer;
376   HRESULT hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
377                             optimal_transfer_size, &bytes_read);
378   // IStream::Read() returns S_FALSE when the actual number of bytes read from
379   // the stream object is less than the number of bytes requested (aka
380   // |optimal_transfer_size|). This indicates the end of the stream has been
381   // reached.
382   if (FAILED(hr))
383     return 0U;
384   DCHECK_GT(bytes_read, 0U);
385   CHECK_LE(bytes_read, buffer.length());
386   int data_len =
387       base::checked_numeric_cast<int>(
388           std::min(bytes_read,
389                    base::checked_numeric_cast<DWORD>(buffer.length())));
390   if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != data_len)
391     return 0U;
392   return data_len;
393 }
394
395 string16 GetObjectIdFromName(IPortableDevice* device,
396                              const string16& parent_id,
397                              const string16& object_name) {
398   MTPDeviceObjectEntries object_entries;
399   if (!GetMTPDeviceObjectEntries(device, parent_id, object_name,
400                                  &object_entries) ||
401       object_entries.empty())
402     return string16();
403   // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
404   // the same name. Handle the situation gracefully. Refer to crbug.com/169930
405   // for more details.
406   DCHECK_EQ(1U, object_entries.size());
407   return object_entries[0].object_id;
408 }
409
410 }  // namespace media_transfer_protocol