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

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: Rebase + Fixed win compile error by implementing GetMTPStorageInfoFromDeviceId in TestStorageNotifi… 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) 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_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
135 // The root storage object describes its content type as
136 // WPD_CONTENT_FUNCTIONAL_OBJECT.
137 return (content_type == WPD_CONTENT_TYPE_FOLDER ||
138 content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT);
139 }
140
141 // Returns the friendly name of the object from the property key values
142 // specified by the |properties_values|.
143 string16 GetObjectName(IPortableDeviceValues* properties_values,
144 bool is_directory) {
145 DCHECK(properties_values);
146 base::win::ScopedCoMem<char16> buffer;
147 REFPROPERTYKEY key =
148 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
149 HRESULT hr = properties_values->GetStringValue(key, &buffer);
150 string16 result;
151 if (SUCCEEDED(hr))
152 result.assign(buffer);
153 return result;
154 }
155
156 // Gets the last modified time of the object from the property key values
157 // specified by the |properties_values|. On success, returns true and fills in
158 // |last_modified_time|.
159 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
160 base::Time* last_modified_time) {
161 DCHECK(properties_values);
162 DCHECK(last_modified_time);
163 PROPVARIANT last_modified_date = {0};
164 PropVariantInit(&last_modified_date);
165 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
166 &last_modified_date);
167 if (FAILED(hr))
168 return false;
169
170 bool last_modified_time_set = (last_modified_date.vt == VT_DATE);
171 if (last_modified_time_set) {
172 SYSTEMTIME system_time;
173 FILETIME file_time;
174 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
175 SystemTimeToFileTime(&system_time, &file_time))
176 *last_modified_time = base::Time::FromFileTime(file_time);
177 else
178 last_modified_time_set = false;
179 }
180 PropVariantClear(&last_modified_date);
181 return last_modified_time_set;
182 }
183
184 // Gets the size of the file object in bytes from the property key values
185 // specified by the |properties_values|. On success, returns true and fills
186 // in |size|.
187 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
188 DCHECK(properties_values);
189 DCHECK(size);
190 ULONGLONG actual_size;
191 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE,
192 &actual_size);
193 bool success = SUCCEEDED(hr) && (actual_size <= kint64max);
194 if (success)
195 *size = static_cast<int64>(actual_size);
196 return success;
197 }
198
199 // Gets the details of the object specified by the |object_id| given the media
200 // transfer protocol |device|. On success, returns true and fills in |name|,
201 // |is_directory|, |size| and |last_modified_time|.
202 bool GetObjectDetails(IPortableDevice* device,
203 const string16 object_id,
204 string16* name,
205 bool* is_directory,
206 int64* size,
207 base::Time* last_modified_time) {
208 base::ThreadRestrictions::AssertIOAllowed();
209 DCHECK(device);
210 DCHECK(!object_id.empty());
211 DCHECK(name);
212 DCHECK(is_directory);
213 DCHECK(size);
214 DCHECK(last_modified_time);
215 base::win::ScopedComPtr<IPortableDeviceContent> content =
216 GetDeviceContent(device);
217 if (!content)
218 return false;
219
220 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
221 HRESULT hr = content->Properties(properties.Receive());
222 if (FAILED(hr))
223 return false;
224
225 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
226 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
227 NULL,
228 CLSCTX_INPROC_SERVER);
229 if (FAILED(hr))
230 return false;
231
232 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
233 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
234 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
235 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
236 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
237 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
238 return false;
239
240 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
241 hr = properties->GetValues(object_id.c_str(),
242 properties_to_read.get(),
243 properties_values.Receive());
244 if (FAILED(hr))
245 return false;
246
247 *is_directory = IsDirectory(properties_values.get());
248 *name = GetObjectName(properties_values.get(), *is_directory);
249 if (name->empty())
250 return false;
251
252 if (*is_directory) {
253 // Directory entry does not have size and last modified date property key
254 // values.
255 *size = 0;
256 *last_modified_time = base::Time();
257 return true;
258 }
259 return (GetObjectSize(properties_values.get(), size) &&
260 GetLastModifiedTime(properties_values.get(), last_modified_time));
261 }
262
263 // Creates an MTP device object entry for the given |device| and |object_id|.
264 // On success, returns true and fills in |entry|.
265 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
266 const string16& object_id,
267 MTPDeviceObjectEntry* entry) {
268 base::ThreadRestrictions::AssertIOAllowed();
269 DCHECK(device);
270 DCHECK(!object_id.empty());
271 DCHECK(entry);
272 string16 name;
273 bool is_directory;
274 int64 size;
275 base::Time last_modified_time;
276 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
277 &last_modified_time))
278 return false;
279 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
280 last_modified_time);
281 return true;
282 }
283
284 // Gets the entries of the directory specified by |directory_object_id| from
285 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
286 // of the directory. To request a specific object entry, put the object name in
287 // |object_name|. Leave |object_name| blank to request all entries. On
288 // success returns true and set |object_entries|.
289 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
290 const string16& directory_object_id,
291 const string16& object_name,
292 MTPDeviceObjectEntries* object_entries) {
293 base::ThreadRestrictions::AssertIOAllowed();
294 DCHECK(device);
295 DCHECK(!directory_object_id.empty());
296 DCHECK(object_entries);
297 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids =
298 GetDeviceObjectEnumerator(device, directory_object_id);
299 if (!enum_object_ids)
300 return false;
301
302 // Loop calling Next() while S_OK is being returned.
303 const DWORD num_objects_to_request = 10;
304 const bool get_all_entries = object_name.empty();
305 for (HRESULT hr = S_OK; hr == S_OK;) {
306 DWORD num_objects_fetched = 0;
307 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]);
308 hr = enum_object_ids->Next(num_objects_to_request,
309 object_ids.get(),
310 &num_objects_fetched);
311 for (DWORD index = 0; index < num_objects_fetched; ++index) {
312 MTPDeviceObjectEntry entry;
313 if (GetMTPDeviceObjectEntry(device,
314 object_ids[index],
315 &entry)) {
316 if (get_all_entries) {
317 object_entries->push_back(entry);
318 } else if (entry.name == object_name) {
319 object_entries->push_back(entry); // Object entry found.
320 break;
321 }
322 }
323 }
324 for (DWORD index = 0; index < num_objects_fetched; ++index)
325 CoTaskMemFree(object_ids[index]);
326 }
327 return true;
328 }
329
330 } // namespace
331
332 base::win::ScopedComPtr<IPortableDevice> OpenDevice(
333 const string16& pnp_device_id) {
334 base::ThreadRestrictions::AssertIOAllowed();
335 DCHECK(!pnp_device_id.empty());
336 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
337 if (!GetClientInformation(&client_info))
338 return base::win::ScopedComPtr<IPortableDevice>();
339 base::win::ScopedComPtr<IPortableDevice> device;
340 HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL,
341 CLSCTX_INPROC_SERVER);
342 if (FAILED(hr))
343 return base::win::ScopedComPtr<IPortableDevice>();
344
345 hr = device->Open(pnp_device_id.c_str(), client_info.get());
346 if (SUCCEEDED(hr))
347 return device;
348 if (hr == E_ACCESSDENIED)
349 DPLOG(ERROR) << "Access denied to open the device";
350 return base::win::ScopedComPtr<IPortableDevice>();
351 }
352
353 base::PlatformFileError GetFileEntryInfo(
354 IPortableDevice* device,
355 const string16& object_id,
356 base::PlatformFileInfo* file_entry_info) {
357 DCHECK(device);
358 DCHECK(!object_id.empty());
359 DCHECK(file_entry_info);
360 MTPDeviceObjectEntry entry;
361 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
362 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
363
364 file_entry_info->size = entry.size;
365 file_entry_info->is_directory = entry.is_directory;
366 file_entry_info->is_symbolic_link = false;
367 file_entry_info->last_modified = entry.last_modified_time;
368 file_entry_info->last_accessed = entry.last_modified_time;
369 file_entry_info->creation_time = base::Time();
370 return base::PLATFORM_FILE_OK;
371 }
372
373 bool GetDirectoryEntries(IPortableDevice* device,
374 const string16& directory_object_id,
375 MTPDeviceObjectEntries* object_entries) {
376 return GetMTPDeviceObjectEntries(device, directory_object_id, string16(),
377 object_entries);
378 }
379
380 bool WriteFileObjectContentToPath(IPortableDevice* device,
381 const string16& file_object_id,
382 const FilePath& local_path) {
383 base::ThreadRestrictions::AssertIOAllowed();
384 DCHECK(device);
385 DCHECK(!file_object_id.empty());
386 DCHECK(!local_path.empty());
387 base::win::ScopedComPtr<IPortableDeviceContent> content =
388 GetDeviceContent(device);
389 if (!content)
390 return false;
391
392 base::win::ScopedComPtr<IPortableDeviceResources> resources;
393 HRESULT hr = content->Transfer(resources.Receive());
394 if (FAILED(hr))
395 return false;
396
397 base::win::ScopedComPtr<IStream> file_stream;
398 DWORD optimal_transfer_size = 0;
399 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
400 STGM_READ, &optimal_transfer_size,
401 file_stream.Receive());
402 if (FAILED(hr))
403 return false;
404 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
405 local_path);
406 }
407
408 string16 GetObjectIdFromName(IPortableDevice* device,
409 const string16& parent_id,
410 const string16& object_name) {
411 MTPDeviceObjectEntries object_entries;
412 if (!GetMTPDeviceObjectEntries(device, parent_id, object_name,
413 &object_entries) ||
414 object_entries.empty())
415 return string16();
416 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have
417 // the same name. Handle the situation gracefully. Refer to crbug.com/169930
418 // for more details.
419 DCHECK_EQ(1U, object_entries.size());
420 return object_entries[0].object_id;
421 }
422
423 } // namespace media_transfer_protocol
424
425 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698