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

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: Fixed class comments and removed file 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 "base/file_path.h"
10 #include "base/file_util.h"
11 #include "base/logging.h"
12 #include "base/string_util.h"
13 #include "base/threading/thread_restrictions.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 "chrome/common/chrome_constants.h"
18 #include "content/public/browser/browser_thread.h"
19
20 namespace chrome {
21
22 namespace {
23
24 // On success, returns true and updates |client_info| with a reference to an
25 // IPortableDeviceValues interface that holds information about the
26 // application that communicates with the device.
27 bool GetClientInformation(
28 base::win::ScopedComPtr<IPortableDeviceValues>* client_info) {
29 base::ThreadRestrictions::AssertIOAllowed();
30 DCHECK(client_info);
31 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues),
32 NULL, CLSCTX_INPROC_SERVER);
33 if (FAILED(hr)) {
34 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
35 return false;
36 }
37
38 (*client_info)->SetStringValue(WPD_CLIENT_NAME,
39 chrome::kBrowserProcessExecutableName);
40 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0);
41 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
42 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
43 (*client_info)->SetUnsignedIntegerValue(
44 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION);
45 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS,
46 GENERIC_READ);
Ryan Sleevi 2013/01/09 20:22:25 BUG? You aren't checking the HRESULT of any of the
kmadhusu 2013/01/10 04:59:19 We attempt to set the client information. It is ok
47 return true;
48 }
49
50 // Gets the content interface of the portable |device|. On success, returns
51 // true and fills in |content| with the given portable |device| content.
52 bool GetDeviceContent(IPortableDevice* device,
53 IPortableDeviceContent** content) {
54 base::ThreadRestrictions::AssertIOAllowed();
55 DCHECK(device);
56 DCHECK(content);
57 return SUCCEEDED(device->Content(content));
58 }
59
60 // Gets the portable |device| enumerator interface to enumerate the objects.
61 // |parent_id| specifies the parent object identifier. On success, returns
62 // true and fills in |enum_object_ids|.
63 bool GetDeviceObjectEnumerator(IPortableDevice* device,
64 const string16& parent_id,
65 IEnumPortableDeviceObjectIDs** enum_object_ids) {
66 base::ThreadRestrictions::AssertIOAllowed();
67 DCHECK(device);
68 DCHECK(!parent_id.empty());
69 DCHECK(enum_object_ids);
70 base::win::ScopedComPtr<IPortableDeviceContent> content;
71 if (!GetDeviceContent(device, content.Receive()))
72 return false;
73
74 HRESULT hr = content->EnumObjects(0, parent_id.c_str(), NULL,
75 enum_object_ids);
76 return SUCCEEDED(hr);
77 }
78
79 // Writes data from |stream| to the snapshot file specified by |local_path|. On
80 // success, returns true and the stream contents are appended to snapshot file.
Ryan Sleevi 2013/01/09 20:22:25 comment: What is a "snapshot file" ? Is this an c
kmadhusu 2013/01/10 04:59:19 Yes, the term "snapshot file" is a FileAPI concept
81 bool WriteStreamContentsToFile(IStream* stream,
82 size_t optimal_transfer_size,
83 const FilePath& local_path) {
84 base::ThreadRestrictions::AssertIOAllowed();
85 DCHECK(stream);
86 DCHECK_GT(optimal_transfer_size, 0u);
87 DCHECK(!local_path.empty());
88 DWORD read = 0;
89 HRESULT hr = S_OK;
90 std::string buffer;
91 do {
92 hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
93 optimal_transfer_size, &read);
Ryan Sleevi 2013/01/09 20:22:25 FUTURE/DESIGN: It seems like this interface can be
kmadhusu 2013/01/10 04:59:19 crbug.com/110119. FileSystem/File API doesn't supp
Ryan Sleevi 2013/01/10 05:07:18 That's an orthogonal issue. I understand that File
kmadhusu 2013/01/10 23:37:56 Until we fix the async issue, this CL will run beh
94 // IStream::Read() returns S_FALSE when the actual number of bytes read from
95 // the stream object is less than the number of bytes requested (aka
96 // |optimal_transfer_size|). This indicates the end of the stream has been
97 // reached. Therefore, it is fine to return true when Read() returns
98 // S_FALSE.
99 if ((hr != S_OK) && (hr != S_FALSE))
Ryan Sleevi 2013/01/09 20:22:25 minor nit: the inner parenthesis seem superfluous:
kmadhusu 2013/01/10 04:59:19 pkasting@ suggested parens around binary subexpres
100 return false;
101 if (read) {
102 buffer.erase(read);
103 DCHECK_EQ(read, buffer.length());
104 int data_len = static_cast<int>(buffer.length());
105 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) !=
106 data_len)
107 return false;
108 }
109 } while ((read > 0) && SUCCEEDED(hr));
110 return true;
111 }
112
113 // Returns whether the object is a directory/folder/album. |properties_values|
114 // contains the object property key values.
115 bool IsDirectory(IPortableDeviceValues* properties_values) {
116 DCHECK(properties_values);
117 GUID content_type;
118 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
119 &content_type);
120 if (FAILED(hr))
121 return false;
122 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) ||
123 (content_type == WPD_CONTENT_TYPE_FOLDER) ||
124 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) ||
125 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) ||
126 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) ||
127 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM));
128 }
129
130 // Returns the friendly name of the object from the property key values
131 // specified by the |properties_values|.
132 string16 GetObjectName(IPortableDeviceValues* properties_values,
133 bool is_directory) {
134 DCHECK(properties_values);
135 base::win::ScopedCoMem<char16> buffer;
136 REFPROPERTYKEY key =
137 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
138 HRESULT hr = properties_values->GetStringValue(key, &buffer);
139 return SUCCEEDED(hr) ? static_cast<const char16*>(buffer) : string16();
Ryan Sleevi 2013/01/09 20:22:25 DESIGN: This caught me a bit by surprise, since |b
kmadhusu 2013/01/10 04:59:19 Done.
140 }
141
142 // Gets the last modified time of the object from the property key values
143 // specified by the |properties_values|. On success, returns true and fills in
144 // |last_modified_time|.
145 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
146 base::Time* last_modified_time) {
147 DCHECK(properties_values);
148 DCHECK(last_modified_time);
149 PROPVARIANT last_modified_date = {0};
150 PropVariantInit(&last_modified_date);
151 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
152 &last_modified_date);
153 if (FAILED(hr))
154 return false;
155
156 bool last_modified_time_set = (last_modified_date.vt == VT_DATE);
157 if (last_modified_time_set) {
158 SYSTEMTIME system_time;
159 FILETIME file_time;
160 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
161 SystemTimeToFileTime(&system_time, &file_time))
162 *last_modified_time = base::Time::FromFileTime(file_time);
163 else
164 last_modified_time_set = false;
165 }
166 PropVariantClear(&last_modified_date);
167 return last_modified_time_set;
168 }
169
170 // Gets the size of the file object in bytes from the property key values
171 // specified by the |properties_values|. On success, returns true and fills
172 // in |size|.
173 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
Ryan Sleevi 2013/01/09 20:22:25 DESIGN: The impedence mismatch between an int64 an
kmadhusu 2013/01/10 04:59:19 Done.
174 DCHECK(properties_values);
175 DCHECK(size);
176 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(
177 WPD_OBJECT_SIZE, reinterpret_cast<ULONGLONG*>(size));
178 return SUCCEEDED(hr);
179 }
180
181 // Gets the details of the object specified by the |object_id| given the media
182 // transfer protocol |device|. On success, returns true and fills in |name|,
183 // |is_directory|, |size| and |last_modified_time|.
184 bool GetObjectDetails(IPortableDevice* device,
185 const string16 object_id,
186 string16* name,
187 bool* is_directory,
188 int64* size,
189 base::Time* last_modified_time) {
190 base::ThreadRestrictions::AssertIOAllowed();
191 DCHECK(device);
192 DCHECK(!object_id.empty());
193 DCHECK(name);
194 DCHECK(is_directory);
195 DCHECK(size);
196 DCHECK(last_modified_time);
197 base::win::ScopedComPtr<IPortableDeviceContent> content;
198 if (!GetDeviceContent(device, content.Receive()))
199 return false;
200
201 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
202 HRESULT hr = content->Properties(properties.Receive());
203 if (FAILED(hr))
204 return false;
205
206 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
207 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
208 NULL,
209 CLSCTX_INPROC_SERVER);
210 if (FAILED(hr))
211 return false;
212
213 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
214 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
215 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
216 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
217 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
218 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
219 return false;
220
221 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
222 hr = properties->GetValues(object_id.c_str(),
223 properties_to_read.get(),
224 properties_values.Receive());
225 if (FAILED(hr))
226 return false;
227
228 *is_directory = IsDirectory(properties_values.get());
229 *name = GetObjectName(properties_values.get(), *is_directory);
230 if (name->empty())
231 return false;
232
233 if (*is_directory) {
234 // Directory entry does not have size and last modified date property key
235 // values.
236 *size = 0;
237 *last_modified_time = base::Time();
238 return true;
239 }
240 return (GetObjectSize(properties_values.get(), size) &&
241 GetLastModifiedTime(properties_values.get(), last_modified_time));
242 }
243
244 // Creates an MTP device object entry for the given |device| and |object_id|.
245 // On success, returns true and fills in |entry|.
246 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
247 const string16& object_id,
248 MTPDeviceObjectEntry* entry) {
249 base::ThreadRestrictions::AssertIOAllowed();
250 DCHECK(device);
251 DCHECK(!object_id.empty());
252 DCHECK(entry);
253 string16 name;
254 bool is_directory;
255 int64 size;
256 base::Time last_modified_time;
257 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
258 &last_modified_time))
259 return false;
260 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
261 last_modified_time);
262 return true;
263 }
264
265 // Gets the entries of the directory specified by |directory_object_id| from
266 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
267 // of the directory. To request a specific object entry, set |get_all_entries|
268 // to false and set |object_name| to the name of the required object. On
269 // success returns true and set |object_entries|.
270 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
271 const string16& directory_object_id,
272 bool get_all_entries,
273 const string16& object_name,
274 MTPDeviceObjectEntries* object_entries) {
275 base::ThreadRestrictions::AssertIOAllowed();
276 DCHECK(device);
277 DCHECK(!directory_object_id.empty());
278 DCHECK(object_entries);
279 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
280 if (!GetDeviceObjectEnumerator(device, directory_object_id,
281 enum_object_ids.Receive()))
282 return false;
283
284 // Loop calling Next() while S_OK is being returned.
285 DWORD num_objects_to_request = 10;
286 for (HRESULT hr = S_OK; hr == S_OK;) {
287 DWORD num_objects_fetched = 0;
288 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]);
289 hr = enum_object_ids->Next(num_objects_to_request,
290 object_ids.get(),
291 &num_objects_fetched);
292 for (DWORD index = 0; index < num_objects_fetched; ++index) {
293 MTPDeviceObjectEntry entry;
294 if (GetMTPDeviceObjectEntry(device,
295 object_ids[index],
296 &entry)) {
297 if (get_all_entries) {
298 object_entries->push_back(entry);
299 } else if (entry.name == object_name) {
300 object_entries->push_back(entry); // Object entry found.
301 break;
302 }
303 }
304 }
305 for (DWORD index = 0; index < num_objects_fetched; ++index)
306 CoTaskMemFree(object_ids[index]);
307 }
308 return true;
309 }
310
311 } // namespace
312
313 bool OpenDevice(const string16& pnp_device_id,
314 base::win::ScopedComPtr<IPortableDevice>* device) {
315 base::ThreadRestrictions::AssertIOAllowed();
316 DCHECK(!pnp_device_id.empty());
317 DCHECK(device);
318 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
319 if (!GetClientInformation(&client_info))
320 return false;
321 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
322 CLSCTX_INPROC_SERVER);
323 if (FAILED(hr))
324 return false;
325
326 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
327 if (SUCCEEDED(hr))
328 return true;
329 if (hr == E_ACCESSDENIED)
330 DPLOG(ERROR) << "Access denied to open the device";
Ryan Sleevi 2013/01/09 20:22:25 1) Why only DPLOG for E_ACCESSDENIED 2) DPLOG uses
kmadhusu 2013/01/10 04:59:19 The |client_info| used by Open() call requests for
331 return false;
332 }
333
334 base::PlatformFileError GetFileEntryInfo(
335 IPortableDevice* device,
336 const string16& object_id,
337 base::PlatformFileInfo* file_entry_info) {
338 DCHECK(device);
339 DCHECK(!object_id.empty());
340 DCHECK(file_entry_info);
341 MTPDeviceObjectEntry entry;
342 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
343 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
344
345 file_entry_info->size = entry.size;
346 file_entry_info->is_directory = entry.is_directory;
347 file_entry_info->is_symbolic_link = false;
348 file_entry_info->last_modified = entry.last_modified_time;
349 file_entry_info->last_accessed = entry.last_modified_time;
350 file_entry_info->creation_time = base::Time();
351 return base::PLATFORM_FILE_OK;
352 }
353
354 bool GetDirectoryEntries(IPortableDevice* device,
355 const string16& directory_object_id,
356 MTPDeviceObjectEntries* object_entries) {
357 return GetMTPDeviceObjectEntries(device, directory_object_id, true,
358 string16(), object_entries);
359 }
360
361 bool WriteFileObjectData(IPortableDevice* device,
362 const string16& file_object_id,
363 const FilePath& local_path) {
364 base::ThreadRestrictions::AssertIOAllowed();
365 DCHECK(device);
366 DCHECK(!file_object_id.empty());
367 DCHECK(!local_path.value().empty());
368 base::win::ScopedComPtr<IPortableDeviceContent> content;
369 if (!GetDeviceContent(device, content.Receive()))
370 return false;
371
372 base::win::ScopedComPtr<IPortableDeviceResources> resources;
373 HRESULT hr = content->Transfer(resources.Receive());
374 if (FAILED(hr))
375 return false;
376
377 base::win::ScopedComPtr<IStream> file_stream;
378 DWORD optimal_transfer_size = 0;
379 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
380 STGM_READ, &optimal_transfer_size,
381 file_stream.Receive());
382 if (FAILED(hr))
383 return false;
384 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
385 local_path);
386 }
387
388 string16 GetObjectIdFromName(IPortableDevice* device,
389 const string16& parent_id,
390 const string16& object_name) {
391 MTPDeviceObjectEntries object_entries;
392 if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name,
393 &object_entries) ||
394 object_entries.empty())
395 return string16();
396 DCHECK_EQ(1U, object_entries.size());
397 return object_entries[0].object_id;
398 }
399
400 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698