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

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: Modified LazyInit() to handle GetDeviceStorageInfo failure 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());
Ryan Sleevi 2013/01/05 03:00:58 DESIGN: Is it really a design constraint that you
kmadhusu 2013/01/08 03:19:40 Replaced some of the instances of "DCHECK(content:
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;
Ryan Sleevi 2013/01/05 03:00:58 perf: Constantly re-allocating a new buffer inside
kmadhusu 2013/01/08 03:19:40 Done.
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;
Ryan Sleevi 2013/01/05 03:00:58 style: should be using braces here
Peter Kasting 2013/01/05 03:17:19 Braces are optional if the body is one line. It d
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
Ryan Sleevi 2013/01/05 03:00:58 style: should be using braces here
kmadhusu 2013/01/08 03:19:40 Ignoring this comment since the braces are optiona
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;
Ryan Sleevi 2013/01/05 03:00:58 style: should be using braces here
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;
Ryan Sleevi 2013/01/05 03:00:58 style: Should be using braces here
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)
308 CoTaskMemFree(object_ids[index]);
309 }
310 return true;
311 }
312
313 } // namespace
314
315
316 // MTPDeviceOperationsUtil ----------------------------------------------------
317
318 // static
319 bool MTPDeviceOperationsUtil::OpenDevice(
320 const string16& pnp_device_id,
321 base::win::ScopedComPtr<IPortableDevice>* device) {
322 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
323 DCHECK(!pnp_device_id.empty());
324 DCHECK(device);
325 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
326 if (!GetClientInformation(&client_info))
327 return false;
328 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
329 CLSCTX_INPROC_SERVER);
330 if (FAILED(hr))
331 return false;
332
333 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
334 if (SUCCEEDED(hr))
335 return true;
336 if (hr == E_ACCESSDENIED)
337 DPLOG(ERROR) << "Access denied to open the device";
338 return false;
339 }
340
341 // static
342 base::PlatformFileError MTPDeviceOperationsUtil::GetFileEntryInfo(
343 IPortableDevice* device,
344 const string16& object_id,
345 base::PlatformFileInfo* file_entry_info) {
346 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
347 DCHECK(device);
348 DCHECK(!object_id.empty());
349 DCHECK(file_entry_info);
350 MTPDeviceObjectEntry entry;
351 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
352 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
353
354 file_entry_info->size = entry.size;
355 file_entry_info->is_directory = entry.is_directory;
356 file_entry_info->is_symbolic_link = false;
357 file_entry_info->last_modified = entry.last_modified_time;
358 file_entry_info->last_accessed = entry.last_modified_time;
359 file_entry_info->creation_time = base::Time();
360 return base::PLATFORM_FILE_OK;
361 }
362
363 // static
364 bool MTPDeviceOperationsUtil::GetDirectoryEntries(
365 IPortableDevice* device,
366 const string16& directory_object_id,
367 MTPDeviceObjectEntries* object_entries) {
368 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
369 return GetMTPDeviceObjectEntries(device, directory_object_id, true,
370 string16(), object_entries);
371 }
372
373 // static
374 bool MTPDeviceOperationsUtil::WriteFileObjectData(
375 IPortableDevice* device,
376 const string16& file_object_id,
377 const FilePath& local_path) {
378 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
379 DCHECK(device);
380 DCHECK(!file_object_id.empty());
381 DCHECK(!local_path.value().empty());
382 base::win::ScopedComPtr<IPortableDeviceContent> content;
383 if (!GetDeviceContent(device, content.Receive()))
384 return false;
385
386 base::win::ScopedComPtr<IPortableDeviceResources> resources;
387 HRESULT hr = content->Transfer(resources.Receive());
388 if (FAILED(hr))
389 return false;
390
391 base::win::ScopedComPtr<IStream> file_stream;
392 DWORD optimal_transfer_size = 0;
393 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
394 STGM_READ, &optimal_transfer_size,
395 file_stream.Receive());
396 if (FAILED(hr))
397 return false;
398 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
399 local_path);
400 }
401
402 // static
403 string16 MTPDeviceOperationsUtil::GetObjectIdFromName(
404 IPortableDevice* device,
405 const string16& parent_id,
406 const string16& object_name) {
407 MTPDeviceObjectEntries object_entries;
408 if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name,
409 &object_entries) ||
410 object_entries.empty())
Ryan Sleevi 2013/01/05 03:00:58 style: You should be using braces here
411 return string16();
412 DCHECK_EQ(1U, object_entries.size());
Ryan Sleevi 2013/01/05 03:00:58 Should be a CHECK_EQ - you have no guarantee the n
Peter Kasting 2013/01/05 03:17:19 That's fine. We don't need to force a crash when
Ryan Sleevi 2013/01/05 03:44:37 You absolutely should force a crash when you're ab
kmadhusu 2013/01/07 19:17:26 rsleevi@: At line 410, I already handled the objec
Ryan Sleevi 2013/01/07 19:29:29 ACK - you're right. DCHECK is totally fine here th
413 return object_entries[0].object_id;
414 }
415
416 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698