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_galleries/mac/mtp_device_delegate_impl_mac.h" 6 7 #include <algorithm> 8 9 #include "base/mac/scoped_nsobject.h" 10 #include "base/threading/sequenced_worker_pool.h" 11 #include "components/storage_monitor/image_capture_device.h" 12 #include "components/storage_monitor/image_capture_device_manager.h" 13 #include "content/public/browser/browser_thread.h" 14 #include "webkit/browser/fileapi/async_file_util.h" 15 16 namespace { 17 18 int kReadDirectoryTimeLimitSeconds = 20; 19 20 typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback 21 CreateSnapshotFileSuccessCallback; 22 typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback; 23 typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback 24 GetFileInfoSuccessCallback; 25 typedef MTPDeviceAsyncDelegate::ReadDirectorySuccessCallback 26 ReadDirectorySuccessCallback; 27 28 } // namespace 29 30 // This class handles the UI-thread hand-offs needed to interface 31 // with the ImageCapture library. It will forward callbacks to 32 // its delegate on the task runner with which it is created. All 33 // interactions with it are done on the UI thread, but it may be 34 // created/destroyed on another thread. 35 class MTPDeviceDelegateImplMac::DeviceListener 36 : public storage_monitor::ImageCaptureDeviceListener, 37 public base::SupportsWeakPtr<DeviceListener> { 38 public: 39 DeviceListener(MTPDeviceDelegateImplMac* delegate) 40 : delegate_(delegate) {} 41 virtual ~DeviceListener() {} 42 43 void OpenCameraSession(const std::string& device_id); 44 void CloseCameraSessionAndDelete(); 45 46 void DownloadFile(const std::string& name, const base::FilePath& local_path); 47 48 // ImageCaptureDeviceListener 49 virtual void ItemAdded(const std::string& name, 50 const base::File::Info& info) OVERRIDE; 51 virtual void NoMoreItems() OVERRIDE; 52 virtual void DownloadedFile(const std::string& name, 53 base::File::Error error) OVERRIDE; 54 virtual void DeviceRemoved() OVERRIDE; 55 56 // Used during delegate destruction to ensure there are no more calls 57 // to the delegate by the listener. 58 virtual void ResetDelegate(); 59 60 private: 61 base::scoped_nsobject<ImageCaptureDevice> camera_device_; 62 63 // Weak pointer 64 MTPDeviceDelegateImplMac* delegate_; 65 66 DISALLOW_COPY_AND_ASSIGN(DeviceListener); 67 }; 68 69 void MTPDeviceDelegateImplMac::DeviceListener::OpenCameraSession( 70 const std::string& device_id) { 71 camera_device_.reset( 72 [storage_monitor::ImageCaptureDeviceManager::deviceForUUID(device_id) 73 retain]); 74 [camera_device_ setListener:AsWeakPtr()]; 75 [camera_device_ open]; 76 } 77 78 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() { 79 [camera_device_ close]; 80 [camera_device_ setListener:base::WeakPtr<DeviceListener>()]; 81 82 delete this; 83 } 84 85 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile( 86 const std::string& name, 87 const base::FilePath& local_path) { 88 [camera_device_ downloadFile:name localPath:local_path]; 89 } 90 91 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded( 92 const std::string& name, 93 const base::File::Info& info) { 94 if (delegate_) 95 delegate_->ItemAdded(name, info); 96 } 97 98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() { 99 if (delegate_) 100 delegate_->NoMoreItems(); 101 } 102 103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile( 104 const std::string& name, 105 base::File::Error error) { 106 if (delegate_) 107 delegate_->DownloadedFile(name, error); 108 } 109 110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() { 111 [camera_device_ close]; 112 camera_device_.reset(); 113 if (delegate_) 114 delegate_->NoMoreItems(); 115 } 116 117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() { 118 delegate_ = NULL; 119 } 120 121 MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac( 122 const std::string& device_id, 123 const base::FilePath::StringType& synthetic_path) 124 : device_id_(device_id), 125 root_path_(synthetic_path), 126 received_all_files_(false), 127 weak_factory_(this) { 128 129 // Make a synthetic entry for the root of the filesystem. 130 base::File::Info info; 131 info.is_directory = true; 132 file_paths_.push_back(root_path_); 133 file_info_[root_path_.value()] = info; 134 135 camera_interface_.reset(new DeviceListener(this)); 136 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 137 base::Bind(&DeviceListener::OpenCameraSession, 138 base::Unretained(camera_interface_.get()), 139 device_id_)); 140 } 141 142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() { 143 } 144 145 namespace { 146 147 void ForwardGetFileInfo( 148 base::File::Info* info, 149 base::File::Error* error, 150 const GetFileInfoSuccessCallback& success_callback, 151 const ErrorCallback& error_callback) { 152 if (*error == base::File::FILE_OK) 153 success_callback.Run(*info); 154 else 155 error_callback.Run(*error); 156 } 157 158 } // namespace 159 160 void MTPDeviceDelegateImplMac::GetFileInfo( 161 const base::FilePath& file_path, 162 const GetFileInfoSuccessCallback& success_callback, 163 const ErrorCallback& error_callback) { 164 base::File::Info* info = new base::File::Info; 165 base::File::Error* error = new base::File::Error; 166 // Note: ownership of these objects passed into the reply callback. 167 content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI, 168 FROM_HERE, 169 base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl, 170 base::Unretained(this), file_path, info, error), 171 base::Bind(&ForwardGetFileInfo, 172 base::Owned(info), base::Owned(error), 173 success_callback, error_callback)); 174 } 175 176 void MTPDeviceDelegateImplMac::ReadDirectory( 177 const base::FilePath& root, 178 const ReadDirectorySuccessCallback& success_callback, 179 const ErrorCallback& error_callback) { 180 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 181 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl, 182 base::Unretained(this), 183 root, success_callback, error_callback)); 184 } 185 186 void MTPDeviceDelegateImplMac::CreateSnapshotFile( 187 const base::FilePath& device_file_path, 188 const base::FilePath& local_path, 189 const CreateSnapshotFileSuccessCallback& success_callback, 190 const ErrorCallback& error_callback) { 191 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 192 base::Bind(&MTPDeviceDelegateImplMac::DownloadFile, 193 base::Unretained(this), 194 device_file_path, local_path, 195 success_callback, error_callback)); 196 } 197 198 bool MTPDeviceDelegateImplMac::IsStreaming() { 199 return false; 200 } 201 202 void MTPDeviceDelegateImplMac::ReadBytes( 203 const base::FilePath& device_file_path, 204 net::IOBuffer* buf, int64 offset, int buf_len, 205 const ReadBytesSuccessCallback& success_callback, 206 const ErrorCallback& error_callback) { 207 NOTREACHED(); 208 } 209 210 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() { 211 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 212 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete, 213 base::Unretained(this))); 214 } 215 216 void MTPDeviceDelegateImplMac::GetFileInfoImpl( 217 const base::FilePath& file_path, 218 base::File::Info* file_info, 219 base::File::Error* error) { 220 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 221 base::hash_map<base::FilePath::StringType, 222 base::File::Info>::const_iterator i = 223 file_info_.find(file_path.value()); 224 if (i == file_info_.end()) { 225 *error = base::File::FILE_ERROR_NOT_FOUND; 226 return; 227 } 228 *file_info = i->second; 229 *error = base::File::FILE_OK; 230 } 231 232 void MTPDeviceDelegateImplMac::ReadDirectoryImpl( 233 const base::FilePath& root, 234 const ReadDirectorySuccessCallback& success_callback, 235 const ErrorCallback& error_callback) { 236 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 237 238 read_dir_transactions_.push_back(ReadDirectoryRequest( 239 root, success_callback, error_callback)); 240 241 if (received_all_files_) { 242 NotifyReadDir(); 243 return; 244 } 245 246 // Schedule a timeout in case the directory read doesn't complete. 247 content::BrowserThread::PostDelayedTask( 248 content::BrowserThread::UI, FROM_HERE, 249 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout, 250 weak_factory_.GetWeakPtr(), root), 251 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds)); 252 } 253 254 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout( 255 const base::FilePath& root) { 256 if (received_all_files_) 257 return; 258 259 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin(); 260 iter != read_dir_transactions_.end();) { 261 if (iter->directory != root) { 262 ++iter; 263 continue; 264 } 265 iter->error_callback.Run(base::File::FILE_ERROR_ABORT); 266 iter = read_dir_transactions_.erase(iter); 267 } 268 } 269 270 void MTPDeviceDelegateImplMac::DownloadFile( 271 const base::FilePath& device_file_path, 272 const base::FilePath& local_path, 273 const CreateSnapshotFileSuccessCallback& success_callback, 274 const ErrorCallback& error_callback) { 275 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 276 277 base::File::Error error; 278 base::File::Info info; 279 GetFileInfoImpl(device_file_path, &info, &error); 280 if (error != base::File::FILE_OK) { 281 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 282 base::Bind(error_callback, 283 error)); 284 return; 285 } 286 287 base::FilePath relative_path; 288 root_path_.AppendRelativePath(device_file_path, &relative_path); 289 290 read_file_transactions_.push_back( 291 ReadFileRequest(relative_path.value(), local_path, 292 success_callback, error_callback)); 293 294 camera_interface_->DownloadFile(relative_path.value(), local_path); 295 } 296 297 void MTPDeviceDelegateImplMac::CancelAndDelete() { 298 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 299 // Artificially pretend that we have already gotten all items we're going 300 // to get. 301 NoMoreItems(); 302 303 CancelDownloads(); 304 305 // Schedule the camera session to be closed and the interface deleted. 306 // This will cancel any downloads in progress. 307 camera_interface_->ResetDelegate(); 308 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 309 base::Bind(&DeviceListener::CloseCameraSessionAndDelete, 310 base::Unretained(camera_interface_.release()))); 311 312 delete this; 313 } 314 315 void MTPDeviceDelegateImplMac::CancelDownloads() { 316 // Cancel any outstanding callbacks. 317 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin(); 318 iter != read_file_transactions_.end(); ++iter) { 319 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 320 base::Bind(iter->error_callback, 321 base::File::FILE_ERROR_ABORT)); 322 } 323 read_file_transactions_.clear(); 324 325 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin(); 326 iter != read_dir_transactions_.end(); ++iter) { 327 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 328 base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT)); 329 } 330 read_dir_transactions_.clear(); 331 } 332 333 // Called on the UI thread by the listener 334 void MTPDeviceDelegateImplMac::ItemAdded( 335 const std::string& name, const base::File::Info& info) { 336 if (received_all_files_) 337 return; 338 339 // This kinda should go in a Join method in FilePath... 340 base::FilePath relative_path(name); 341 std::vector<base::FilePath::StringType> components; 342 relative_path.GetComponents(&components); 343 base::FilePath item_filename = root_path_; 344 for (std::vector<base::FilePath::StringType>::iterator iter = 345 components.begin(); 346 iter != components.end(); ++iter) { 347 item_filename = item_filename.Append(*iter); 348 } 349 350 file_info_[item_filename.value()] = info; 351 file_paths_.push_back(item_filename); 352 353 // TODO(gbillock): Should we send new files to 354 // read_dir_transactions_ callbacks? 355 } 356 357 // Called in the UI thread by delegate. 358 void MTPDeviceDelegateImplMac::NoMoreItems() { 359 received_all_files_ = true; 360 std::sort(file_paths_.begin(), file_paths_.end()); 361 362 NotifyReadDir(); 363 } 364 365 void MTPDeviceDelegateImplMac::NotifyReadDir() { 366 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin(); 367 iter != read_dir_transactions_.end(); ++iter) { 368 base::FilePath read_path = iter->directory; 369 // This code assumes that the list of paths is sorted, so we skip to 370 // where we find the entry for the directory, then read out all first-level 371 // children. We then break when the DirName is greater than the read_path, 372 // as that means we've passed the subdir we're reading. 373 fileapi::AsyncFileUtil::EntryList entry_list; 374 bool found_path = false; 375 for (size_t i = 0; i < file_paths_.size(); ++i) { 376 if (file_paths_[i] == read_path) { 377 found_path = true; 378 continue; 379 } 380 if (!read_path.IsParent(file_paths_[i])) { 381 if (read_path < file_paths_[i].DirName()) 382 break; 383 continue; 384 } 385 if (file_paths_[i].DirName() != read_path) 386 continue; 387 388 base::FilePath relative_path; 389 read_path.AppendRelativePath(file_paths_[i], &relative_path); 390 base::File::Info info = file_info_[file_paths_[i].value()]; 391 fileapi::DirectoryEntry entry; 392 entry.name = relative_path.value(); 393 entry.is_directory = info.is_directory; 394 entry.size = info.size; 395 entry.last_modified_time = info.last_modified; 396 entry_list.push_back(entry); 397 } 398 399 if (found_path) { 400 content::BrowserThread::PostTask(content::BrowserThread::IO, 401 FROM_HERE, 402 base::Bind(iter->success_callback, entry_list, false)); 403 } else { 404 content::BrowserThread::PostTask(content::BrowserThread::IO, 405 FROM_HERE, 406 base::Bind(iter->error_callback, 407 base::File::FILE_ERROR_NOT_FOUND)); 408 } 409 } 410 411 read_dir_transactions_.clear(); 412 } 413 414 // Invoked on UI thread from the listener. 415 void MTPDeviceDelegateImplMac::DownloadedFile( 416 const std::string& name, base::File::Error error) { 417 // If we're cancelled and deleting, we may have deleted the camera. 418 if (!camera_interface_.get()) 419 return; 420 421 bool found = false; 422 ReadFileTransactionList::iterator iter = read_file_transactions_.begin(); 423 for (; iter != read_file_transactions_.end(); ++iter) { 424 if (iter->request_file == name) { 425 found = true; 426 break; 427 } 428 } 429 if (!found) 430 return; 431 432 if (error != base::File::FILE_OK) { 433 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 434 base::Bind(iter->error_callback, error)); 435 read_file_transactions_.erase(iter); 436 return; 437 } 438 439 base::FilePath relative_path(name); 440 std::vector<base::FilePath::StringType> components; 441 relative_path.GetComponents(&components); 442 base::FilePath item_filename = root_path_; 443 for (std::vector<base::FilePath::StringType>::iterator i = 444 components.begin(); 445 i != components.end(); ++i) { 446 item_filename = item_filename.Append(*i); 447 } 448 449 base::File::Info info = file_info_[item_filename.value()]; 450 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 451 base::Bind(iter->success_callback, info, iter->snapshot_file)); 452 read_file_transactions_.erase(iter); 453 } 454 455 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest( 456 const std::string& file, 457 const base::FilePath& snapshot_filename, 458 CreateSnapshotFileSuccessCallback success_cb, 459 ErrorCallback error_cb) 460 : request_file(file), 461 snapshot_file(snapshot_filename), 462 success_callback(success_cb), 463 error_callback(error_cb) {} 464 465 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {} 466 467 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {} 468 469 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest( 470 const base::FilePath& dir, 471 ReadDirectorySuccessCallback success_cb, 472 ErrorCallback error_cb) 473 : directory(dir), 474 success_callback(success_cb), 475 error_callback(error_cb) {} 476 477 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {} 478 479 void CreateMTPDeviceAsyncDelegate( 480 const base::FilePath::StringType& device_location, 481 const CreateMTPDeviceAsyncDelegateCallback& cb) { 482 std::string device_name = base::FilePath(device_location).BaseName().value(); 483 std::string device_id; 484 storage_monitor::StorageInfo::Type type; 485 bool cracked = storage_monitor::StorageInfo::CrackDeviceId( 486 device_name, &type, &device_id); 487 DCHECK(cracked); 488 DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type); 489 490 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location)); 491 } 492