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

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 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 <algorithm>
10
11 #include "base/basictypes.h"
12 #include "base/file_path.h"
13 #include "base/file_util.h"
14 #include "base/logging.h"
15 #include "base/string_util.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "base/time.h"
18 #include "base/win/scoped_co_mem.h"
19 #include "chrome/browser/system_monitor/removable_device_constants.h"
20 #include "chrome/common/chrome_constants.h"
21 #include "content/public/browser/browser_thread.h"
22
23 namespace chrome {
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 // Writes data from |stream| to the file specified by |local_path|. On success,
89 // returns true and the stream contents are appended to the file.
90 // TODO(kmadhusu) Deprecate this function after fixing crbug.com/110119.
91 bool WriteStreamContentsToFile(IStream* stream,
92 size_t optimal_transfer_size,
93 const FilePath& local_path) {
94 base::ThreadRestrictions::AssertIOAllowed();
95 DCHECK(stream);
96 DCHECK(!local_path.empty());
97 if (optimal_transfer_size == 0U)
98 return false;
99 DWORD read = 0;
100 HRESULT hr = S_OK;
101 std::string buffer;
102 do {
103 hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
104 optimal_transfer_size, &read);
105 // IStream::Read() returns S_FALSE when the actual number of bytes read from
106 // the stream object is less than the number of bytes requested (aka
107 // |optimal_transfer_size|). This indicates the end of the stream has been
108 // reached. Therefore, it is fine to return true when Read() returns
109 // S_FALSE.
110 if ((hr != S_OK) && (hr != S_FALSE))
111 return false;
112 if (read) {
113 int data_len = std::min<int>(read, buffer.length());
114 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) !=
115 data_len)
116 return false;
117 }
118 } while (read > 0);
119 return true;
120 }
121
122 // Returns whether the object is a directory/folder/album. |properties_values|
123 // contains the object property key values.
124 bool IsDirectory(IPortableDeviceValues* properties_values) {
125 DCHECK(properties_values);
126 GUID content_type;
127 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
128 &content_type);
129 if (FAILED(hr))
130 return false;
131 // TODO(kmadhusu): |content_type| can be an image or audio or video or mixed
132 // album. It is not clear whether an album is a collection of physical objects
133 // or virtual objects. Investigate this in detail.
134 return ((content_type == WPD_CONTENT_TYPE_FOLDER) ||
135 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT));
Lei Zhang 2013/01/15 23:53:37 Can you add a comment to mention WPD_CONTENT_TYPE_
Lei Zhang 2013/01/15 23:53:37 You don't need extra parenthesis around each compa
kmadhusu 2013/01/16 00:14:24 Done.
kmadhusu 2013/01/16 00:14:24 Done.
136 }
137
138 // Returns the friendly name of the object from the property key values
139 // specified by the |properties_values|.
140 string16 GetObjectName(IPortableDeviceValues* properties_values,
141 bool is_directory) {
142 DCHECK(properties_values);
143 base::win::ScopedCoMem<char16> buffer;
144 REFPROPERTYKEY key =
145 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
146 HRESULT hr = properties_values->GetStringValue(key, &buffer);
147 string16 result;
148 if (SUCCEEDED(hr))
149 result.assign(buffer);
150 return result;
151 }
152
153 // Gets the last modified time of the object from the property key values
154 // specified by the |properties_values|. On success, returns true and fills in
155 // |last_modified_time|.
156 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
157 base::Time* last_modified_time) {
158 DCHECK(properties_values);
159 DCHECK(last_modified_time);
160 PROPVARIANT last_modified_date = {0};
161 PropVariantInit(&last_modified_date);
162 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
163 &last_modified_date);
164 if (FAILED(hr))
165 return false;
166
167 bool last_modified_time_set = (last_modified_date.vt == VT_DATE);
168 if (last_modified_time_set) {
169 SYSTEMTIME system_time;
170 FILETIME file_time;
171 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
172 SystemTimeToFileTime(&system_time, &file_time))
173 *last_modified_time = base::Time::FromFileTime(file_time);
174 else
175 last_modified_time_set = false;
176 }
177 PropVariantClear(&last_modified_date);
178 return last_modified_time_set;
179 }
180
181 // Gets the size of the file object in bytes from the property key values
182 // specified by the |properties_values|. On success, returns true and fills
183 // in |size|.
184 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
185 DCHECK(properties_values);
186 DCHECK(size);
187 ULONGLONG actual_size;
188 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE,
189 &actual_size);
190 bool success = SUCCEEDED(hr) && (actual_size <= kint64max);
191 if (success)
192 *size = static_cast<int64>(actual_size);
193 return success;
194 }
195
196 // Gets the details of the object specified by the |object_id| given the media
197 // transfer protocol |device|. On success, returns true and fills in |name|,
198 // |is_directory|, |size| and |last_modified_time|.
199 bool GetObjectDetails(IPortableDevice* device,
200 const string16 object_id,
201 string16* name,
202 bool* is_directory,
203 int64* size,
204 base::Time* last_modified_time) {
205 base::ThreadRestrictions::AssertIOAllowed();
206 DCHECK(device);
207 DCHECK(!object_id.empty());
208 DCHECK(name);
209 DCHECK(is_directory);
210 DCHECK(size);
211 DCHECK(last_modified_time);
212 base::win::ScopedComPtr<IPortableDeviceContent> content =
213 GetDeviceContent(device);
214 if (!content)
215 return false;
216
217 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
218 HRESULT hr = content->Properties(properties.Receive());
219 if (FAILED(hr))
220 return false;
221
222 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
223 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
224 NULL,
225 CLSCTX_INPROC_SERVER);
226 if (FAILED(hr))
227 return false;
228
229 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
230 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
231 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
232 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
233 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
234 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
235 return false;
236
237 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
238 hr = properties->GetValues(object_id.c_str(),
239 properties_to_read.get(),
240 properties_values.Receive());
241 if (FAILED(hr))
242 return false;
243
244 *is_directory = IsDirectory(properties_values.get());
245 *name = GetObjectName(properties_values.get(), *is_directory);
246 if (name->empty())
247 return false;
248
249 if (*is_directory) {
250 // Directory entry does not have size and last modified date property key
251 // values.
252 *size = 0;
253 *last_modified_time = base::Time();
254 return true;
255 }
256 return (GetObjectSize(properties_values.get(), size) &&
257 GetLastModifiedTime(properties_values.get(), last_modified_time));
258 }
259
260 // Creates an MTP device object entry for the given |device| and |object_id|.
261 // On success, returns true and fills in |entry|.
262 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
263 const string16& object_id,
264 MTPDeviceObjectEntry* entry) {
265 base::ThreadRestrictions::AssertIOAllowed();
266 DCHECK(device);
267 DCHECK(!object_id.empty());
268 DCHECK(entry);
269 string16 name;
270 bool is_directory;
271 int64 size;
272 base::Time last_modified_time;
273 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
274 &last_modified_time))
275 return false;
276 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
277 last_modified_time);
278 return true;
279 }
280
281 // Gets the entries of the directory specified by |directory_object_id| from
282 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
283 // of the directory. To request a specific object entry, put the object name in
284 // |object_name|. Leave |object_name| blank to request all entries. On
285 // success returns true and set |object_entries|.
286 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
287 const string16& directory_object_id,
288 const string16& object_name,
289 MTPDeviceObjectEntries* object_entries) {
290 base::ThreadRestrictions::AssertIOAllowed();
291 DCHECK(device);
292 DCHECK(!directory_object_id.empty());
293 DCHECK(object_entries);
294 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids =
295 GetDeviceObjectEnumerator(device, directory_object_id);
296 if (!enum_object_ids)
297 return false;
298
299 // Loop calling Next() while S_OK is being returned.
300 const DWORD num_objects_to_request = 10;
301 const bool get_all_entries = object_name.empty();
302 for (HRESULT hr = S_OK; hr == S_OK;) {
303 DWORD num_objects_fetched = 0;
304 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]);
305 hr = enum_object_ids->Next(num_objects_to_request,
306 object_ids.get(),
307 &num_objects_fetched);
308 for (DWORD index = 0; index < num_objects_fetched; ++index) {
309 MTPDeviceObjectEntry entry;
310 if (GetMTPDeviceObjectEntry(device,
311 object_ids[index],
312 &entry)) {
313 if (get_all_entries) {
314 object_entries->push_back(entry);
315 } else if (entry.name == object_name) {
316 object_entries->push_back(entry); // Object entry found.
317 break;
318 }
319 }
320 }
321 for (DWORD index = 0; index < num_objects_fetched; ++index)
322 CoTaskMemFree(object_ids[index]);
323 }
324 return true;
325 }
326
327 } // namespace
328
329 base::win::ScopedComPtr<IPortableDevice> OpenDevice(
330 const string16& pnp_device_id) {
331 base::ThreadRestrictions::AssertIOAllowed();
332 DCHECK(!pnp_device_id.empty());
333 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
334 if (!GetClientInformation(&client_info))
335 return base::win::ScopedComPtr<IPortableDevice>();
336 base::win::ScopedComPtr<IPortableDevice> device;
337 HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL,
338 CLSCTX_INPROC_SERVER);
339 if (FAILED(hr))
340 return base::win::ScopedComPtr<IPortableDevice>();
341
342 hr = device->Open(pnp_device_id.c_str(), client_info.get());
343 if (SUCCEEDED(hr))
344 return device;
345 if (hr == E_ACCESSDENIED)
346 DPLOG(ERROR) << "Access denied to open the device";
347 return base::win::ScopedComPtr<IPortableDevice>();
348 }
349
350 base::PlatformFileError GetFileEntryInfo(
351 IPortableDevice* device,
352 const string16& object_id,
353 base::PlatformFileInfo* file_entry_info) {
354 DCHECK(device);
355 DCHECK(!object_id.empty());
356 DCHECK(file_entry_info);
357 MTPDeviceObjectEntry entry;
358 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
359 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
360
361 file_entry_info->size = entry.size;
362 file_entry_info->is_directory = entry.is_directory;
363 file_entry_info->is_symbolic_link = false;
364 file_entry_info->last_modified = entry.last_modified_time;
365 file_entry_info->last_accessed = entry.last_modified_time;
366 file_entry_info->creation_time = base::Time();
367 return base::PLATFORM_FILE_OK;
368 }
369
370 bool GetDirectoryEntries(IPortableDevice* device,
371 const string16& directory_object_id,
372 MTPDeviceObjectEntries* object_entries) {
373 return GetMTPDeviceObjectEntries(device, directory_object_id, string16(),
374 object_entries);
375 }
376
377 bool WriteFileObjectContentToPath(IPortableDevice* device,
378 const string16& file_object_id,
379 const FilePath& local_path) {
380 base::ThreadRestrictions::AssertIOAllowed();
381 DCHECK(device);
382 DCHECK(!file_object_id.empty());
383 DCHECK(!local_path.empty());
384 base::win::ScopedComPtr<IPortableDeviceContent> content =
385 GetDeviceContent(device);
386 if (!content)
387 return false;
388
389 base::win::ScopedComPtr<IPortableDeviceResources> resources;
390 HRESULT hr = content->Transfer(resources.Receive());
391 if (FAILED(hr))
392 return false;
393
394 base::win::ScopedComPtr<IStream> file_stream;
395 DWORD optimal_transfer_size = 0;
396 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
397 STGM_READ, &optimal_transfer_size,
398 file_stream.Receive());
399 if (FAILED(hr))
400 return false;
401 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
402 local_path);
403 }
404
405 string16 GetObjectIdFromName(IPortableDevice* device,
406 const string16& parent_id,
407 const string16& object_name) {
408 MTPDeviceObjectEntries object_entries;
409 if (!GetMTPDeviceObjectEntries(device, parent_id, object_name,
410 &object_entries) ||
411 object_entries.empty())
412 return string16();
413 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
414 // the same name. Handle the situation gracefully. Refer to crbug.com/169930
415 // for more details.
416 DCHECK_EQ(1U, object_entries.size());
417 return object_entries[0].object_id;
418 }
419
420 } // namespace media_transfer_protocol
421
422 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698