Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(217)

Side by Side Diff: chrome/browser/media_gallery/win/mtp_device_operations_util.cc

Issue 11297002: [Media Gallery] Added code to support mtp device media file system on Windows. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed review comments Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 // MTPDeviceOperationsUtil implementation.
6
7 #include "chrome/browser/media_gallery/win/mtp_device_operations_util.h"
8
9 #include <portabledevice.h>
10
11 #include "base/logging.h"
12 #include "base/string_util.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "base/time.h"
15 #include "base/win/scoped_co_mem.h"
16 #include "chrome/browser/system_monitor/removable_device_constants.h"
17 #include "content/public/browser/browser_thread.h"
18
19 namespace chrome {
20
21 namespace {
22
23 // Name of the client application that communicates with the MTP device.
24 const char16 kClientName[] = L"Chromium";
Peter Kasting 2012/11/01 21:38:37 This is a bit unusual. I assume it doesn't matter
kmadhusu 2012/11/02 03:27:16 Fixed. I didn't know about this constant. It doesn
25
26 // On success, returns true and updates |client_info| with a reference to an
27 // IPortableDeviceValues interface that holds information about the
28 // application that communicates with the device.
29 bool GetClientInformation(
30 base::win::ScopedComPtr<IPortableDeviceValues>* client_info) {
31 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
Peter Kasting 2012/11/01 21:38:37 Seems like at least some of these utilities won't
kmadhusu 2012/11/02 03:27:16 Most of these functions just reads the properties
32 DCHECK(client_info);
33 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues),
34 NULL, CLSCTX_INPROC_SERVER);
35 if (FAILED(hr)) {
36 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
37 return false;
38 }
39
40 // Attempt to set client details.
41 (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName);
42 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0);
43 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
44 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
45 (*client_info)->SetUnsignedIntegerValue(
46 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION);
47 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS,
48 GENERIC_READ);
49 return true;
50 }
51
52 // Gets the content interface of the portable |device|. On success, returns
53 // true and fills in |content| with the given portable |device| content.
54 bool GetDeviceContent(IPortableDevice* device,
55 IPortableDeviceContent** content) {
56 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
57 DCHECK(device);
58 DCHECK(content);
59 return SUCCEEDED(device->Content(content));
60 }
61
62 // Gets the portable |device| enumerator interface to enumerate the objects.
63 // |parent_id| specifies the parent object identifier. On success, returns
64 // true and fills in |enum_object_ids|. On failure, returns false and
65 // |enum_object_ids| is not set.
66 bool GetDeviceObjectEnumerator(IPortableDevice* device,
67 const string16& parent_id,
68 IEnumPortableDeviceObjectIDs** enum_object_ids) {
69 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
70 DCHECK(device);
71 DCHECK(!parent_id.empty());
72 DCHECK(enum_object_ids);
73 base::win::ScopedComPtr<IPortableDeviceContent> content;
74 if (!GetDeviceContent(device, content.Receive()))
75 return false;
76
77 HRESULT hr = content->EnumObjects(0, parent_id.c_str(), NULL,
78 enum_object_ids);
79 return SUCCEEDED(hr);
80 }
81
82 // Reads data from |stream| into a |data| string.
83 void ReadStream(IStream* stream, size_t size, std::string* data) {
84 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
85 DCHECK(stream);
86 DCHECK_GT(size, 0u);
87 DCHECK(data);
88 DWORD read = 0;
89 HRESULT hr = S_OK;
90 do {
91 std::string buffer;
92 hr = stream->Read(WriteInto(&buffer, size + 1), size, &read);
93 DCHECK(hr == S_OK || hr == S_FALSE || hr == E_PENDING);
94 if (read) {
95 buffer.erase(read);
96 DCHECK_EQ(read, buffer.length());
97 *data += buffer;
98 }
99 } while ((read > 0) && SUCCEEDED(hr));
100 }
101
102 // Returns true, if the object is a directory/folder/album otherwise returns
Peter Kasting 2012/11/01 21:38:37 Nit: First sentence can be just "Returns whether t
kmadhusu 2012/11/02 03:27:16 Done.
103 // false. |properties_values| contains the object property key values.
104 bool IsDirectory(IPortableDeviceValues* properties_values) {
105 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
106 DCHECK(properties_values);
107 GUID content_type;
108 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
109 &content_type);
110 DCHECK(SUCCEEDED(hr));
111 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) ||
112 (content_type == WPD_CONTENT_TYPE_FOLDER) ||
113 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) ||
114 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) ||
115 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) ||
116 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM));
117 }
118
119 // Returns the object extension type based on the object format specified
120 // by the |properties_values|. If the object is an unsupport format, this
121 // function returns an empty string.
122 string16 GetObjectExtension(IPortableDeviceValues* properties_values) {
123 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
124 DCHECK(properties_values);
125 GUID format;
126 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_FORMAT, &format);
127 DCHECK(SUCCEEDED(hr));
128 if (IsEqualGUID(WPD_OBJECT_FORMAT_BMP, format))
129 return L".bmp";
Peter Kasting 2012/11/01 21:38:37 Nit: Extra space (several places)
kmadhusu 2012/11/02 03:27:16 oops. Fixed.
130 if (IsEqualGUID(WPD_OBJECT_FORMAT_EXIF, format))
131 return L".jpg";
132 if (IsEqualGUID(WPD_OBJECT_FORMAT_GIF, format))
133 return L".gif";
134 if (IsEqualGUID(WPD_OBJECT_FORMAT_JFIF, format))
135 return L".jfif";
136 if (IsEqualGUID(WPD_OBJECT_FORMAT_JP2, format))
137 return L".jxr";
138 if (IsEqualGUID(WPD_OBJECT_FORMAT_JPEGXR, format))
139 return L".jpx";
140 if (IsEqualGUID(WPD_OBJECT_FORMAT_JPX, format))
141 return L".jp2";
142 if (IsEqualGUID(WPD_OBJECT_FORMAT_PICT, format))
143 return L".pic";
144 if (IsEqualGUID(WPD_OBJECT_FORMAT_PNG, format))
145 return L".png";
146 return string16();
147 }
148
149 // Gets the friendly name of the object from the property key values specified
150 // by the |properties_values|. On success, returns true and fills in |name|.
151 // On failure, returns false and |name| is not set.
152 bool GetObjectName(IPortableDeviceValues* properties_values,
153 bool is_directory,
154 string16* name) {
155 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
156 DCHECK(properties_values);
157 DCHECK(name);
158 base::win::ScopedCoMem<char16> buffer;
159 HRESULT hr = properties_values->GetStringValue(WPD_OBJECT_NAME, &buffer);
160 DCHECK(SUCCEEDED(hr));
161 *name = static_cast<const char16*>(buffer);
162 if (name->empty())
163 return false;
Peter Kasting 2012/11/01 21:38:37 Here you wrote |name| and then returned false, whi
kmadhusu 2012/11/02 03:27:16 I would like to return bool. Fixed the comment to
Peter Kasting 2012/11/02 04:08:11 What does returning a bool buy you over just retur
kmadhusu 2012/11/29 23:58:14 Your argument convinced me. Returning the name ins
164 if (is_directory)
165 return true;
166
167 // Some MTP device objects does not have extension as part of their name.
168 // Check to see if an extension exists in the given object name.
169 if (name->find_first_of(L".") == string16::npos)
170 *name += GetObjectExtension(properties_values);
171 return true;
172 }
173
174 // Gets the last modified time of the object from the property key values
175 // specified by the |properties_values|. On success, returns true and fills in
176 // |last_modified_time|. On failure, returns false and |last_modified_time| is
177 // not set.
178 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
179 base::Time* last_modified_time) {
180 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
181 DCHECK(properties_values);
182 DCHECK(last_modified_time);
183 PROPVARIANT last_modified_date = {0};
184 PropVariantInit(&last_modified_date);
185 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
186 &last_modified_date);
187 if (FAILED(hr))
188 return false;
189
190 if (SUCCEEDED(hr) && last_modified_date.vt == VT_DATE) {
Peter Kasting 2012/11/01 21:38:37 Nit: SUCCEEDED(hr) is guaranteed true here
kmadhusu 2012/11/02 03:27:16 Removed SUCCEEDED(hr).
191 SYSTEMTIME system_time;
192 FILETIME file_time;
193 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
194 SystemTimeToFileTime(&system_time, &file_time))
195 *last_modified_time = base::Time::FromFileTime(file_time);
196 }
197 PropVariantClear(&last_modified_date);
198 return true;
Peter Kasting 2012/11/01 21:38:37 Doesn't seem like this should return true in cases
kmadhusu 2012/11/02 03:27:16 Some MTP devices does not set the modification tim
Peter Kasting 2012/11/02 04:08:11 The point is that last_modified_time may be entire
kmadhusu 2012/11/29 23:58:14 I have a test device (Canon powershot A70 camera)
199 }
200
201 // Gets the size of the file object in bytes from the property key values
202 // specified by the |properties_values|. On success, returns true and fills
203 // in |size|. On failure, returns false and |size| is not set.
204 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
205 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
206 DCHECK(properties_values);
207 DCHECK(size);
208 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(
209 WPD_OBJECT_SIZE, reinterpret_cast<ULONGLONG*>(size));
210 return SUCCEEDED(hr);
211 }
212
213 // Gets the details of the object specified by the |object_id| given the media
214 // transfer protocol |device|. On success, returns true and fills in |name|,
215 // |is_directory|, |size| and |last_modified_time|. On failure, returns false
216 // and all the output params are not set.
217 bool GetObjectDetails(IPortableDevice* device,
218 const string16 object_id,
219 string16* name,
220 bool* is_directory,
221 int64* size,
222 base::Time* last_modified_time) {
223 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
224 DCHECK(device);
225 DCHECK(!object_id.empty());
226 DCHECK(name);
227 DCHECK(is_directory);
228 DCHECK(size);
229 DCHECK(last_modified_time);
230 base::win::ScopedComPtr<IPortableDeviceContent> content;
231 if (!GetDeviceContent(device, content.Receive()))
232 return false;
233
234 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
235 HRESULT hr = content->Properties(properties.Receive());
236 if (FAILED(hr))
237 return false;
238
239 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
240 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
241 NULL,
242 CLSCTX_INPROC_SERVER);
243 if (FAILED(hr))
244 return false;
245
246 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
247 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
248 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
249 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
250 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
251 return false;
252
253 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
254 hr = properties->GetValues(object_id.c_str(),
255 properties_to_read.get(),
256 properties_values.Receive());
257 if (FAILED(hr))
258 return false;
259
260 *is_directory = IsDirectory(properties_values.get());
261 if (!GetObjectName(properties_values.get(), *is_directory, name))
262 return false;
Peter Kasting 2012/11/01 21:38:37 More cases where you return false after writing to
kmadhusu 2012/11/02 03:27:16 Fixed comment.
263
264 if (*is_directory) {
265 // Directory entry does not have size and last modified date property key
266 // values.
267 *size = 0;
268 *last_modified_time = base::Time();
269 return true;
270 }
271 return (GetObjectSize(properties_values.get(), size) &&
272 GetLastModifiedTime(properties_values.get(), last_modified_time));
273 }
274
275 // Creates a MTP device object entry for the given |device| and |object_id|.
Peter Kasting 2012/11/01 21:38:37 Nit: a -> an
kmadhusu 2012/11/02 03:27:16 Done.
276 // On success, returns true and fills in |entry|. On failure, returns false
277 // and |entry| is not set.
278 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
279 const string16& object_id,
280 MTPDeviceObjectEntry* entry) {
281 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
282 DCHECK(device);
283 DCHECK(!object_id.empty());
284 DCHECK(entry);
285 string16 name;
286 bool is_directory;
287 int64 size;
288 base::Time last_modified_time;
289 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
290 &last_modified_time))
291 return false;
292 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
293 last_modified_time);
294 return true;
295 }
296
297 } // namespace
298
299
300 // MTPDeviceOperationsUtil ----------------------------------------------------
301
302 // static
303 bool MTPDeviceOperationsUtil::OpenDevice(
304 const string16& pnp_device_id,
305 base::win::ScopedComPtr<IPortableDevice>* device) {
306 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
307 DCHECK(!pnp_device_id.empty());
308 DCHECK(device);
309 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
310 if (!GetClientInformation(&client_info))
311 return false;
312 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
313 CLSCTX_INPROC_SERVER);
314 if (FAILED(hr))
315 return false;
316
317 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
318 if (SUCCEEDED(hr))
319 return true;
320 if (hr == E_ACCESSDENIED)
321 DPLOG(ERROR) << "Access denied to open the device";
322 return false;
323 }
324
325 // static
326 base::PlatformFileError MTPDeviceOperationsUtil::GetFileEntryInfo(
327 IPortableDevice* device,
328 const string16& object_id,
329 base::PlatformFileInfo* file_entry_info) {
330 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
331 DCHECK(device);
332 DCHECK(!object_id.empty());
333 DCHECK(file_entry_info);
334 MTPDeviceObjectEntry entry;
335 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
336 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
337
338 file_entry_info->size = entry.size();
339 file_entry_info->is_directory = entry.is_directory();
340 file_entry_info->is_symbolic_link = false;
341 file_entry_info->last_modified = entry.last_modified_time();
342 file_entry_info->last_accessed = entry.last_modified_time();
343 file_entry_info->creation_time = base::Time();
344 return base::PLATFORM_FILE_OK;
345 }
346
347 // static
348 bool MTPDeviceOperationsUtil::GetDirectoryEntries(
349 IPortableDevice* device,
350 const string16& directory_object_id,
351 MTPDeviceObjectEntries* object_entries) {
352 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
353 DCHECK(device);
354 DCHECK(!directory_object_id.empty());
355 DCHECK(object_entries);
356 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
357 if (!GetDeviceObjectEnumerator(device, directory_object_id,
358 enum_object_ids.Receive()))
359 return false;
360
361 // Loop calling Next() while S_OK is being returned.
362 DWORD num_objects_to_request = 10;
363 HRESULT hr = S_OK;
364 while (hr == S_OK) {
Peter Kasting 2012/11/01 21:38:37 Nit: Or for (HRESULT hr = S_OK; hr == S_OK; ) {
kmadhusu 2012/11/02 03:27:16 Done.
365 DWORD num_objects_fetched = 0;
Peter Kasting 2012/11/01 21:38:37 Nit: extra space
kmadhusu 2012/11/02 03:27:16 Done.
366 scoped_array<char16*> object_ids(new char16*[num_objects_to_request]);
367 hr = enum_object_ids->Next(num_objects_to_request, object_ids.get(),
368 &num_objects_fetched);
369 for (DWORD index = 0; index < num_objects_fetched; index++) {
Peter Kasting 2012/11/01 21:38:37 Nit: Prefer preincrement whenever possible
kmadhusu 2012/11/02 03:27:16 Done.
370 MTPDeviceObjectEntry entry;
371 if (GetMTPDeviceObjectEntry(device, object_ids[index], &entry))
372 object_entries->push_back(entry);
373 }
374 }
375 return true;
376 }
377
378 // static
379 bool MTPDeviceOperationsUtil::GetFileObjectData(IPortableDevice* device,
380 const string16& file_object_id,
381 std::string* file_data) {
382 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
383 DCHECK(device);
384 DCHECK(!file_object_id.empty());
385 DCHECK(file_data);
386 base::win::ScopedComPtr<IPortableDeviceContent> content;
387 if (!GetDeviceContent(device, content.Receive()))
388 return false;
389
390 base::win::ScopedComPtr<IPortableDeviceResources> resources;
391 HRESULT hr = content->Transfer(resources.Receive());
392 if (FAILED(hr))
393 return false;
394
395 base::win::ScopedComPtr<IStream> file_stream;
396 DWORD optimal_transfer_size = 0;
397 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
398 STGM_READ, &optimal_transfer_size,
399 file_stream.Receive());
400 if (FAILED(hr))
401 return false;
402 ReadStream(file_stream.get(), optimal_transfer_size, file_data);
403 return true;
404 }
405
406 // static
407 string16 MTPDeviceOperationsUtil::GetObjectIdFromName(
408 IPortableDevice* device,
409 const string16& parent_id,
410 const string16& object_name) {
Peter Kasting 2012/11/01 21:38:37 This function seems very similar to GetDirectoryEn
kmadhusu 2012/11/02 03:27:16 Done. Created a helper function "GetMTPDeviceObjec
411 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
412 DCHECK(device);
413 DCHECK(!parent_id.empty());
414 DCHECK(!object_name.empty());
415 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
416 if (!GetDeviceObjectEnumerator(device, parent_id, enum_object_ids.Receive()))
417 return string16();
418
419 DWORD num_objects_to_request = 10;
420 HRESULT hr = S_OK;
421 while (hr == S_OK) {
422 DWORD num_objects_fetched = 0;
423 scoped_array<char16*> object_ids(new char16*[num_objects_to_request]);
424 hr = enum_object_ids->Next(num_objects_to_request, object_ids.get(),
425 &num_objects_fetched);
426 for (DWORD index = 0; index < num_objects_fetched; index++) {
427 MTPDeviceObjectEntry entry;
428 if (!GetMTPDeviceObjectEntry(device, object_ids[index], &entry))
429 return string16();
430 if (entry.file_name() == object_name)
431 return entry.object_id();
432 }
433 }
434 return string16();
435 }
436
437 MTPDeviceOperationsUtil::MTPDeviceOperationsUtil() {
438 }
439
440 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698