Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |