| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2013 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 <algorithm> |
| 10 |
| 11 #include "base/basictypes.h" |
| 12 #include "base/file_path.h" |
| 13 #include "base/file_util.h" |
| 14 #include "base/logging.h" |
| 15 #include "base/string_util.h" |
| 16 #include "base/threading/thread_restrictions.h" |
| 17 #include "base/time.h" |
| 18 #include "base/win/scoped_co_mem.h" |
| 19 #include "chrome/browser/system_monitor/removable_device_constants.h" |
| 20 #include "chrome/common/chrome_constants.h" |
| 21 #include "content/public/browser/browser_thread.h" |
| 22 |
| 23 namespace chrome { |
| 24 |
| 25 namespace media_transfer_protocol { |
| 26 |
| 27 namespace { |
| 28 |
| 29 // On success, returns true and updates |client_info| with a reference to an |
| 30 // IPortableDeviceValues interface that holds information about the |
| 31 // application that communicates with the device. |
| 32 bool GetClientInformation( |
| 33 base::win::ScopedComPtr<IPortableDeviceValues>* client_info) { |
| 34 base::ThreadRestrictions::AssertIOAllowed(); |
| 35 DCHECK(client_info); |
| 36 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues), |
| 37 NULL, CLSCTX_INPROC_SERVER); |
| 38 if (FAILED(hr)) { |
| 39 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues"; |
| 40 return false; |
| 41 } |
| 42 |
| 43 (*client_info)->SetStringValue(WPD_CLIENT_NAME, |
| 44 chrome::kBrowserProcessExecutableName); |
| 45 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0); |
| 46 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); |
| 47 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0); |
| 48 (*client_info)->SetUnsignedIntegerValue( |
| 49 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION); |
| 50 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, |
| 51 GENERIC_READ); |
| 52 return true; |
| 53 } |
| 54 |
| 55 // Gets the content interface of the portable |device|. On success, returns |
| 56 // the IPortableDeviceContent interface. On failure, returns NULL. |
| 57 base::win::ScopedComPtr<IPortableDeviceContent> GetDeviceContent( |
| 58 IPortableDevice* device) { |
| 59 base::ThreadRestrictions::AssertIOAllowed(); |
| 60 DCHECK(device); |
| 61 base::win::ScopedComPtr<IPortableDeviceContent> content; |
| 62 if (SUCCEEDED(device->Content(content.Receive()))) |
| 63 return content; |
| 64 return base::win::ScopedComPtr<IPortableDeviceContent>(); |
| 65 } |
| 66 |
| 67 // On success, returns IEnumPortableDeviceObjectIDs interface to enumerate |
| 68 // the device objects. On failure, returns NULL. |
| 69 // |parent_id| specifies the parent object identifier. |
| 70 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> GetDeviceObjectEnumerator( |
| 71 IPortableDevice* device, |
| 72 const string16& parent_id) { |
| 73 base::ThreadRestrictions::AssertIOAllowed(); |
| 74 DCHECK(device); |
| 75 DCHECK(!parent_id.empty()); |
| 76 base::win::ScopedComPtr<IPortableDeviceContent> content = |
| 77 GetDeviceContent(device); |
| 78 if (!content) |
| 79 return base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs>(); |
| 80 |
| 81 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids; |
| 82 if (SUCCEEDED(content->EnumObjects(0, parent_id.c_str(), NULL, |
| 83 enum_object_ids.Receive()))) |
| 84 return enum_object_ids; |
| 85 return base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs>(); |
| 86 } |
| 87 |
| 88 // Writes data from |stream| to the file specified by |local_path|. On success, |
| 89 // returns true and the stream contents are appended to the file. |
| 90 // TODO(kmadhusu) Deprecate this function after fixing crbug.com/110119. |
| 91 bool WriteStreamContentsToFile(IStream* stream, |
| 92 size_t optimal_transfer_size, |
| 93 const FilePath& local_path) { |
| 94 base::ThreadRestrictions::AssertIOAllowed(); |
| 95 DCHECK(stream); |
| 96 DCHECK(!local_path.empty()); |
| 97 if (optimal_transfer_size == 0U) |
| 98 return false; |
| 99 DWORD read = 0; |
| 100 HRESULT hr = S_OK; |
| 101 std::string buffer; |
| 102 do { |
| 103 hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1), |
| 104 optimal_transfer_size, &read); |
| 105 // IStream::Read() returns S_FALSE when the actual number of bytes read from |
| 106 // the stream object is less than the number of bytes requested (aka |
| 107 // |optimal_transfer_size|). This indicates the end of the stream has been |
| 108 // reached. Therefore, it is fine to return true when Read() returns |
| 109 // S_FALSE. |
| 110 if ((hr != S_OK) && (hr != S_FALSE)) |
| 111 return false; |
| 112 if (read) { |
| 113 int data_len = std::min<int>(read, buffer.length()); |
| 114 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != |
| 115 data_len) |
| 116 return false; |
| 117 } |
| 118 } while (read > 0); |
| 119 return true; |
| 120 } |
| 121 |
| 122 // Returns whether the object is a directory/folder/album. |properties_values| |
| 123 // contains the object property key values. |
| 124 bool IsDirectory(IPortableDeviceValues* properties_values) { |
| 125 DCHECK(properties_values); |
| 126 GUID content_type; |
| 127 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, |
| 128 &content_type); |
| 129 if (FAILED(hr)) |
| 130 return false; |
| 131 // TODO(kmadhusu): |content_type| can be an image or audio or video or mixed |
| 132 // album. It is not clear whether an album is a collection of physical objects |
| 133 // or virtual objects. Investigate this in detail. |
| 134 |
| 135 // The root storage object describes its content type as |
| 136 // WPD_CONTENT_FUNCTIONAL_OBJECT. |
| 137 return (content_type == WPD_CONTENT_TYPE_FOLDER || |
| 138 content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT); |
| 139 } |
| 140 |
| 141 // Returns the friendly name of the object from the property key values |
| 142 // specified by the |properties_values|. |
| 143 string16 GetObjectName(IPortableDeviceValues* properties_values, |
| 144 bool is_directory) { |
| 145 DCHECK(properties_values); |
| 146 base::win::ScopedCoMem<char16> buffer; |
| 147 REFPROPERTYKEY key = |
| 148 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME; |
| 149 HRESULT hr = properties_values->GetStringValue(key, &buffer); |
| 150 string16 result; |
| 151 if (SUCCEEDED(hr)) |
| 152 result.assign(buffer); |
| 153 return result; |
| 154 } |
| 155 |
| 156 // Gets the last modified time of the object from the property key values |
| 157 // specified by the |properties_values|. On success, returns true and fills in |
| 158 // |last_modified_time|. |
| 159 bool GetLastModifiedTime(IPortableDeviceValues* properties_values, |
| 160 base::Time* last_modified_time) { |
| 161 DCHECK(properties_values); |
| 162 DCHECK(last_modified_time); |
| 163 PROPVARIANT last_modified_date = {0}; |
| 164 PropVariantInit(&last_modified_date); |
| 165 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED, |
| 166 &last_modified_date); |
| 167 if (FAILED(hr)) |
| 168 return false; |
| 169 |
| 170 bool last_modified_time_set = (last_modified_date.vt == VT_DATE); |
| 171 if (last_modified_time_set) { |
| 172 SYSTEMTIME system_time; |
| 173 FILETIME file_time; |
| 174 if (VariantTimeToSystemTime(last_modified_date.date, &system_time) && |
| 175 SystemTimeToFileTime(&system_time, &file_time)) |
| 176 *last_modified_time = base::Time::FromFileTime(file_time); |
| 177 else |
| 178 last_modified_time_set = false; |
| 179 } |
| 180 PropVariantClear(&last_modified_date); |
| 181 return last_modified_time_set; |
| 182 } |
| 183 |
| 184 // Gets the size of the file object in bytes from the property key values |
| 185 // specified by the |properties_values|. On success, returns true and fills |
| 186 // in |size|. |
| 187 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) { |
| 188 DCHECK(properties_values); |
| 189 DCHECK(size); |
| 190 ULONGLONG actual_size; |
| 191 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, |
| 192 &actual_size); |
| 193 bool success = SUCCEEDED(hr) && (actual_size <= kint64max); |
| 194 if (success) |
| 195 *size = static_cast<int64>(actual_size); |
| 196 return success; |
| 197 } |
| 198 |
| 199 // Gets the details of the object specified by the |object_id| given the media |
| 200 // transfer protocol |device|. On success, returns true and fills in |name|, |
| 201 // |is_directory|, |size| and |last_modified_time|. |
| 202 bool GetObjectDetails(IPortableDevice* device, |
| 203 const string16 object_id, |
| 204 string16* name, |
| 205 bool* is_directory, |
| 206 int64* size, |
| 207 base::Time* last_modified_time) { |
| 208 base::ThreadRestrictions::AssertIOAllowed(); |
| 209 DCHECK(device); |
| 210 DCHECK(!object_id.empty()); |
| 211 DCHECK(name); |
| 212 DCHECK(is_directory); |
| 213 DCHECK(size); |
| 214 DCHECK(last_modified_time); |
| 215 base::win::ScopedComPtr<IPortableDeviceContent> content = |
| 216 GetDeviceContent(device); |
| 217 if (!content) |
| 218 return false; |
| 219 |
| 220 base::win::ScopedComPtr<IPortableDeviceProperties> properties; |
| 221 HRESULT hr = content->Properties(properties.Receive()); |
| 222 if (FAILED(hr)) |
| 223 return false; |
| 224 |
| 225 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; |
| 226 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection), |
| 227 NULL, |
| 228 CLSCTX_INPROC_SERVER); |
| 229 if (FAILED(hr)) |
| 230 return false; |
| 231 |
| 232 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) || |
| 233 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) || |
| 234 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) || |
| 235 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) || |
| 236 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) || |
| 237 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE))) |
| 238 return false; |
| 239 |
| 240 base::win::ScopedComPtr<IPortableDeviceValues> properties_values; |
| 241 hr = properties->GetValues(object_id.c_str(), |
| 242 properties_to_read.get(), |
| 243 properties_values.Receive()); |
| 244 if (FAILED(hr)) |
| 245 return false; |
| 246 |
| 247 *is_directory = IsDirectory(properties_values.get()); |
| 248 *name = GetObjectName(properties_values.get(), *is_directory); |
| 249 if (name->empty()) |
| 250 return false; |
| 251 |
| 252 if (*is_directory) { |
| 253 // Directory entry does not have size and last modified date property key |
| 254 // values. |
| 255 *size = 0; |
| 256 *last_modified_time = base::Time(); |
| 257 return true; |
| 258 } |
| 259 return (GetObjectSize(properties_values.get(), size) && |
| 260 GetLastModifiedTime(properties_values.get(), last_modified_time)); |
| 261 } |
| 262 |
| 263 // Creates an MTP device object entry for the given |device| and |object_id|. |
| 264 // On success, returns true and fills in |entry|. |
| 265 bool GetMTPDeviceObjectEntry(IPortableDevice* device, |
| 266 const string16& object_id, |
| 267 MTPDeviceObjectEntry* entry) { |
| 268 base::ThreadRestrictions::AssertIOAllowed(); |
| 269 DCHECK(device); |
| 270 DCHECK(!object_id.empty()); |
| 271 DCHECK(entry); |
| 272 string16 name; |
| 273 bool is_directory; |
| 274 int64 size; |
| 275 base::Time last_modified_time; |
| 276 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size, |
| 277 &last_modified_time)) |
| 278 return false; |
| 279 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size, |
| 280 last_modified_time); |
| 281 return true; |
| 282 } |
| 283 |
| 284 // Gets the entries of the directory specified by |directory_object_id| from |
| 285 // the given MTP |device|. Set |get_all_entries| to true to get all the entries |
| 286 // of the directory. To request a specific object entry, put the object name in |
| 287 // |object_name|. Leave |object_name| blank to request all entries. On |
| 288 // success returns true and set |object_entries|. |
| 289 bool GetMTPDeviceObjectEntries(IPortableDevice* device, |
| 290 const string16& directory_object_id, |
| 291 const string16& object_name, |
| 292 MTPDeviceObjectEntries* object_entries) { |
| 293 base::ThreadRestrictions::AssertIOAllowed(); |
| 294 DCHECK(device); |
| 295 DCHECK(!directory_object_id.empty()); |
| 296 DCHECK(object_entries); |
| 297 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids = |
| 298 GetDeviceObjectEnumerator(device, directory_object_id); |
| 299 if (!enum_object_ids) |
| 300 return false; |
| 301 |
| 302 // Loop calling Next() while S_OK is being returned. |
| 303 const DWORD num_objects_to_request = 10; |
| 304 const bool get_all_entries = object_name.empty(); |
| 305 for (HRESULT hr = S_OK; hr == S_OK;) { |
| 306 DWORD num_objects_fetched = 0; |
| 307 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]); |
| 308 hr = enum_object_ids->Next(num_objects_to_request, |
| 309 object_ids.get(), |
| 310 &num_objects_fetched); |
| 311 for (DWORD index = 0; index < num_objects_fetched; ++index) { |
| 312 MTPDeviceObjectEntry entry; |
| 313 if (GetMTPDeviceObjectEntry(device, |
| 314 object_ids[index], |
| 315 &entry)) { |
| 316 if (get_all_entries) { |
| 317 object_entries->push_back(entry); |
| 318 } else if (entry.name == object_name) { |
| 319 object_entries->push_back(entry); // Object entry found. |
| 320 break; |
| 321 } |
| 322 } |
| 323 } |
| 324 for (DWORD index = 0; index < num_objects_fetched; ++index) |
| 325 CoTaskMemFree(object_ids[index]); |
| 326 } |
| 327 return true; |
| 328 } |
| 329 |
| 330 } // namespace |
| 331 |
| 332 base::win::ScopedComPtr<IPortableDevice> OpenDevice( |
| 333 const string16& pnp_device_id) { |
| 334 base::ThreadRestrictions::AssertIOAllowed(); |
| 335 DCHECK(!pnp_device_id.empty()); |
| 336 base::win::ScopedComPtr<IPortableDeviceValues> client_info; |
| 337 if (!GetClientInformation(&client_info)) |
| 338 return base::win::ScopedComPtr<IPortableDevice>(); |
| 339 base::win::ScopedComPtr<IPortableDevice> device; |
| 340 HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL, |
| 341 CLSCTX_INPROC_SERVER); |
| 342 if (FAILED(hr)) |
| 343 return base::win::ScopedComPtr<IPortableDevice>(); |
| 344 |
| 345 hr = device->Open(pnp_device_id.c_str(), client_info.get()); |
| 346 if (SUCCEEDED(hr)) |
| 347 return device; |
| 348 if (hr == E_ACCESSDENIED) |
| 349 DPLOG(ERROR) << "Access denied to open the device"; |
| 350 return base::win::ScopedComPtr<IPortableDevice>(); |
| 351 } |
| 352 |
| 353 base::PlatformFileError GetFileEntryInfo( |
| 354 IPortableDevice* device, |
| 355 const string16& object_id, |
| 356 base::PlatformFileInfo* file_entry_info) { |
| 357 DCHECK(device); |
| 358 DCHECK(!object_id.empty()); |
| 359 DCHECK(file_entry_info); |
| 360 MTPDeviceObjectEntry entry; |
| 361 if (!GetMTPDeviceObjectEntry(device, object_id, &entry)) |
| 362 return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| 363 |
| 364 file_entry_info->size = entry.size; |
| 365 file_entry_info->is_directory = entry.is_directory; |
| 366 file_entry_info->is_symbolic_link = false; |
| 367 file_entry_info->last_modified = entry.last_modified_time; |
| 368 file_entry_info->last_accessed = entry.last_modified_time; |
| 369 file_entry_info->creation_time = base::Time(); |
| 370 return base::PLATFORM_FILE_OK; |
| 371 } |
| 372 |
| 373 bool GetDirectoryEntries(IPortableDevice* device, |
| 374 const string16& directory_object_id, |
| 375 MTPDeviceObjectEntries* object_entries) { |
| 376 return GetMTPDeviceObjectEntries(device, directory_object_id, string16(), |
| 377 object_entries); |
| 378 } |
| 379 |
| 380 bool WriteFileObjectContentToPath(IPortableDevice* device, |
| 381 const string16& file_object_id, |
| 382 const FilePath& local_path) { |
| 383 base::ThreadRestrictions::AssertIOAllowed(); |
| 384 DCHECK(device); |
| 385 DCHECK(!file_object_id.empty()); |
| 386 DCHECK(!local_path.empty()); |
| 387 base::win::ScopedComPtr<IPortableDeviceContent> content = |
| 388 GetDeviceContent(device); |
| 389 if (!content) |
| 390 return false; |
| 391 |
| 392 base::win::ScopedComPtr<IPortableDeviceResources> resources; |
| 393 HRESULT hr = content->Transfer(resources.Receive()); |
| 394 if (FAILED(hr)) |
| 395 return false; |
| 396 |
| 397 base::win::ScopedComPtr<IStream> file_stream; |
| 398 DWORD optimal_transfer_size = 0; |
| 399 hr = resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT, |
| 400 STGM_READ, &optimal_transfer_size, |
| 401 file_stream.Receive()); |
| 402 if (FAILED(hr)) |
| 403 return false; |
| 404 return WriteStreamContentsToFile(file_stream.get(), optimal_transfer_size, |
| 405 local_path); |
| 406 } |
| 407 |
| 408 string16 GetObjectIdFromName(IPortableDevice* device, |
| 409 const string16& parent_id, |
| 410 const string16& object_name) { |
| 411 MTPDeviceObjectEntries object_entries; |
| 412 if (!GetMTPDeviceObjectEntries(device, parent_id, object_name, |
| 413 &object_entries) || |
| 414 object_entries.empty()) |
| 415 return string16(); |
| 416 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have |
| 417 // the same name. Handle the situation gracefully. Refer to crbug.com/169930 |
| 418 // for more details. |
| 419 DCHECK_EQ(1U, object_entries.size()); |
| 420 return object_entries[0].object_id; |
| 421 } |
| 422 |
| 423 } // namespace media_transfer_protocol |
| 424 |
| 425 } // namespace chrome |
| OLD | NEW |