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 "webkit/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 ImageCaptureDeviceListener,
37 public base::SupportsWeakPtr<DeviceListener> {
39 DeviceListener(MTPDeviceDelegateImplMac* delegate)
40 : delegate_(delegate) {}
41 virtual ~DeviceListener() {}
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 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;
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 [ImageCaptureDeviceManager::deviceForUUID(device_id) retain]);
73 [camera_device_ setListener:AsWeakPtr()];
74 [camera_device_ open];
77 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
78 [camera_device_ close];
79 [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
84 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
85 const std::string& name,
86 const base::FilePath& local_path) {
87 [camera_device_ downloadFile:name localPath:local_path];
90 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
91 const std::string& name,
92 const base::File::Info& info) {
94 delegate_->ItemAdded(name, info);
97 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
99 delegate_->NoMoreItems();
102 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
103 const std::string& name,
104 base::File::Error error) {
106 delegate_->DownloadedFile(name, error);
109 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
110 [camera_device_ close];
111 camera_device_.reset();
113 delegate_->NoMoreItems();
116 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
120 MTPDeviceDelegateImplMac::MTPDeviceDelegateImplMac(
121 const std::string& device_id,
122 const base::FilePath::StringType& synthetic_path)
123 : device_id_(device_id),
124 root_path_(synthetic_path),
125 received_all_files_(false),
126 weak_factory_(this) {
128 // Make a synthetic entry for the root of the filesystem.
129 base::File::Info info;
130 info.is_directory = true;
131 file_paths_.push_back(root_path_);
132 file_info_[root_path_.value()] = info;
134 camera_interface_.reset(new DeviceListener(this));
135 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
136 base::Bind(&DeviceListener::OpenCameraSession,
137 base::Unretained(camera_interface_.get()),
141 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
146 void ForwardGetFileInfo(
147 base::File::Info* info,
148 base::File::Error* error,
149 const GetFileInfoSuccessCallback& success_callback,
150 const ErrorCallback& error_callback) {
151 if (*error == base::File::FILE_OK)
152 success_callback.Run(*info);
154 error_callback.Run(*error);
159 void MTPDeviceDelegateImplMac::GetFileInfo(
160 const base::FilePath& file_path,
161 const GetFileInfoSuccessCallback& success_callback,
162 const ErrorCallback& error_callback) {
163 base::File::Info* info = new base::File::Info;
164 base::File::Error* error = new base::File::Error;
165 // Note: ownership of these objects passed into the reply callback.
166 content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI,
168 base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl,
169 base::Unretained(this), file_path, info, error),
170 base::Bind(&ForwardGetFileInfo,
171 base::Owned(info), base::Owned(error),
172 success_callback, error_callback));
175 void MTPDeviceDelegateImplMac::ReadDirectory(
176 const base::FilePath& root,
177 const ReadDirectorySuccessCallback& success_callback,
178 const ErrorCallback& error_callback) {
179 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
180 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
181 base::Unretained(this),
182 root, success_callback, error_callback));
185 void MTPDeviceDelegateImplMac::CreateSnapshotFile(
186 const base::FilePath& device_file_path,
187 const base::FilePath& local_path,
188 const CreateSnapshotFileSuccessCallback& success_callback,
189 const ErrorCallback& error_callback) {
190 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
191 base::Bind(&MTPDeviceDelegateImplMac::DownloadFile,
192 base::Unretained(this),
193 device_file_path, local_path,
194 success_callback, error_callback));
197 bool MTPDeviceDelegateImplMac::IsStreaming() {
201 void MTPDeviceDelegateImplMac::ReadBytes(
202 const base::FilePath& device_file_path,
203 net::IOBuffer* buf, int64 offset, int buf_len,
204 const ReadBytesSuccessCallback& success_callback,
205 const ErrorCallback& error_callback) {
209 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
210 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
211 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
212 base::Unretained(this)));
215 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
216 const base::FilePath& file_path,
217 base::File::Info* file_info,
218 base::File::Error* error) {
219 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
220 base::hash_map<base::FilePath::StringType,
221 base::File::Info>::const_iterator i =
222 file_info_.find(file_path.value());
223 if (i == file_info_.end()) {
224 *error = base::File::FILE_ERROR_NOT_FOUND;
227 *file_info = i->second;
228 *error = base::File::FILE_OK;
231 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
232 const base::FilePath& root,
233 const ReadDirectorySuccessCallback& success_callback,
234 const ErrorCallback& error_callback) {
235 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
237 read_dir_transactions_.push_back(ReadDirectoryRequest(
238 root, success_callback, error_callback));
240 if (received_all_files_) {
245 // Schedule a timeout in case the directory read doesn't complete.
246 content::BrowserThread::PostDelayedTask(
247 content::BrowserThread::UI, FROM_HERE,
248 base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
249 weak_factory_.GetWeakPtr(), root),
250 base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
253 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
254 const base::FilePath& root) {
255 if (received_all_files_)
258 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
259 iter != read_dir_transactions_.end();) {
260 if (iter->directory != root) {
264 iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
265 iter = read_dir_transactions_.erase(iter);
269 void MTPDeviceDelegateImplMac::DownloadFile(
270 const base::FilePath& device_file_path,
271 const base::FilePath& local_path,
272 const CreateSnapshotFileSuccessCallback& success_callback,
273 const ErrorCallback& error_callback) {
274 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
276 base::File::Error error;
277 base::File::Info info;
278 GetFileInfoImpl(device_file_path, &info, &error);
279 if (error != base::File::FILE_OK) {
280 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
281 base::Bind(error_callback,
286 base::FilePath relative_path;
287 root_path_.AppendRelativePath(device_file_path, &relative_path);
289 read_file_transactions_.push_back(
290 ReadFileRequest(relative_path.value(), local_path,
291 success_callback, error_callback));
293 camera_interface_->DownloadFile(relative_path.value(), local_path);
296 void MTPDeviceDelegateImplMac::CancelAndDelete() {
297 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
298 // Artificially pretend that we have already gotten all items we're going
304 // Schedule the camera session to be closed and the interface deleted.
305 // This will cancel any downloads in progress.
306 camera_interface_->ResetDelegate();
307 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
308 base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
309 base::Unretained(camera_interface_.release())));
314 void MTPDeviceDelegateImplMac::CancelDownloads() {
315 // Cancel any outstanding callbacks.
316 for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
317 iter != read_file_transactions_.end(); ++iter) {
318 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
319 base::Bind(iter->error_callback,
320 base::File::FILE_ERROR_ABORT));
322 read_file_transactions_.clear();
324 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
325 iter != read_dir_transactions_.end(); ++iter) {
326 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
327 base::Bind(iter->error_callback, base::File::FILE_ERROR_ABORT));
329 read_dir_transactions_.clear();
332 // Called on the UI thread by the listener
333 void MTPDeviceDelegateImplMac::ItemAdded(
334 const std::string& name, const base::File::Info& info) {
335 if (received_all_files_)
338 // This kinda should go in a Join method in FilePath...
339 base::FilePath relative_path(name);
340 std::vector<base::FilePath::StringType> components;
341 relative_path.GetComponents(&components);
342 base::FilePath item_filename = root_path_;
343 for (std::vector<base::FilePath::StringType>::iterator iter =
345 iter != components.end(); ++iter) {
346 item_filename = item_filename.Append(*iter);
349 file_info_[item_filename.value()] = info;
350 file_paths_.push_back(item_filename);
352 // TODO(gbillock): Should we send new files to
353 // read_dir_transactions_ callbacks?
356 // Called in the UI thread by delegate.
357 void MTPDeviceDelegateImplMac::NoMoreItems() {
358 received_all_files_ = true;
359 std::sort(file_paths_.begin(), file_paths_.end());
364 void MTPDeviceDelegateImplMac::NotifyReadDir() {
365 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
366 iter != read_dir_transactions_.end(); ++iter) {
367 base::FilePath read_path = iter->directory;
368 // This code assumes that the list of paths is sorted, so we skip to
369 // where we find the entry for the directory, then read out all first-level
370 // children. We then break when the DirName is greater than the read_path,
371 // as that means we've passed the subdir we're reading.
372 fileapi::AsyncFileUtil::EntryList entry_list;
373 bool found_path = false;
374 for (size_t i = 0; i < file_paths_.size(); ++i) {
375 if (file_paths_[i] == read_path) {
379 if (!read_path.IsParent(file_paths_[i])) {
380 if (read_path < file_paths_[i].DirName())
384 if (file_paths_[i].DirName() != read_path)
387 base::FilePath relative_path;
388 read_path.AppendRelativePath(file_paths_[i], &relative_path);
389 base::File::Info info = file_info_[file_paths_[i].value()];
390 fileapi::DirectoryEntry entry;
391 entry.name = relative_path.value();
392 entry.is_directory = info.is_directory;
393 entry.size = info.size;
394 entry.last_modified_time = info.last_modified;
395 entry_list.push_back(entry);
399 content::BrowserThread::PostTask(content::BrowserThread::IO,
401 base::Bind(iter->success_callback, entry_list, false));
403 content::BrowserThread::PostTask(content::BrowserThread::IO,
405 base::Bind(iter->error_callback,
406 base::File::FILE_ERROR_NOT_FOUND));
410 read_dir_transactions_.clear();
413 // Invoked on UI thread from the listener.
414 void MTPDeviceDelegateImplMac::DownloadedFile(
415 const std::string& name, base::File::Error error) {
416 // If we're cancelled and deleting, we may have deleted the camera.
417 if (!camera_interface_.get())
421 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
422 for (; iter != read_file_transactions_.end(); ++iter) {
423 if (iter->request_file == name) {
431 if (error != base::File::FILE_OK) {
432 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
433 base::Bind(iter->error_callback, error));
434 read_file_transactions_.erase(iter);
438 base::FilePath relative_path(name);
439 std::vector<base::FilePath::StringType> components;
440 relative_path.GetComponents(&components);
441 base::FilePath item_filename = root_path_;
442 for (std::vector<base::FilePath::StringType>::iterator i =
444 i != components.end(); ++i) {
445 item_filename = item_filename.Append(*i);
448 base::File::Info info = file_info_[item_filename.value()];
449 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
450 base::Bind(iter->success_callback, info, iter->snapshot_file));
451 read_file_transactions_.erase(iter);
454 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
455 const std::string& file,
456 const base::FilePath& snapshot_filename,
457 CreateSnapshotFileSuccessCallback success_cb,
458 ErrorCallback error_cb)
459 : request_file(file),
460 snapshot_file(snapshot_filename),
461 success_callback(success_cb),
462 error_callback(error_cb) {}
464 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
466 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
468 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
469 const base::FilePath& dir,
470 ReadDirectorySuccessCallback success_cb,
471 ErrorCallback error_cb)
473 success_callback(success_cb),
474 error_callback(error_cb) {}
476 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
478 void CreateMTPDeviceAsyncDelegate(
479 const base::FilePath::StringType& device_location,
480 const CreateMTPDeviceAsyncDelegateCallback& cb) {
481 std::string device_name = base::FilePath(device_location).BaseName().value();
482 std::string device_id;
483 StorageInfo::Type type;
484 bool cracked = StorageInfo::CrackDeviceId(device_name, &type, &device_id);
486 DCHECK_EQ(StorageInfo::MAC_IMAGE_CAPTURE, type);
488 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));