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.
5 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
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 "storage/browser/fileapi/async_file_util.h"
18 int kReadDirectoryTimeLimitSeconds = 20;
20 typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
21 CreateSnapshotFileSuccessCallback;
22 typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback;
23 typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback
24 GetFileInfoSuccessCallback;
25 typedef MTPDeviceAsyncDelegate::ReadDirectorySuccessCallback
26 ReadDirectorySuccessCallback;
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> {
39 DeviceListener(MTPDeviceDelegateImplMac* delegate)
40 : delegate_(delegate) {}
41 ~DeviceListener() override {}
43 void OpenCameraSession(const std::string& device_id);
44 void CloseCameraSessionAndDelete();
46 void DownloadFile(const std::string& name, const base::FilePath& local_path);
48 // ImageCaptureDeviceListener
49 void ItemAdded(const std::string& name,
50 const base::File::Info& info) override;
51 void NoMoreItems() override;
52 void DownloadedFile(const std::string& name,
53 base::File::Error error) override;
54 void DeviceRemoved() override;
56 // Used during delegate destruction to ensure there are no more calls
57 // to the delegate by the listener.
58 virtual void ResetDelegate();
61 base::scoped_nsobject<ImageCaptureDevice> camera_device_;
64 MTPDeviceDelegateImplMac* delegate_;
66 DISALLOW_COPY_AND_ASSIGN(DeviceListener);
69 void MTPDeviceDelegateImplMac::DeviceListener::OpenCameraSession(
70 const std::string& device_id) {
72 [storage_monitor::ImageCaptureDeviceManager::deviceForUUID(device_id)
74 [camera_device_ setListener:AsWeakPtr()];
75 [camera_device_ open];
78 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
79 [camera_device_ close];
80 [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
85 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
86 const std::string& name,
87 const base::FilePath& local_path) {
88 [camera_device_ downloadFile:name localPath:local_path];
91 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
92 const std::string& name,
93 const base::File::Info& info) {
95 delegate_->ItemAdded(name, info);
98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
100 delegate_->NoMoreItems();
103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
104 const std::string& name,
105 base::File::Error error) {
107 delegate_->DownloadedFile(name, error);
110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
111 [camera_device_ close];
112 camera_device_.reset();
114 delegate_->NoMoreItems();
117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
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) {
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;
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()),
142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
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);
155 error_callback.Run(*error);
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,
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));
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));
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));
198 bool MTPDeviceDelegateImplMac::IsStreaming() {
202 void MTPDeviceDelegateImplMac::ReadBytes(
203 const base::FilePath& device_file_path,
204 const scoped_refptr<net::IOBuffer>& buf,
207 const ReadBytesSuccessCallback& success_callback,
208 const ErrorCallback& error_callback) {
212 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
213 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
214 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
215 base::Unretained(this)));
218 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
219 const base::FilePath& file_path,
220 base::File::Info* file_info,
221 base::File::Error* error) {
222 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
223 base::hash_map<base::FilePath::StringType,
224 base::File::Info>::const_iterator i =
225 file_info_.find(file_path.value());
226 if (i == file_info_.end()) {
227 *error = base::File::FILE_ERROR_NOT_FOUND;
230 *file_info = i->second;
231 *error = base::File::FILE_OK;
234 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
235 const base::FilePath& root,
236 const ReadDirectorySuccessCallback& success_callback,
237 const ErrorCallback& error_callback) {
238 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
240 read_dir_transactions_.push_back(ReadDirectoryRequest(
241 root, success_callback, error_callback));
243 if (received_all_files_) {
248 // Schedule a timeout in case the directory read doesn't complete.
249 content::BrowserThread::PostDelayedTask(
250 content::BrowserThread::UI, FROM_HERE,
251 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
252 weak_factory_.GetWeakPtr(), root),
253 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
256 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
257 const base::FilePath& root) {
258 if (received_all_files_)
261 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
262 iter != read_dir_transactions_.end();) {
263 if (iter->directory != root) {
267 iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
268 iter = read_dir_transactions_.erase(iter);
272 void MTPDeviceDelegateImplMac::DownloadFile(
273 const base::FilePath& device_file_path,
274 const base::FilePath& local_path,
275 const CreateSnapshotFileSuccessCallback& success_callback,
276 const ErrorCallback& error_callback) {
277 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
279 base::File::Error error;
280 base::File::Info info;
281 GetFileInfoImpl(device_file_path, &info, &error);
282 if (error != base::File::FILE_OK) {
283 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
284 base::Bind(error_callback,
289 base::FilePath relative_path;
290 root_path_.AppendRelativePath(device_file_path, &relative_path);
292 read_file_transactions_.push_back(
293 ReadFileRequest(relative_path.value(), local_path,
294 success_callback, error_callback));
296 camera_interface_->DownloadFile(relative_path.value(), local_path);
299 void MTPDeviceDelegateImplMac::CancelAndDelete() {
300 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
301 // Artificially pretend that we have already gotten all items we're going
307 // Schedule the camera session to be closed and the interface deleted.
308 // This will cancel any downloads in progress.
309 camera_interface_->ResetDelegate();
310 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
311 base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
312 base::Unretained(camera_interface_.release())));
317 void MTPDeviceDelegateImplMac::CancelDownloads() {
318 // Cancel any outstanding callbacks.
319 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
320 iter != read_file_transactions_.end(); ++iter) {
321 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
322 base::Bind(iter->error_callback,
323 base::File::FILE_ERROR_ABORT));
325 read_file_transactions_.clear();
327 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
328 iter != read_dir_transactions_.end(); ++iter) {
329 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
330 base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT));
332 read_dir_transactions_.clear();
335 // Called on the UI thread by the listener
336 void MTPDeviceDelegateImplMac::ItemAdded(
337 const std::string& name, const base::File::Info& info) {
338 if (received_all_files_)
341 // This kinda should go in a Join method in FilePath...
342 base::FilePath relative_path(name);
343 std::vector<base::FilePath::StringType> components;
344 relative_path.GetComponents(&components);
345 base::FilePath item_filename = root_path_;
346 for (std::vector<base::FilePath::StringType>::iterator iter =
348 iter != components.end(); ++iter) {
349 item_filename = item_filename.Append(*iter);
352 file_info_[item_filename.value()] = info;
353 file_paths_.push_back(item_filename);
355 // TODO(gbillock): Should we send new files to
356 // read_dir_transactions_ callbacks?
359 // Called in the UI thread by delegate.
360 void MTPDeviceDelegateImplMac::NoMoreItems() {
361 received_all_files_ = true;
362 std::sort(file_paths_.begin(), file_paths_.end());
367 void MTPDeviceDelegateImplMac::NotifyReadDir() {
368 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
369 iter != read_dir_transactions_.end(); ++iter) {
370 base::FilePath read_path = iter->directory;
371 // This code assumes that the list of paths is sorted, so we skip to
372 // where we find the entry for the directory, then read out all first-level
373 // children. We then break when the DirName is greater than the read_path,
374 // as that means we've passed the subdir we're reading.
375 storage::AsyncFileUtil::EntryList entry_list;
376 bool found_path = false;
377 for (size_t i = 0; i < file_paths_.size(); ++i) {
378 if (file_paths_[i] == read_path) {
382 if (!read_path.IsParent(file_paths_[i])) {
383 if (read_path < file_paths_[i].DirName())
387 if (file_paths_[i].DirName() != read_path)
390 base::FilePath relative_path;
391 read_path.AppendRelativePath(file_paths_[i], &relative_path);
392 base::File::Info info = file_info_[file_paths_[i].value()];
393 storage::DirectoryEntry entry;
394 entry.name = relative_path.value();
395 entry.is_directory = info.is_directory;
396 entry.size = info.size;
397 entry.last_modified_time = info.last_modified;
398 entry_list.push_back(entry);
402 content::BrowserThread::PostTask(content::BrowserThread::IO,
404 base::Bind(iter->success_callback, entry_list, false));
406 content::BrowserThread::PostTask(content::BrowserThread::IO,
408 base::Bind(iter->error_callback,
409 base::File::FILE_ERROR_NOT_FOUND));
413 read_dir_transactions_.clear();
416 // Invoked on UI thread from the listener.
417 void MTPDeviceDelegateImplMac::DownloadedFile(
418 const std::string& name, base::File::Error error) {
419 // If we're cancelled and deleting, we may have deleted the camera.
420 if (!camera_interface_.get())
424 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
425 for (; iter != read_file_transactions_.end(); ++iter) {
426 if (iter->request_file == name) {
434 if (error != base::File::FILE_OK) {
435 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
436 base::Bind(iter->error_callback, error));
437 read_file_transactions_.erase(iter);
441 base::FilePath relative_path(name);
442 std::vector<base::FilePath::StringType> components;
443 relative_path.GetComponents(&components);
444 base::FilePath item_filename = root_path_;
445 for (std::vector<base::FilePath::StringType>::iterator i =
447 i != components.end(); ++i) {
448 item_filename = item_filename.Append(*i);
451 base::File::Info info = file_info_[item_filename.value()];
452 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
453 base::Bind(iter->success_callback, info, iter->snapshot_file));
454 read_file_transactions_.erase(iter);
457 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
458 const std::string& file,
459 const base::FilePath& snapshot_filename,
460 CreateSnapshotFileSuccessCallback success_cb,
461 ErrorCallback error_cb)
462 : request_file(file),
463 snapshot_file(snapshot_filename),
464 success_callback(success_cb),
465 error_callback(error_cb) {}
467 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
469 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
471 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
472 const base::FilePath& dir,
473 ReadDirectorySuccessCallback success_cb,
474 ErrorCallback error_cb)
476 success_callback(success_cb),
477 error_callback(error_cb) {}
479 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
481 void CreateMTPDeviceAsyncDelegate(
482 const base::FilePath::StringType& device_location,
483 const CreateMTPDeviceAsyncDelegateCallback& cb) {
484 std::string device_name = base::FilePath(device_location).BaseName().value();
485 std::string device_id;
486 storage_monitor::StorageInfo::Type type;
487 bool cracked = storage_monitor::StorageInfo::CrackDeviceId(
488 device_name, &type, &device_id);
490 DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
492 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));