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

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 comment and disabled MediaFileSystemRegistryTest.GalleryNameMTP Created 7 years, 11 months 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 #include "chrome/browser/media_gallery/win/mtp_device_operations_util.h"
6
7 #include <portabledevice.h>
8
9 #include "base/basictypes.h"
10 #include "base/file_path.h"
11 #include "base/file_util.h"
12 #include "base/logging.h"
13 #include "base/string_util.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "base/time.h"
16 #include "base/win/scoped_co_mem.h"
17 #include "chrome/browser/system_monitor/removable_device_constants.h"
18 #include "chrome/common/chrome_constants.h"
19 #include "content/public/browser/browser_thread.h"
20
21 namespace chrome {
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 // true and fills in |content| with the given portable |device| content.
55 bool GetDeviceContent(IPortableDevice* device,
56 IPortableDeviceContent** content) {
57 base::ThreadRestrictions::AssertIOAllowed();
58 DCHECK(device);
59 DCHECK(content);
60 return SUCCEEDED(device->Content(content));
61 }
62
63 // Gets the portable |device| enumerator interface to enumerate the objects.
64 // |parent_id| specifies the parent object identifier. On success, returns
65 // true and fills in |enum_object_ids|.
66 bool GetDeviceObjectEnumerator(IPortableDevice* device,
Lei Zhang 2013/01/14 23:25:30 This can also just return an IEnumPortableDeviceOb
kmadhusu 2013/01/15 19:08:17 Done and also fixed GetDeviceContent.
67 const string16& parent_id,
68 IEnumPortableDeviceObjectIDs** enum_object_ids) {
69 base::ThreadRestrictions::AssertIOAllowed();
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 // Writes data from |stream| to the file specified by |local_path|. On success,
83 // returns true and the stream contents are appended to the file.
84 // TODO(kmadhusu) Deprecate this function after fixing crbug.com/110119.
85 bool WriteStreamContentsToFile(IStream* stream,
86 size_t optimal_transfer_size,
87 const FilePath& local_path) {
88 base::ThreadRestrictions::AssertIOAllowed();
89 DCHECK(stream);
90 DCHECK_GT(optimal_transfer_size, 0u);
Lei Zhang 2013/01/14 23:25:30 I don't see any guarantee from IPortableDeviceReso
kmadhusu 2013/01/15 19:08:17 If the optimal_transfer_size is equal to 0, this f
91 DCHECK(!local_path.empty());
92 DWORD read = 0;
Lei Zhang 2013/01/14 23:25:30 Doesn't IStream::Read return a ULONG? Is a ULONG a
kmadhusu 2013/01/15 19:08:17 As per http://msdn.microsoft.com/en-us/library/win
93 HRESULT hr = S_OK;
94 std::string buffer;
95 do {
96 hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
97 optimal_transfer_size, &read);
98 // IStream::Read() returns S_FALSE when the actual number of bytes read from
99 // the stream object is less than the number of bytes requested (aka
100 // |optimal_transfer_size|). This indicates the end of the stream has been
101 // reached. Therefore, it is fine to return true when Read() returns
102 // S_FALSE.
103 if ((hr != S_OK) && (hr != S_FALSE))
104 return false;
105 if (read) {
106 buffer.erase(read);
Lei Zhang 2013/01/14 23:25:30 Can't you avoid manipulating the buffer, and inste
kmadhusu 2013/01/15 19:08:17 Done.
107 DCHECK_EQ(read, buffer.length());
108 int data_len = static_cast<int>(buffer.length());
109 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) !=
110 data_len)
111 return false;
112 }
113 } while ((read > 0) && SUCCEEDED(hr));
Lei Zhang 2013/01/14 23:25:30 Why do you need the SUCCEEDED() part? You've alrea
kmadhusu 2013/01/15 19:08:17 If |hr| is S_FALSE, SUCCEEDED(hr) will be false. P
Lei Zhang 2013/01/15 21:00:13 No, SUCCEEDED(S_FALSE) returns true. S_ means succ
kmadhusu 2013/01/15 23:41:58 Removed SUCCEEDED(hr).
114 return true;
115 }
116
117 // Returns whether the object is a directory/folder/album. |properties_values|
118 // contains the object property key values.
119 bool IsDirectory(IPortableDeviceValues* properties_values) {
120 DCHECK(properties_values);
121 GUID content_type;
122 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
123 &content_type);
124 if (FAILED(hr))
125 return false;
126 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) ||
Lei Zhang 2013/01/14 23:25:30 I'm not sure albums (aka playlists??) are director
kmadhusu 2013/01/15 19:08:17 As per http://msdn.microsoft.com/en-us/library/win
Lei Zhang 2013/01/15 21:00:13 Based on my understanding of MTP, I don't believe
kmadhusu 2013/01/15 23:41:58 As discussed, removed the album types and added a
127 (content_type == WPD_CONTENT_TYPE_FOLDER) ||
128 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) ||
129 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) ||
130 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) ||
131 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM));
132 }
133
134 // Returns the friendly name of the object from the property key values
135 // specified by the |properties_values|.
136 string16 GetObjectName(IPortableDeviceValues* properties_values,
137 bool is_directory) {
138 DCHECK(properties_values);
139 base::win::ScopedCoMem<char16> buffer;
140 REFPROPERTYKEY key =
141 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
142 HRESULT hr = properties_values->GetStringValue(key, &buffer);
143 string16 result;
144 if (SUCCEEDED(hr))
145 result.assign(buffer);
146 return result;
147 }
148
149 // Gets the last modified time of the object from the property key values
150 // specified by the |properties_values|. On success, returns true and fills in
151 // |last_modified_time|.
152 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
153 base::Time* last_modified_time) {
154 DCHECK(properties_values);
155 DCHECK(last_modified_time);
156 PROPVARIANT last_modified_date = {0};
157 PropVariantInit(&last_modified_date);
158 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
159 &last_modified_date);
160 if (FAILED(hr))
161 return false;
162
163 bool last_modified_time_set = (last_modified_date.vt == VT_DATE);
164 if (last_modified_time_set) {
165 SYSTEMTIME system_time;
166 FILETIME file_time;
167 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
168 SystemTimeToFileTime(&system_time, &file_time))
169 *last_modified_time = base::Time::FromFileTime(file_time);
170 else
171 last_modified_time_set = false;
172 }
173 PropVariantClear(&last_modified_date);
174 return last_modified_time_set;
175 }
176
177 // Gets the size of the file object in bytes from the property key values
178 // specified by the |properties_values|. On success, returns true and fills
179 // in |size|.
180 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
181 DCHECK(properties_values);
182 DCHECK(size);
183 ULONGLONG actual_size;
184 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE,
185 &actual_size);
186 bool success = SUCCEEDED(hr) && (actual_size <= kint64max);
187 if (success)
188 *size = static_cast<int64>(actual_size);
189 return success;
190 }
191
192 // Gets the details of the object specified by the |object_id| given the media
193 // transfer protocol |device|. On success, returns true and fills in |name|,
194 // |is_directory|, |size| and |last_modified_time|.
195 bool GetObjectDetails(IPortableDevice* device,
196 const string16 object_id,
197 string16* name,
198 bool* is_directory,
199 int64* size,
200 base::Time* last_modified_time) {
201 base::ThreadRestrictions::AssertIOAllowed();
202 DCHECK(device);
203 DCHECK(!object_id.empty());
204 DCHECK(name);
205 DCHECK(is_directory);
206 DCHECK(size);
207 DCHECK(last_modified_time);
208 base::win::ScopedComPtr<IPortableDeviceContent> content;
209 if (!GetDeviceContent(device, content.Receive()))
210 return false;
211
212 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
213 HRESULT hr = content->Properties(properties.Receive());
214 if (FAILED(hr))
215 return false;
216
217 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
218 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
219 NULL,
220 CLSCTX_INPROC_SERVER);
221 if (FAILED(hr))
222 return false;
223
224 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
225 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
226 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
227 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
228 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
229 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
230 return false;
231
232 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
233 hr = properties->GetValues(object_id.c_str(),
234 properties_to_read.get(),
235 properties_values.Receive());
236 if (FAILED(hr))
237 return false;
238
239 *is_directory = IsDirectory(properties_values.get());
240 *name = GetObjectName(properties_values.get(), *is_directory);
241 if (name->empty())
242 return false;
243
244 if (*is_directory) {
245 // Directory entry does not have size and last modified date property key
246 // values.
247 *size = 0;
248 *last_modified_time = base::Time();
249 return true;
250 }
251 return (GetObjectSize(properties_values.get(), size) &&
252 GetLastModifiedTime(properties_values.get(), last_modified_time));
253 }
254
255 // Creates an MTP device object entry for the given |device| and |object_id|.
256 // On success, returns true and fills in |entry|.
257 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
258 const string16& object_id,
259 MTPDeviceObjectEntry* entry) {
260 base::ThreadRestrictions::AssertIOAllowed();
261 DCHECK(device);
262 DCHECK(!object_id.empty());
263 DCHECK(entry);
264 string16 name;
265 bool is_directory;
266 int64 size;
267 base::Time last_modified_time;
268 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
269 &last_modified_time))
270 return false;
271 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
272 last_modified_time);
273 return true;
274 }
275
276 // Gets the entries of the directory specified by |directory_object_id| from
277 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
278 // of the directory. To request a specific object entry, set |get_all_entries|
279 // to false and set |object_name| to the name of the required object. On
280 // success returns true and set |object_entries|.
281 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
282 const string16& directory_object_id,
283 bool get_all_entries,
Lei Zhang 2013/01/14 23:25:30 You can just indicate this with a non empty |objec
kmadhusu 2013/01/15 19:08:17 This is more clearer than the suggested solution.
Lei Zhang 2013/01/15 21:00:13 Not particularly. It's not hard to understand "emp
kmadhusu 2013/01/15 23:41:58 Done.
284 const string16& object_name,
285 MTPDeviceObjectEntries* object_entries) {
286 base::ThreadRestrictions::AssertIOAllowed();
287 DCHECK(device);
288 DCHECK(!directory_object_id.empty());
289 DCHECK(object_entries);
290 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
291 if (!GetDeviceObjectEnumerator(device, directory_object_id,
292 enum_object_ids.Receive()))
293 return false;
294
295 // Loop calling Next() while S_OK is being returned.
296 DWORD num_objects_to_request = 10;
Lei Zhang 2013/01/14 23:25:30 const?
kmadhusu 2013/01/15 19:08:17 Done.
297 for (HRESULT hr = S_OK; hr == S_OK;) {
Lei Zhang 2013/01/14 23:25:30 Isn't this just do { } while (hr == S_OK) ?
kmadhusu 2013/01/15 19:08:17 Yes. Leaving it as it is.
298 DWORD num_objects_fetched = 0;
299 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]);
300 hr = enum_object_ids->Next(num_objects_to_request,
301 object_ids.get(),
302 &num_objects_fetched);
303 for (DWORD index = 0; index < num_objects_fetched; ++index) {
304 MTPDeviceObjectEntry entry;
305 if (GetMTPDeviceObjectEntry(device,
306 object_ids[index],
307 &entry)) {
308 if (get_all_entries) {
309 object_entries->push_back(entry);
310 } else if (entry.name == object_name) {
311 object_entries->push_back(entry); // Object entry found.
312 break;
313 }
314 }
315 }
316 for (DWORD index = 0; index < num_objects_fetched; ++index)
317 CoTaskMemFree(object_ids[index]);
318 }
319 return true;
320 }
321
322 } // namespace
323
324 bool OpenDevice(const string16& pnp_device_id,
325 base::win::ScopedComPtr<IPortableDevice>* device) {
326 base::ThreadRestrictions::AssertIOAllowed();
327 DCHECK(!pnp_device_id.empty());
328 DCHECK(device);
329 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
330 if (!GetClientInformation(&client_info))
331 return false;
332 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
333 CLSCTX_INPROC_SERVER);
334 if (FAILED(hr))
335 return false;
336
337 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
338 if (SUCCEEDED(hr))
339 return true;
340 if (hr == E_ACCESSDENIED)
341 DPLOG(ERROR) << "Access denied to open the device";
342 return false;
343 }
344
345 base::PlatformFileError GetFileEntryInfo(
346 IPortableDevice* device,
347 const string16& object_id,
348 base::PlatformFileInfo* file_entry_info) {
349 DCHECK(device);
350 DCHECK(!object_id.empty());
351 DCHECK(file_entry_info);
352 MTPDeviceObjectEntry entry;
353 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
354 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
355
356 file_entry_info->size = entry.size;
357 file_entry_info->is_directory = entry.is_directory;
358 file_entry_info->is_symbolic_link = false;
359 file_entry_info->last_modified = entry.last_modified_time;
360 file_entry_info->last_accessed = entry.last_modified_time;
361 file_entry_info->creation_time = base::Time();
362 return base::PLATFORM_FILE_OK;
363 }
364
365 bool GetDirectoryEntries(IPortableDevice* device,
366 const string16& directory_object_id,
367 MTPDeviceObjectEntries* object_entries) {
368 return GetMTPDeviceObjectEntries(device, directory_object_id, true,
369 string16(), object_entries);
370 }
371
372 bool WriteFileObjectData(IPortableDevice* device,
373 const string16& file_object_id,
374 const FilePath& local_path) {
375 base::ThreadRestrictions::AssertIOAllowed();
376 DCHECK(device);
377 DCHECK(!file_object_id.empty());
378 DCHECK(!local_path.value().empty());
Lei Zhang 2013/01/14 23:25:30 !local_path.empty() ?
kmadhusu 2013/01/15 19:08:17 Done.
379 base::win::ScopedComPtr<IPortableDeviceContent> content;
380 if (!GetDeviceContent(device, content.Receive()))
381 return false;
382
383 base::win::ScopedComPtr<IPortableDeviceResources> resources;
384 HRESULT hr = content->Transfer(resources.Receive());
385 if (FAILED(hr))
386 return false;
387
388 base::win::ScopedComPtr<IStream> file_stream;
389 DWORD optimal_transfer_size = 0;
390 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
391 STGM_READ, &optimal_transfer_size,
392 file_stream.Receive());
393 if (FAILED(hr))
394 return false;
395 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
396 local_path);
397 }
398
399 string16 GetObjectIdFromName(IPortableDevice* device,
400 const string16& parent_id,
401 const string16& object_name) {
402 MTPDeviceObjectEntries object_entries;
403 if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name,
404 &object_entries) ||
405 object_entries.empty())
406 return string16();
407 DCHECK_EQ(1U, object_entries.size());
Lei Zhang 2013/01/14 23:25:30 FWIW, this DCHECK can fail. In MTP, it is perfectl
kmadhusu 2013/01/15 19:08:17 Done.
408 return object_entries[0].object_id;
409 }
410
411 } // namespace media_transfer_protocol
412
413 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698