app_shell: Add version number in user agent
[chromium-blink-merge.git] / chrome / browser / media_galleries / mac / mtp_device_delegate_impl_mac.mm
blob567d1c125a729ced7ad8ed09d422b951e2ad1a60
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"
7 #include <algorithm>
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"
16 namespace {
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;
28 }  // namespace
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() {}
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();
60  private:
61   base::scoped_nsobject<ImageCaptureDevice> camera_device_;
63   // Weak pointer
64   MTPDeviceDelegateImplMac* delegate_;
66   DISALLOW_COPY_AND_ASSIGN(DeviceListener);
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];
78 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
79   [camera_device_ close];
80   [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
82   delete this;
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) {
94   if (delegate_)
95     delegate_->ItemAdded(name, info);
98 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
99   if (delegate_)
100     delegate_->NoMoreItems();
103 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
104     const std::string& name,
105     base::File::Error error) {
106   if (delegate_)
107     delegate_->DownloadedFile(name, error);
110 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
111   [camera_device_ close];
112   camera_device_.reset();
113   if (delegate_)
114     delegate_->NoMoreItems();
117 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
118   delegate_ = NULL;
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()),
139                  device_id_));
142 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
145 namespace {
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);
158 }  // namespace
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));
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() {
199   return false;
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();
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;
226     return;
227   }
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_) {
242     NotifyReadDir();
243     return;
244   }
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_)
257     return;
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   }
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,
283                                                 error));
284     return;
285   }
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
300   // to get.
301   NoMoreItems();
303   CancelDownloads();
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())));
312   delete this;
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();
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();
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;
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   }
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());
362   NotifyReadDir();
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) {
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;
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);
397     }
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   }
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())
419     return;
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;
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   }
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   }
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)
473     : directory(dir),
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);
487   DCHECK(cracked);
488   DCHECK_EQ(storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, type);
490   cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));