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 // MTPDeviceOperationsUtil implementation. | |
| 6 | |
| 7 #include "chrome/browser/media_gallery/win/mtp_device_operations_util.h" | |
| 8 | |
| 9 #include <portabledevice.h> | |
| 10 | |
| 11 #include "base/logging.h" | |
| 12 #include "base/string_util.h" | |
| 13 #include "base/threading/sequenced_worker_pool.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 "content/public/browser/browser_thread.h" | |
| 18 | |
| 19 namespace chrome { | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 // Name of the client application that communicates with the MTP device. | |
| 24 const char16 kClientName[] = L"Chromium"; | |
| 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. | |
| 41 (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName); | |
| 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|. On failure, returns false and | |
| 65 // |enum_object_ids| is not set. | |
| 66 bool GetDeviceObjectEnumerator(IPortableDevice* device, | |
| 67 const string16& parent_id, | |
| 68 IEnumPortableDeviceObjectIDs** enum_object_ids) { | |
| 69 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 70 DCHECK(device); | |
| 71 DCHECK(!parent_id.empty()); | |
| 72 DCHECK(enum_object_ids); | |
| 73 base::win::ScopedComPtr<IPortableDeviceContent> content; | |
| 74 if (!GetDeviceContent(device, content.Receive())) | |
| 75 return false; | |
| 76 | |
| 77 HRESULT hr = content->EnumObjects(0, parent_id.c_str(), NULL, | |
| 78 enum_object_ids); | |
| 79 return SUCCEEDED(hr); | |
| 80 } | |
| 81 | |
| 82 // Reads data from |stream| into a |data| string. | |
| 83 void ReadStream(IStream* stream, size_t size, std::string* data) { | |
| 84 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 85 DCHECK(stream); | |
| 86 DCHECK_GT(size, 0u); | |
| 87 DCHECK(data); | |
| 88 DWORD read = 0; | |
| 89 HRESULT hr = S_OK; | |
| 90 do { | |
| 91 std::string buffer; | |
| 92 hr = stream->Read(WriteInto(&buffer, size + 1), size, &read); | |
| 93 DCHECK(hr == S_OK || hr == S_FALSE || hr == E_PENDING); | |
| 94 if (read) { | |
| 95 buffer.erase(read); | |
| 96 DCHECK_EQ(read, buffer.length()); | |
| 97 *data += buffer; | |
| 98 } | |
| 99 } while ((read > 0) && SUCCEEDED(hr)); | |
| 100 } | |
| 101 | |
| 102 // Returns true, if the object is a directory/folder/album otherwise returns | |
| 103 // false. |properties_values| contains the object property key values. | |
| 104 bool IsDirectory(IPortableDeviceValues* properties_values) { | |
| 105 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 106 DCHECK(properties_values); | |
| 107 GUID content_type; | |
| 108 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, | |
| 109 &content_type); | |
| 110 DCHECK(SUCCEEDED(hr)); | |
| 111 return ((content_type == WPD_CONTENT_TYPE_AUDIO_ALBUM) || | |
| 112 (content_type == WPD_CONTENT_TYPE_FOLDER) || | |
| 113 (content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) || | |
| 114 (content_type == WPD_CONTENT_TYPE_IMAGE_ALBUM) || | |
| 115 (content_type == WPD_CONTENT_TYPE_MIXED_CONTENT_ALBUM) || | |
| 116 (content_type == WPD_CONTENT_TYPE_VIDEO_ALBUM)); | |
| 117 } | |
| 118 | |
| 119 // Returns the object extension type based on the object format specified | |
| 120 // by the |properties_values|. If the object is an unsupport format, this | |
| 121 // function returns an empty string. | |
| 122 string16 GetObjectExtension(IPortableDeviceValues* properties_values) { | |
| 123 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 124 DCHECK(properties_values); | |
| 125 GUID format; | |
| 126 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_FORMAT, &format); | |
| 127 DCHECK(SUCCEEDED(hr)); | |
| 128 if (IsEqualGUID(WPD_OBJECT_FORMAT_BMP, format)) | |
| 129 return kbmpFormat; | |
| 130 if (IsEqualGUID(WPD_OBJECT_FORMAT_EXIF, format)) | |
| 131 return kexifFormat; | |
| 132 if (IsEqualGUID(WPD_OBJECT_FORMAT_GIF, format)) | |
| 133 return kgifFormat; | |
| 134 if (IsEqualGUID(WPD_OBJECT_FORMAT_JFIF, format)) | |
| 135 return kjfifFormat; | |
| 136 if (IsEqualGUID(WPD_OBJECT_FORMAT_JP2, format)) | |
| 137 return kjp2Format; | |
| 138 if (IsEqualGUID(WPD_OBJECT_FORMAT_JPEGXR, format)) | |
| 139 return kjpegxrFormat; | |
| 140 if (IsEqualGUID(WPD_OBJECT_FORMAT_JPX, format)) | |
| 141 return kjpxFormat; | |
| 142 if (IsEqualGUID(WPD_OBJECT_FORMAT_PICT, format)) | |
| 143 return kpictFormat; | |
| 144 if (IsEqualGUID(WPD_OBJECT_FORMAT_PNG, format)) | |
| 145 return kpngFormat; | |
| 146 return string16(); | |
| 147 } | |
| 148 | |
| 149 // Gets the friendly name of the object from the property key values specified | |
| 150 // by the |properties_values|. On success, returns true and fills in |name|. | |
| 151 // On failure, returns false and |name| is not set. | |
| 152 bool GetObjectName(IPortableDeviceValues* properties_values, | |
| 153 bool is_directory, | |
| 154 string16* name) { | |
| 155 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 156 DCHECK(properties_values); | |
| 157 DCHECK(name); | |
| 158 base::win::ScopedCoMem<char16> buffer; | |
| 159 HRESULT hr = properties_values->GetStringValue(WPD_OBJECT_NAME, &buffer); | |
| 160 DCHECK(SUCCEEDED(hr)); | |
| 161 *name = static_cast<const char16*>(buffer); | |
| 162 if (name->empty()) | |
| 163 return false; | |
| 164 if (is_directory) | |
| 165 return true; | |
| 166 | |
| 167 // Some MTP device objects does not have extension as part of their name. | |
| 168 // Check to see if an extension exists in the given object name. | |
| 169 if (name->find_first_of(L".") == string16::npos) | |
| 170 *name += GetObjectExtension(properties_values); | |
| 171 return true; | |
| 172 } | |
| 173 | |
| 174 // Gets the last modified time of the object from the property key values | |
| 175 // specified by the |properties_values|. On success, returns true and fills in | |
| 176 // |last_modified_time|. On failure, returns false and |last_modified_time| is | |
| 177 // not set. | |
| 178 bool GetLastModifiedTime(IPortableDeviceValues* properties_values, | |
| 179 base::Time* last_modified_time) { | |
| 180 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 181 DCHECK(properties_values); | |
| 182 DCHECK(last_modified_time); | |
| 183 PROPVARIANT last_modified_date = {0}; | |
| 184 PropVariantInit(&last_modified_date); | |
| 185 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED, | |
| 186 &last_modified_date); | |
| 187 if (FAILED(hr)) | |
| 188 return false; | |
| 189 | |
| 190 if (SUCCEEDED(hr) && last_modified_date.vt == VT_DATE) { | |
| 191 SYSTEMTIME system_time; | |
| 192 FILETIME file_time; | |
| 193 VariantTimeToSystemTime(last_modified_date.date, &system_time); | |
|
Lei Zhang
2012/11/01 02:34:57
This may fail.
kmadhusu
2012/11/01 17:59:00
Fixed.
| |
| 194 SystemTimeToFileTime(&system_time, &file_time); | |
| 195 *last_modified_time = base::Time::FromFileTime(file_time); | |
| 196 } | |
| 197 PropVariantClear(&last_modified_date); | |
| 198 return true; | |
| 199 } | |
| 200 | |
| 201 // Gets the size of the file object in bytes from the property key values | |
| 202 // specified by the |properties_values|. On success, returns true and fills | |
| 203 // in |size|. On failure, returns false and |size| is not set. | |
| 204 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) { | |
| 205 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 206 DCHECK(properties_values); | |
| 207 DCHECK(size); | |
| 208 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue( | |
| 209 WPD_OBJECT_SIZE, reinterpret_cast<ULONGLONG*>(size)); | |
| 210 return SUCCEEDED(hr); | |
| 211 } | |
| 212 | |
| 213 // Gets the details of the object specified by the |object_id| given the media | |
| 214 // transfer protocol |device|. On success, returns true and fills in |name|, | |
| 215 // |is_directory|, |size| and |last_modified_time|. On failure, returns false | |
| 216 // and all the output params are not set. | |
| 217 bool GetObjectDetails(IPortableDevice* device, | |
| 218 const string16 object_id, | |
| 219 string16* name, | |
| 220 bool* is_directory, | |
| 221 int64* size, | |
| 222 base::Time* last_modified_time) { | |
| 223 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 224 DCHECK(device); | |
| 225 DCHECK(!object_id.empty()); | |
| 226 DCHECK(name); | |
| 227 DCHECK(is_directory); | |
| 228 DCHECK(size); | |
| 229 DCHECK(last_modified_time); | |
| 230 base::win::ScopedComPtr<IPortableDeviceContent> content; | |
| 231 if (!GetDeviceContent(device, content.Receive())) | |
| 232 return false; | |
| 233 | |
| 234 base::win::ScopedComPtr<IPortableDeviceProperties> properties; | |
| 235 HRESULT hr = content->Properties(properties.Receive()); | |
| 236 if (FAILED(hr)) | |
| 237 return false; | |
| 238 | |
| 239 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; | |
| 240 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection), | |
| 241 NULL, | |
| 242 CLSCTX_INPROC_SERVER); | |
| 243 if (FAILED(hr)) | |
| 244 return false; | |
| 245 | |
| 246 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) || | |
| 247 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) || | |
| 248 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) || | |
| 249 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) || | |
| 250 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE))) | |
| 251 return false; | |
| 252 | |
| 253 base::win::ScopedComPtr<IPortableDeviceValues> properties_values; | |
| 254 hr = properties->GetValues(object_id.c_str(), | |
| 255 properties_to_read.get(), | |
| 256 properties_values.Receive()); | |
| 257 if (FAILED(hr)) | |
| 258 return false; | |
| 259 | |
| 260 *is_directory = IsDirectory(properties_values.get()); | |
| 261 if (!GetObjectName(properties_values.get(), *is_directory, name)) | |
| 262 return false; | |
| 263 | |
| 264 if (*is_directory) { | |
| 265 // Directory entry does not have size and last modified date property key | |
| 266 // values. | |
| 267 *size = 0; | |
| 268 *last_modified_time = base::Time(); | |
| 269 return true; | |
| 270 } | |
| 271 return (GetObjectSize(properties_values.get(), size) && | |
| 272 GetLastModifiedTime(properties_values.get(), last_modified_time)); | |
| 273 } | |
| 274 | |
| 275 } // namespace | |
| 276 | |
| 277 | |
| 278 // MTPDeviceOperationsUtil ---------------------------------------------------- | |
| 279 | |
| 280 // static | |
| 281 bool MTPDeviceOperationsUtil::OpenDevice( | |
| 282 const string16& pnp_device_id, | |
| 283 base::win::ScopedComPtr<IPortableDevice>* device) { | |
| 284 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 285 DCHECK(!pnp_device_id.empty()); | |
| 286 DCHECK(device); | |
| 287 base::win::ScopedComPtr<IPortableDeviceValues> client_info; | |
| 288 if (!GetClientInformation(&client_info)) | |
| 289 return false; | |
| 290 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL, | |
| 291 CLSCTX_INPROC_SERVER); | |
| 292 if (FAILED(hr)) | |
| 293 return false; | |
| 294 | |
| 295 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get()); | |
| 296 if (SUCCEEDED(hr)) | |
| 297 return true; | |
| 298 if (hr == E_ACCESSDENIED) | |
| 299 DPLOG(ERROR) << "Access denied to open the device"; | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 303 // static | |
| 304 base::PlatformFileError MTPDeviceOperationsUtil::GetFileEntryInfo( | |
| 305 IPortableDevice* device, | |
| 306 const string16& object_id, | |
| 307 base::PlatformFileInfo* file_entry_info) { | |
| 308 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 309 DCHECK(device); | |
| 310 DCHECK(!object_id.empty()); | |
| 311 DCHECK(file_entry_info); | |
| 312 MTPDeviceObjectEntry entry; | |
| 313 if (!GetMTPDeviceObjectEntry(device, object_id, &entry)) | |
| 314 return base::PLATFORM_FILE_ERROR_NOT_FOUND; | |
| 315 | |
| 316 file_entry_info->size = entry.size(); | |
| 317 file_entry_info->is_directory = entry.is_directory(); | |
| 318 file_entry_info->is_symbolic_link = false; | |
| 319 file_entry_info->last_modified = entry.last_modified_time(); | |
| 320 file_entry_info->last_accessed = entry.last_modified_time(); | |
| 321 file_entry_info->creation_time = base::Time(); | |
| 322 return base::PLATFORM_FILE_OK; | |
| 323 } | |
| 324 | |
| 325 // static | |
| 326 bool MTPDeviceOperationsUtil::GetDirectoryEntries( | |
| 327 IPortableDevice* device, | |
| 328 const string16& directory_object_id, | |
| 329 MTPDeviceObjectEntries* object_entries) { | |
| 330 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 331 DCHECK(device); | |
| 332 DCHECK(!directory_object_id.empty()); | |
| 333 DCHECK(object_entries); | |
| 334 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids; | |
| 335 if (!GetDeviceObjectEnumerator(device, directory_object_id, | |
| 336 enum_object_ids.Receive())) | |
| 337 return false; | |
| 338 | |
| 339 // Loop calling Next() while S_OK is being returned. | |
| 340 DWORD num_objects_to_request = 10; | |
| 341 HRESULT hr = S_OK; | |
| 342 while (hr == S_OK) { | |
| 343 DWORD num_objects_fetched = 0; | |
| 344 scoped_array<char16*> object_ids(new char16*[num_objects_to_request]); | |
| 345 hr = enum_object_ids->Next(num_objects_to_request, object_ids.get(), | |
| 346 &num_objects_fetched); | |
| 347 for (DWORD index = 0; index < num_objects_fetched; index++) { | |
| 348 MTPDeviceObjectEntry entry; | |
| 349 if (GetMTPDeviceObjectEntry(device, object_ids[index], &entry)) | |
| 350 object_entries->push_back(entry); | |
| 351 } | |
| 352 } | |
| 353 return true; | |
| 354 } | |
| 355 | |
| 356 // static | |
| 357 bool MTPDeviceOperationsUtil::GetFileObjectData(IPortableDevice* device, | |
| 358 const string16& file_object_id, | |
| 359 std::string* file_data) { | |
| 360 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 361 DCHECK(device); | |
| 362 DCHECK(!file_object_id.empty()); | |
| 363 DCHECK(file_data); | |
| 364 base::win::ScopedComPtr<IPortableDeviceContent> content; | |
| 365 if (!GetDeviceContent(device, content.Receive())) | |
| 366 return false; | |
| 367 | |
| 368 base::win::ScopedComPtr<IPortableDeviceResources> resources; | |
| 369 HRESULT hr = content->Transfer(resources.Receive()); | |
| 370 if (FAILED(hr)) | |
| 371 return false; | |
| 372 | |
| 373 base::win::ScopedComPtr<IStream> file_stream; | |
| 374 DWORD optimal_transfer_size = 0; | |
| 375 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT, | |
| 376 STGM_READ, &optimal_transfer_size, | |
| 377 file_stream.Receive()); | |
| 378 if (FAILED(hr)) | |
| 379 return false; | |
| 380 ReadStream(file_stream.get(), optimal_transfer_size, file_data); | |
| 381 return true; | |
| 382 } | |
| 383 | |
| 384 // static | |
| 385 string16 MTPDeviceOperationsUtil::GetObjectIdFromName( | |
| 386 IPortableDevice* device, | |
| 387 const string16& parent_id, | |
| 388 const string16& object_name) { | |
| 389 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 390 DCHECK(device); | |
| 391 DCHECK(!parent_id.empty()); | |
| 392 DCHECK(!object_name.empty()); | |
| 393 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids; | |
| 394 if (!GetDeviceObjectEnumerator(device, parent_id, enum_object_ids.Receive())) | |
| 395 return string16(); | |
| 396 | |
| 397 DWORD num_objects_to_request = 10; | |
| 398 HRESULT hr = S_OK; | |
| 399 while (hr == S_OK) { | |
| 400 DWORD num_objects_fetched = 0; | |
| 401 scoped_array<char16*> object_ids(new char16*[num_objects_to_request]); | |
| 402 hr = enum_object_ids->Next(num_objects_to_request, object_ids.get(), | |
| 403 &num_objects_fetched); | |
| 404 for (DWORD index = 0; index < num_objects_fetched; index++) { | |
| 405 MTPDeviceObjectEntry entry; | |
| 406 if (!GetMTPDeviceObjectEntry(device, object_ids[index], &entry)) | |
| 407 return string16(); | |
| 408 if (entry.file_name() == object_name) | |
| 409 return entry.object_id(); | |
| 410 } | |
| 411 } | |
| 412 return string16(); | |
| 413 } | |
| 414 | |
| 415 MTPDeviceOperationsUtil::MTPDeviceOperationsUtil() { | |
| 416 } | |
| 417 | |
| 418 // static | |
| 419 bool MTPDeviceOperationsUtil::GetMTPDeviceObjectEntry( | |
| 420 IPortableDevice* device, | |
| 421 const string16& object_id, | |
| 422 MTPDeviceObjectEntry* entry) { | |
| 423 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 424 DCHECK(device); | |
| 425 DCHECK(!object_id.empty()); | |
| 426 DCHECK(entry); | |
| 427 string16 name; | |
| 428 bool is_directory; | |
| 429 int64 size; | |
| 430 base::Time last_modified_time; | |
| 431 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size, | |
| 432 &last_modified_time)) | |
| 433 return false; | |
| 434 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size, | |
| 435 last_modified_time); | |
| 436 return true; | |
| 437 } | |
| 438 | |
| 439 } // namespace chrome | |
| OLD | NEW |