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

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

Powered by Google App Engine
This is Rietveld 408576698