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 storage_monitor::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 [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 net::IOBuffer* buf, int64 offset, int buf_len,
205 const ReadBytesSuccessCallback& success_callback,
206 const ErrorCallback& error_callback) {
210 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
211 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
212 base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
213 base::Unretained(this)));
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;
228 *file_info = i->second;
229 *error = base::File::FILE_OK;
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);
238 read_dir_transactions_.push_back(ReadDirectoryRequest(
239 root, success_callback, error_callback));
241 if (received_all_files_) {
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));
254 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
255 const base::FilePath& root) {
256 if (received_all_files_)
259 for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
260 iter != read_dir_transactions_.end();) {
261 if (iter->directory != root) {
265 iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
266 iter = read_dir_transactions_.erase(iter);
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);
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,
287 base::FilePath relative_path;
288 root_path_.AppendRelativePath(device_file_path, &relative_path);
290 read_file_transactions_.push_back(
291 ReadFileRequest(relative_path.value(), local_path,
292 success_callback, error_callback));
294 camera_interface_->DownloadFile(relative_path.value(), local_path);
297 void MTPDeviceDelegateImplMac::CancelAndDelete() {
298 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
299 // Artificially pretend that we have already gotten all items we're going
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())));
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));
323 read_file_transactions_.clear();
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));
330 read_dir_transactions_.clear();
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_)
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 =
346 iter != components.end(); ++iter) {
347 item_filename = item_filename.Append(*iter);
350 file_info_[item_filename.value()] = info;
351 file_paths_.push_back(item_filename);
353 // TODO(gbillock): Should we send new files to
354 // read_dir_transactions_ callbacks?
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());
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 storage::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) {
380 if (!read_path.IsParent(file_paths_[i])) {
381 if (read_path < file_paths_[i].DirName())
385 if (file_paths_[i].DirName() != read_path)
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 storage::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);
400 content::BrowserThread::PostTask(content::BrowserThread::IO,
402 base::Bind(iter->success_callback, entry_list, false));
404 content::BrowserThread::PostTask(content::BrowserThread::IO,
406 base::Bind(iter->error_callback,
407 base::File::FILE_ERROR_NOT_FOUND));
411 read_dir_transactions_.clear();
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())
422 ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
423 for (; iter != read_file_transactions_.end(); ++iter) {
424 if (iter->request_file == name) {
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);
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 =
445 i != components.end(); ++i) {
446 item_filename = item_filename.Append(*i);
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);
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) {}
465 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
467 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
469 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
470 const base::FilePath& dir,
471 ReadDirectorySuccessCallback success_cb,
472 ErrorCallback error_cb)
474 success_callback(success_cb),
475 error_callback(error_cb) {}
477 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
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);
488 DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
490 cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));