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

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: Removed DCHECK, added lock in PortableDeviceWatcherWin and fixed tests. Created 8 years 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 if ((hr != S_OK) && (hr != S_FALSE))
Peter Kasting 2012/12/21 18:13:30 Nit: I suggest explaining S_FALSE (the comment you
kmadhusu 2013/01/02 19:48:31 Done.
97 return false;
98 if (read) {
99 buffer.erase(read);
100 DCHECK_EQ(read, buffer.length());
101 int data_len = static_cast<int>(buffer.length());
102 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) !=
103 data_len)
104 return false;
105 }
106 } while ((read > 0) && SUCCEEDED(hr));
107 return true;
108 }
109
110 // Returns whether the object is a directory/folder/album. |properties_values|
111 // contains the object property key values.
112 bool IsDirectory(IPortableDeviceValues* properties_values) {
113 DCHECK(properties_values);
114 GUID content_type;
115 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
116 &content_type);
117 if (FAILED(hr))
118 return false;
119 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) ||
120 (content_type == WPD_CONTENT_TYPE_FOLDER) ||
121 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) ||
122 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) ||
123 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) ||
124 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM));
125 }
126
127 // Returns the friendly name of the object from the property key values
128 // specified by the |properties_values|.
129 string16 GetObjectName(IPortableDeviceValues* properties_values,
130 bool is_directory) {
131 DCHECK(properties_values);
132 base::win::ScopedCoMem<char16> buffer;
133 REFPROPERTYKEY key =
134 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
135 HRESULT hr = properties_values->GetStringValue(key, &buffer);
136 return SUCCEEDED(hr) ? static_cast<const char16*>(buffer) : string16();
137 }
138
139 // Gets the last modified time of the object from the property key values
140 // specified by the |properties_values|. On success, returns true and fills in
141 // |last_modified_time|.
142 bool GetLastModifiedTime(IPortableDeviceValues* properties_values,
143 base::Time* last_modified_time) {
144 DCHECK(properties_values);
145 DCHECK(last_modified_time);
146 PROPVARIANT last_modified_date = {0};
147 PropVariantInit(&last_modified_date);
148 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED,
149 &last_modified_date);
150 if (FAILED(hr))
151 return false;
152
153 bool last_modified_time_set = (last_modified_date.vt == VT_DATE);
154 if (last_modified_date.vt == VT_DATE) {
Peter Kasting 2012/12/21 18:13:30 Nit: Use |last_modified_time_set| here for brevity
kmadhusu 2013/01/02 19:48:31 Done.
155 SYSTEMTIME system_time;
156 FILETIME file_time;
157 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
158 SystemTimeToFileTime(&system_time, &file_time))
159 *last_modified_time = base::Time::FromFileTime(file_time);
160 else
161 last_modified_time_set = false;
162 }
163 if (!last_modified_time_set)
164 *last_modified_time = base::Time();
Peter Kasting 2012/12/21 18:13:30 Doesn't explicitly clearing this violate the funct
kmadhusu 2013/01/02 19:48:31 As per your earlier comment(https://codereview.chr
Peter Kasting 2013/01/02 21:41:09 No, I asked you to make sure it was set when the f
kmadhusu 2013/01/02 23:49:48 Fixed.
165 PropVariantClear(&last_modified_date);
166 return last_modified_time_set;
Peter Kasting 2012/12/21 18:13:30 You don't have to do this in this CL, but you coul
kmadhusu 2013/01/02 19:48:31 I will submit a cleanup patch later.
167 }
168
169 // Gets the size of the file object in bytes from the property key values
170 // specified by the |properties_values|. On success, returns true and fills
171 // in |size|.
172 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
173 DCHECK(properties_values);
174 DCHECK(size);
175 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(
176 WPD_OBJECT_SIZE, reinterpret_cast<ULONGLONG*>(size));
177 return SUCCEEDED(hr);
178 }
179
180 // Gets the details of the object specified by the |object_id| given the media
181 // transfer protocol |device|. On success, returns true and fills in |name|,
182 // |is_directory|, |size| and |last_modified_time|.
183 bool GetObjectDetails(IPortableDevice* device,
184 const string16 object_id,
185 string16* name,
186 bool* is_directory,
187 int64* size,
188 base::Time* last_modified_time) {
189 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
190 DCHECK(device);
191 DCHECK(!object_id.empty());
192 DCHECK(name);
193 DCHECK(is_directory);
194 DCHECK(size);
195 DCHECK(last_modified_time);
196 base::win::ScopedComPtr<IPortableDeviceContent> content;
197 if (!GetDeviceContent(device, content.Receive()))
198 return false;
199
200 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
201 HRESULT hr = content->Properties(properties.Receive());
202 if (FAILED(hr))
203 return false;
204
205 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
206 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
207 NULL,
208 CLSCTX_INPROC_SERVER);
209 if (FAILED(hr))
210 return false;
211
212 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
213 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
214 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
215 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
216 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
217 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
218 return false;
219
220 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
221 hr = properties->GetValues(object_id.c_str(),
222 properties_to_read.get(),
223 properties_values.Receive());
224 if (FAILED(hr))
225 return false;
226
227 *is_directory = IsDirectory(properties_values.get());
228 *name = GetObjectName(properties_values.get(), *is_directory);
229 if (name->empty())
230 return false;
231
232 if (*is_directory) {
233 // Directory entry does not have size and last modified date property key
234 // values.
235 *size = 0;
236 *last_modified_time = base::Time();
237 return true;
238 }
239 return (GetObjectSize(properties_values.get(), size) &&
240 GetLastModifiedTime(properties_values.get(), last_modified_time));
241 }
242
243 // Creates an MTP device object entry for the given |device| and |object_id|.
244 // On success, returns true and fills in |entry|.
245 bool GetMTPDeviceObjectEntry(IPortableDevice* device,
246 const string16& object_id,
247 MTPDeviceObjectEntry* entry) {
248 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
249 DCHECK(device);
250 DCHECK(!object_id.empty());
251 DCHECK(entry);
252 string16 name;
253 bool is_directory;
254 int64 size;
255 base::Time last_modified_time;
256 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size,
257 &last_modified_time))
258 return false;
259 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size,
260 last_modified_time);
261 return true;
262 }
263
264 // Gets the entries of the directory specified by |directory_object_id| from
265 // the given MTP |device|. Set |get_all_entries| to true to get all the entries
266 // of the directory. To request a specific object entry, set |get_all_entries|
267 // to false and set |object_name| to the name of the required object. On
268 // success returns true and set |object_entries|.
269 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
270 const string16& directory_object_id,
271 bool get_all_entries,
272 const string16& object_name,
273 MTPDeviceObjectEntries* object_entries) {
274 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
275 DCHECK(device);
276 DCHECK(!directory_object_id.empty());
277 DCHECK(object_entries);
278 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
279 if (!GetDeviceObjectEnumerator(device, directory_object_id,
280 enum_object_ids.Receive()))
281 return false;
282
283 // Loop calling Next() while S_OK is being returned.
284 DWORD num_objects_to_request = 10;
285 for (HRESULT hr = S_OK; hr == S_OK;) {
286 DWORD num_objects_fetched = 0;
287 typedef base::win::ScopedCoMem<char16> ScopedObjectID;
288 scoped_ptr<ScopedObjectID[]> object_ids(
289 new ScopedObjectID[num_objects_to_request]);
290 ZeroMemory(object_ids.get(), num_objects_to_request);
Peter Kasting 2012/12/21 18:13:30 Don't do this. new[] will default-initialize non-
kmadhusu 2013/01/02 19:48:31 Removed ZeroMemory(...).
291 hr = enum_object_ids->Next(num_objects_to_request,
292 static_cast<char16**>(&object_ids[0]),
Peter Kasting 2012/12/21 18:13:30 This makes me leery. It actually works based on h
kmadhusu 2013/01/02 19:48:31 Fixed. Used scoped_ptr<char16*[]> and CoTaskMemFre
293 &num_objects_fetched);
294 for (DWORD index = 0; index < num_objects_fetched; ++index) {
295 MTPDeviceObjectEntry entry;
296 if (GetMTPDeviceObjectEntry(device,
297 static_cast<char16*>(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 }
308 return true;
309 }
310
311 } // namespace
312
313
314 // MTPDeviceOperationsUtil ----------------------------------------------------
315
316 // static
317 bool MTPDeviceOperationsUtil::OpenDevice(
318 const string16& pnp_device_id,
319 base::win::ScopedComPtr<IPortableDevice>* device) {
320 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
321 DCHECK(!pnp_device_id.empty());
322 DCHECK(device);
323 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
324 if (!GetClientInformation(&client_info))
325 return false;
326 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
327 CLSCTX_INPROC_SERVER);
328 if (FAILED(hr))
329 return false;
330
331 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
332 if (SUCCEEDED(hr))
333 return true;
334 if (hr == E_ACCESSDENIED)
335 DPLOG(ERROR) << "Access denied to open the device";
336 return false;
337 }
338
339 // static
340 base::PlatformFileError MTPDeviceOperationsUtil::GetFileEntryInfo(
341 IPortableDevice* device,
342 const string16& object_id,
343 base::PlatformFileInfo* file_entry_info) {
344 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
345 DCHECK(device);
346 DCHECK(!object_id.empty());
347 DCHECK(file_entry_info);
348 MTPDeviceObjectEntry entry;
349 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
350 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
351
352 file_entry_info->size = entry.size;
353 file_entry_info->is_directory = entry.is_directory;
354 file_entry_info->is_symbolic_link = false;
355 file_entry_info->last_modified = entry.last_modified_time;
356 file_entry_info->last_accessed = entry.last_modified_time;
357 file_entry_info->creation_time = base::Time();
358 return base::PLATFORM_FILE_OK;
359 }
360
361 // static
362 bool MTPDeviceOperationsUtil::GetDirectoryEntries(
363 IPortableDevice* device,
364 const string16& directory_object_id,
365 MTPDeviceObjectEntries* object_entries) {
366 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
367 return GetMTPDeviceObjectEntries(device, directory_object_id, true,
368 string16(), object_entries);
369 }
370
371 // static
372 bool MTPDeviceOperationsUtil::WriteFileObjectData(
373 IPortableDevice* device,
374 const string16& file_object_id,
375 const FilePath& local_path) {
376 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
377 DCHECK(device);
378 DCHECK(!file_object_id.empty());
379 DCHECK(!local_path.value().empty());
380 base::win::ScopedComPtr<IPortableDeviceContent> content;
381 if (!GetDeviceContent(device, content.Receive()))
382 return false;
383
384 base::win::ScopedComPtr<IPortableDeviceResources> resources;
385 HRESULT hr = content->Transfer(resources.Receive());
386 if (FAILED(hr))
387 return false;
388
389 base::win::ScopedComPtr<IStream> file_stream;
390 DWORD optimal_transfer_size = 0;
391 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
392 STGM_READ, &optimal_transfer_size,
393 file_stream.Receive());
394 if (FAILED(hr))
395 return false;
396 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
397 local_path);
398 }
399
400 // static
401 string16 MTPDeviceOperationsUtil::GetObjectIdFromName(
402 IPortableDevice* device,
403 const string16& parent_id,
404 const string16& object_name) {
405 MTPDeviceObjectEntries object_entries;
406 if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name,
407 &object_entries) ||
408 object_entries.empty())
409 return string16();
410 DCHECK_EQ(1U, object_entries.size());
411 return object_entries[0].object_id;
412 }
413
414 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698