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

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 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 // Attempt to set client details.
Peter Kasting 2012/12/19 20:17:58 Nit: Comment doesn't really add anything
kmadhusu 2012/12/20 20:37:29 Removed.
41 (*client_info)->SetStringValue(WPD_CLIENT_NAME,
42 chrome::kBrowserProcessExecutableName);
43 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0);
44 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
45 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
46 (*client_info)->SetUnsignedIntegerValue(
47 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION);
48 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS,
49 GENERIC_READ);
50 return true;
51 }
52
53 // Gets the content interface of the portable |device|. On success, returns
54 // true and fills in |content| with the given portable |device| content.
55 bool GetDeviceContent(IPortableDevice* device,
56 IPortableDeviceContent** content) {
57 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
58 DCHECK(device);
59 DCHECK(content);
60 return SUCCEEDED(device->Content(content));
61 }
62
63 // Gets the portable |device| enumerator interface to enumerate the objects.
64 // |parent_id| specifies the parent object identifier. On success, returns
65 // true and fills in |enum_object_ids|. On failure, returns false and
66 // |enum_object_ids| is not set.
Peter Kasting 2012/12/19 20:17:58 Nit: Last sentence isn't really necessary; it's im
kmadhusu 2012/12/20 20:37:29 Removed the last sentence. (Multiple places).
67 bool GetDeviceObjectEnumerator(IPortableDevice* device,
68 const string16& parent_id,
69 IEnumPortableDeviceObjectIDs** enum_object_ids) {
70 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
71 DCHECK(device);
72 DCHECK(!parent_id.empty());
73 DCHECK(enum_object_ids);
74 base::win::ScopedComPtr<IPortableDeviceContent> content;
75 if (!GetDeviceContent(device, content.Receive()))
76 return false;
77
78 HRESULT hr = content->EnumObjects(0, parent_id.c_str(), NULL,
79 enum_object_ids);
80 return SUCCEEDED(hr);
81 }
82
83 // Writes data from |stream| to the snapshot file specified by |local_path|. On
84 // success, returns true and the stream contents are appended to snapshot file.
85 // On failure, returns false.
86 bool WriteStreamContentsToFile(IStream* stream,
87 size_t optimal_transfer_size,
88 const FilePath& local_path) {
89 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
90 DCHECK(stream);
91 DCHECK_GT(optimal_transfer_size, 0u);
92 DCHECK(!local_path.empty());
93 DWORD read = 0;
94 HRESULT hr = S_OK;
95 do {
96 std::string buffer;
97 hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1),
98 optimal_transfer_size, &read);
99 if ((hr != S_OK) && (hr != S_FALSE) && (hr != E_PENDING))
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));
Peter Kasting 2012/12/19 20:17:58 Hmm, won't this mean that if Read() returns S_FALS
kmadhusu 2012/12/20 20:37:29 As per http://msdn.microsoft.com/en-us/library/win
110 return true;
111 }
112
113
Peter Kasting 2012/12/19 20:17:58 Nit: Extra newline
kmadhusu 2012/12/20 20:37:29 Removed.
114 // Returns whether the object is a directory/folder/album. |properties_values|
115 // contains the object property key values.
116 bool IsDirectory(IPortableDeviceValues* properties_values) {
117 DCHECK(properties_values);
118 GUID content_type;
119 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE,
120 &content_type);
121 if (FAILED(hr))
122 return false;
123 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) ||
124 (content_type == WPD_CONTENT_TYPE_FOLDER) ||
125 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) ||
126 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) ||
127 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) ||
128 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM));
129 }
130
131 // Returns the friendly name of the object from the property key values
132 // specified by the |properties_values|.
133 string16 GetObjectName(IPortableDeviceValues* properties_values,
134 bool is_directory) {
135 DCHECK(properties_values);
136 base::win::ScopedCoMem<char16> buffer;
137 REFPROPERTYKEY key =
138 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME;
139 HRESULT hr = properties_values->GetStringValue(key, &buffer);
140 return SUCCEEDED(hr) ? static_cast<const char16*>(buffer) : string16();
Peter Kasting 2012/12/19 20:17:58 Will this compile? I'd think this would barf due
kmadhusu 2012/12/20 20:37:29 It compiles fine for me.
141 }
142
143 // Gets the last modified time of the object from the property key values
144 // specified by the |properties_values|. On success, returns true and fills in
145 // |last_modified_time|. On failure, returns false and |last_modified_time| is
146 // not set.
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 if (last_modified_date.vt == VT_DATE) {
159 SYSTEMTIME system_time;
160 FILETIME file_time;
161 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) &&
162 SystemTimeToFileTime(&system_time, &file_time))
Peter Kasting 2012/12/19 20:17:58 It looks like if these function calls fail, we'll
kmadhusu 2012/12/20 20:37:29 Fixed.
163 *last_modified_time = base::Time::FromFileTime(file_time);
164 } else {
165 *last_modified_time = base::Time();
166 }
167 PropVariantClear(&last_modified_date);
168 return true;
169 }
170
171 // Gets the size of the file object in bytes from the property key values
172 // specified by the |properties_values|. On success, returns true and fills
173 // in |size|. On failure, returns false and |size| is not set.
174 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) {
175 DCHECK(properties_values);
176 DCHECK(size);
177 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(
178 WPD_OBJECT_SIZE, reinterpret_cast<ULONGLONG*>(size));
179 return SUCCEEDED(hr);
180 }
181
182 // Gets the details of the object specified by the |object_id| given the media
183 // transfer protocol |device|. On success, returns true and fills in |name|,
184 // |is_directory|, |size| and |last_modified_time|. On failure, returns false.
185 bool GetObjectDetails(IPortableDevice* device,
186 const string16 object_id,
187 string16* name,
188 bool* is_directory,
189 int64* size,
190 base::Time* last_modified_time) {
191 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
192 DCHECK(device);
193 DCHECK(!object_id.empty());
194 DCHECK(name);
195 DCHECK(is_directory);
196 DCHECK(size);
197 DCHECK(last_modified_time);
198 base::win::ScopedComPtr<IPortableDeviceContent> content;
199 if (!GetDeviceContent(device, content.Receive()))
200 return false;
201
202 base::win::ScopedComPtr<IPortableDeviceProperties> properties;
203 HRESULT hr = content->Properties(properties.Receive());
204 if (FAILED(hr))
205 return false;
206
207 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
208 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection),
209 NULL,
210 CLSCTX_INPROC_SERVER);
211 if (FAILED(hr))
212 return false;
213
214 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) ||
215 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) ||
216 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) ||
217 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) ||
218 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) ||
219 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE)))
220 return false;
221
222 base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
223 hr = properties->GetValues(object_id.c_str(),
224 properties_to_read.get(),
225 properties_values.Receive());
226 if (FAILED(hr))
227 return false;
228
229 *is_directory = IsDirectory(properties_values.get());
230 *name = GetObjectName(properties_values.get(), *is_directory);
231 if (name->empty())
232 return false;
233
234 if (*is_directory) {
235 // Directory entry does not have size and last modified date property key
236 // values.
237 *size = 0;
238 *last_modified_time = base::Time();
239 return true;
240 }
241 return (GetObjectSize(properties_values.get(), size) &&
242 GetLastModifiedTime(properties_values.get(), last_modified_time));
243 }
244
245 // Creates an MTP device object entry for the given |device| and |object_id|.
246 // On success, returns true and fills in |entry|. On failure, returns false
247 // and |entry| is not set.
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|. On failure, returns false and
272 // |object_entries| is not set.
273 bool GetMTPDeviceObjectEntries(IPortableDevice* device,
274 const string16& directory_object_id,
275 bool get_all_entries,
276 const string16& object_name,
277 MTPDeviceObjectEntries* object_entries) {
278 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
279 DCHECK(device);
280 DCHECK(!directory_object_id.empty());
281 DCHECK(object_entries);
282 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids;
283 if (!GetDeviceObjectEnumerator(device, directory_object_id,
284 enum_object_ids.Receive()))
285 return false;
286
287 // Loop calling Next() while S_OK is being returned.
288 DWORD num_objects_to_request = 10;
289 for (HRESULT hr = S_OK; hr == S_OK;) {
290 DWORD num_objects_fetched = 0;
291 scoped_array<char16*> object_ids(new char16*[num_objects_to_request]);
Peter Kasting 2012/12/19 20:17:58 Does new char16*[] zero the pointers? I can't rem
kmadhusu 2012/12/20 20:37:29 Since scoped_array is deprecated, modified code to
292 hr = enum_object_ids->Next(num_objects_to_request, object_ids.get(),
293 &num_objects_fetched);
Peter Kasting 2012/12/19 20:17:58 MSDN for Next() says the strings should be freed w
kmadhusu 2012/12/20 20:37:29 You are right. I should call CoTaskMemFree() to fr
294 for (DWORD index = 0; index < num_objects_fetched; ++index) {
295 MTPDeviceObjectEntry entry;
296 if (GetMTPDeviceObjectEntry(device, object_ids[index], &entry)) {
297 if (get_all_entries) {
298 object_entries->push_back(entry);
299 } else if (entry.file_name() == object_name) {
300 object_entries->push_back(entry); // Object entry found.
301 break;
302 }
303 }
304 }
305 }
306 return true;
307 }
308
309 } // namespace
310
311
312 // MTPDeviceOperationsUtil ----------------------------------------------------
313
314 // static
315 bool MTPDeviceOperationsUtil::OpenDevice(
316 const string16& pnp_device_id,
317 base::win::ScopedComPtr<IPortableDevice>* device) {
318 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
319 DCHECK(!pnp_device_id.empty());
320 DCHECK(device);
321 base::win::ScopedComPtr<IPortableDeviceValues> client_info;
322 if (!GetClientInformation(&client_info))
323 return false;
324 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
325 CLSCTX_INPROC_SERVER);
326 if (FAILED(hr))
327 return false;
328
329 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
330 if (SUCCEEDED(hr))
331 return true;
332 if (hr == E_ACCESSDENIED)
333 DPLOG(ERROR) << "Access denied to open the device";
334 return false;
335 }
336
337 // static
338 base::PlatformFileError MTPDeviceOperationsUtil::GetFileEntryInfo(
339 IPortableDevice* device,
340 const string16& object_id,
341 base::PlatformFileInfo* file_entry_info) {
342 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
343 DCHECK(device);
344 DCHECK(!object_id.empty());
345 DCHECK(file_entry_info);
346 MTPDeviceObjectEntry entry;
347 if (!GetMTPDeviceObjectEntry(device, object_id, &entry))
348 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
349
350 file_entry_info->size = entry.size();
351 file_entry_info->is_directory = entry.is_directory();
352 file_entry_info->is_symbolic_link = false;
353 file_entry_info->last_modified = entry.last_modified_time();
354 file_entry_info->last_accessed = entry.last_modified_time();
355 file_entry_info->creation_time = base::Time();
356 return base::PLATFORM_FILE_OK;
357 }
358
359 // static
360 bool MTPDeviceOperationsUtil::GetDirectoryEntries(
361 IPortableDevice* device,
362 const string16& directory_object_id,
363 MTPDeviceObjectEntries* object_entries) {
364 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
365 return GetMTPDeviceObjectEntries(device, directory_object_id, true,
366 string16(), object_entries);
367 }
368
369 // static
370 bool MTPDeviceOperationsUtil::WriteFileObjectData(
371 IPortableDevice* device,
372 const string16& file_object_id,
373 const FilePath& local_path) {
374 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
375 DCHECK(device);
376 DCHECK(!file_object_id.empty());
377 DCHECK(!local_path.value().empty());
378 base::win::ScopedComPtr<IPortableDeviceContent> content;
379 if (!GetDeviceContent(device, content.Receive()))
380 return false;
381
382 base::win::ScopedComPtr<IPortableDeviceResources> resources;
383 HRESULT hr = content->Transfer(resources.Receive());
384 if (FAILED(hr))
385 return false;
386
387 base::win::ScopedComPtr<IStream> file_stream;
388 DWORD optimal_transfer_size = 0;
389 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT,
390 STGM_READ, &optimal_transfer_size,
391 file_stream.Receive());
392 if (FAILED(hr))
393 return false;
394 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size,
395 local_path);
396 }
397
398 // static
399 string16 MTPDeviceOperationsUtil::GetObjectIdFromName(
400 IPortableDevice* device,
401 const string16& parent_id,
402 const string16& object_name) {
403 MTPDeviceObjectEntries object_entries;
404 if (!GetMTPDeviceObjectEntries(device, parent_id, false, object_name,
405 &object_entries) ||
406 object_entries.empty())
407 return string16();
408 DCHECK_EQ(object_entries.size(), static_cast<size_t>(1));
Peter Kasting 2012/12/19 20:17:58 Nit: DCHECK_EQ(1U, object_entries.size());
kmadhusu 2012/12/20 20:37:29 Done.
409 return object_entries[0].object_id();
410 }
411
412 MTPDeviceOperationsUtil::MTPDeviceOperationsUtil() {
Peter Kasting 2012/12/19 20:17:58 No need to actually define this. (This becomes cl
kmadhusu 2012/12/20 20:37:29 Done.
413 }
414
415 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698