- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / media_galleries / mac / mtp_device_delegate_impl_mac.mm
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 "chrome/browser/storage_monitor/image_capture_device.h"
12 #include "chrome/browser/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 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::PlatformFileInfo& info) OVERRIDE;
51   virtual void NoMoreItems() OVERRIDE;
52   virtual void DownloadedFile(const std::string& name,
53                               base::PlatformFileError 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       [ImageCaptureDeviceManager::deviceForUUID(device_id) retain]);
73   [camera_device_ setListener:AsWeakPtr()];
74   [camera_device_ open];
75 }
76
77 void MTPDeviceDelegateImplMac::DeviceListener::CloseCameraSessionAndDelete() {
78   [camera_device_ close];
79   [camera_device_ setListener:base::WeakPtr<DeviceListener>()];
80
81   delete this;
82 }
83
84 void MTPDeviceDelegateImplMac::DeviceListener::DownloadFile(
85     const std::string& name,
86     const base::FilePath& local_path) {
87   [camera_device_ downloadFile:name localPath:local_path];
88 }
89
90 void MTPDeviceDelegateImplMac::DeviceListener::ItemAdded(
91     const std::string& name,
92     const base::PlatformFileInfo& info) {
93   if (delegate_)
94     delegate_->ItemAdded(name, info);
95 }
96
97 void MTPDeviceDelegateImplMac::DeviceListener::NoMoreItems() {
98   if (delegate_)
99     delegate_->NoMoreItems();
100 }
101
102 void MTPDeviceDelegateImplMac::DeviceListener::DownloadedFile(
103     const std::string& name,
104     base::PlatformFileError error) {
105   if (delegate_)
106     delegate_->DownloadedFile(name, error);
107 }
108
109 void MTPDeviceDelegateImplMac::DeviceListener::DeviceRemoved() {
110   [camera_device_ close];
111   camera_device_.reset();
112   if (delegate_)
113     delegate_->NoMoreItems();
114 }
115
116 void MTPDeviceDelegateImplMac::DeviceListener::ResetDelegate() {
117   delegate_ = NULL;
118 }
119
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) {
127
128   // Make a synthetic entry for the root of the filesystem.
129   base::PlatformFileInfo info;
130   info.is_directory = true;
131   file_paths_.push_back(root_path_);
132   file_info_[root_path_.value()] = info;
133
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()),
138                  device_id_));
139 }
140
141 MTPDeviceDelegateImplMac::~MTPDeviceDelegateImplMac() {
142 }
143
144 namespace {
145
146 void ForwardGetFileInfo(
147     base::PlatformFileInfo* info,
148     base::PlatformFileError* error,
149     const GetFileInfoSuccessCallback& success_callback,
150     const ErrorCallback& error_callback) {
151   if (*error == base::PLATFORM_FILE_OK)
152     success_callback.Run(*info);
153   else
154     error_callback.Run(*error);
155 }
156
157 }  // namespace
158
159 void MTPDeviceDelegateImplMac::GetFileInfo(
160     const base::FilePath& file_path,
161     const GetFileInfoSuccessCallback& success_callback,
162     const ErrorCallback& error_callback) {
163   base::PlatformFileInfo* info = new base::PlatformFileInfo;
164   base::PlatformFileError* error = new base::PlatformFileError;
165   // Note: ownership of these objects passed into the reply callback.
166   content::BrowserThread::PostTaskAndReply(content::BrowserThread::UI,
167       FROM_HERE,
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));
173 }
174
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));
183 }
184
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));
195 }
196
197 void MTPDeviceDelegateImplMac::CancelPendingTasksAndDeleteDelegate() {
198   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
199       base::Bind(&MTPDeviceDelegateImplMac::CancelAndDelete,
200                  base::Unretained(this)));
201 }
202
203 void MTPDeviceDelegateImplMac::GetFileInfoImpl(
204     const base::FilePath& file_path,
205     base::PlatformFileInfo* file_info,
206     base::PlatformFileError* error) {
207   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
208   base::hash_map<base::FilePath::StringType,
209                  base::PlatformFileInfo>::const_iterator i =
210       file_info_.find(file_path.value());
211   if (i == file_info_.end()) {
212     *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
213     return;
214   }
215   *file_info = i->second;
216   *error = base::PLATFORM_FILE_OK;
217 }
218
219 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
220       const base::FilePath& root,
221       const ReadDirectorySuccessCallback& success_callback,
222       const ErrorCallback& error_callback) {
223   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
224
225   read_dir_transactions_.push_back(ReadDirectoryRequest(
226       root, success_callback, error_callback));
227
228   if (received_all_files_) {
229     NotifyReadDir();
230     return;
231   }
232
233   // Schedule a timeout in case the directory read doesn't complete.
234   content::BrowserThread::PostDelayedTask(
235       content::BrowserThread::UI, FROM_HERE,
236       base::Bind(&MTPDeviceDelegateImplMac::ReadDirectoryTimeout,
237                  weak_factory_.GetWeakPtr(), root),
238       base::TimeDelta::FromSeconds(kReadDirectoryTimeLimitSeconds));
239 }
240
241 void MTPDeviceDelegateImplMac::ReadDirectoryTimeout(
242     const base::FilePath& root) {
243   if (received_all_files_)
244     return;
245
246   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
247        iter != read_dir_transactions_.end();) {
248     if (iter->directory != root) {
249       iter++;
250       continue;
251     }
252     iter->error_callback.Run(base::PLATFORM_FILE_ERROR_ABORT);
253     iter = read_dir_transactions_.erase(iter);
254   }
255 }
256
257 void MTPDeviceDelegateImplMac::DownloadFile(
258       const base::FilePath& device_file_path,
259       const base::FilePath& local_path,
260       const CreateSnapshotFileSuccessCallback& success_callback,
261       const ErrorCallback& error_callback) {
262   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
263
264   base::PlatformFileError error;
265   base::PlatformFileInfo info;
266   GetFileInfoImpl(device_file_path, &info, &error);
267   if (error != base::PLATFORM_FILE_OK) {
268     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
269                                      base::Bind(error_callback,
270                                                 error));
271     return;
272   }
273
274   base::FilePath relative_path;
275   root_path_.AppendRelativePath(device_file_path, &relative_path);
276
277   read_file_transactions_.push_back(
278       ReadFileRequest(relative_path.value(), local_path,
279                       success_callback, error_callback));
280
281   camera_interface_->DownloadFile(relative_path.value(), local_path);
282 }
283
284 void MTPDeviceDelegateImplMac::CancelAndDelete() {
285   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
286   // Artificially pretend that we have already gotten all items we're going
287   // to get.
288   NoMoreItems();
289
290   CancelDownloads();
291
292   // Schedule the camera session to be closed and the interface deleted.
293   // This will cancel any downloads in progress.
294   camera_interface_->ResetDelegate();
295   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
296       base::Bind(&DeviceListener::CloseCameraSessionAndDelete,
297                  base::Unretained(camera_interface_.release())));
298
299   delete this;
300 }
301
302 void MTPDeviceDelegateImplMac::CancelDownloads() {
303   // Cancel any outstanding callbacks.
304   for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
305        iter != read_file_transactions_.end(); ++iter) {
306     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
307         base::Bind(iter->error_callback,
308                    base::PLATFORM_FILE_ERROR_ABORT));
309   }
310   read_file_transactions_.clear();
311
312   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
313        iter != read_dir_transactions_.end(); ++iter) {
314     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
315         base::Bind(iter->error_callback, base::PLATFORM_FILE_ERROR_ABORT));
316   }
317   read_dir_transactions_.clear();
318 }
319
320 // Called on the UI thread by the listener
321 void MTPDeviceDelegateImplMac::ItemAdded(
322     const std::string& name, const base::PlatformFileInfo& info) {
323   if (received_all_files_)
324     return;
325
326   // This kinda should go in a Join method in FilePath...
327   base::FilePath relative_path(name);
328   std::vector<base::FilePath::StringType> components;
329   relative_path.GetComponents(&components);
330   base::FilePath item_filename = root_path_;
331   for (std::vector<base::FilePath::StringType>::iterator iter =
332            components.begin();
333        iter != components.end(); ++iter) {
334     item_filename = item_filename.Append(*iter);
335   }
336
337   file_info_[item_filename.value()] = info;
338   file_paths_.push_back(item_filename);
339
340   // TODO(gbillock): Should we send new files to
341   // read_dir_transactions_ callbacks?
342 }
343
344 // Called in the UI thread by delegate.
345 void MTPDeviceDelegateImplMac::NoMoreItems() {
346   received_all_files_ = true;
347   std::sort(file_paths_.begin(), file_paths_.end());
348
349   NotifyReadDir();
350 }
351
352 void MTPDeviceDelegateImplMac::NotifyReadDir() {
353   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
354        iter != read_dir_transactions_.end(); ++iter) {
355     base::FilePath read_path = iter->directory;
356     // This code assumes that the list of paths is sorted, so we skip to
357     // where we find the entry for the directory, then read out all first-level
358     // children. We then break when the DirName is greater than the read_path,
359     // as that means we've passed the subdir we're reading.
360     fileapi::AsyncFileUtil::EntryList entry_list;
361     bool found_path = false;
362     for (size_t i = 0; i < file_paths_.size(); ++i) {
363       if (file_paths_[i] == read_path) {
364         found_path = true;
365         continue;
366       }
367       if (!read_path.IsParent(file_paths_[i])) {
368         if (read_path < file_paths_[i].DirName())
369           break;
370         continue;
371       }
372       if (file_paths_[i].DirName() != read_path)
373         continue;
374
375       base::FilePath relative_path;
376       read_path.AppendRelativePath(file_paths_[i], &relative_path);
377       base::PlatformFileInfo info = file_info_[file_paths_[i].value()];
378       fileapi::DirectoryEntry entry;
379       entry.name = relative_path.value();
380       entry.is_directory = info.is_directory;
381       entry.size = info.size;
382       entry.last_modified_time = info.last_modified;
383       entry_list.push_back(entry);
384     }
385
386     if (found_path) {
387       content::BrowserThread::PostTask(content::BrowserThread::IO,
388           FROM_HERE,
389           base::Bind(iter->success_callback, entry_list, false));
390     } else {
391       content::BrowserThread::PostTask(content::BrowserThread::IO,
392           FROM_HERE,
393           base::Bind(iter->error_callback,
394                      base::PLATFORM_FILE_ERROR_NOT_FOUND));
395     }
396   }
397
398   read_dir_transactions_.clear();
399 }
400
401 // Invoked on UI thread from the listener.
402 void MTPDeviceDelegateImplMac::DownloadedFile(
403     const std::string& name, base::PlatformFileError error) {
404   // If we're cancelled and deleting, we may have deleted the camera.
405   if (!camera_interface_.get())
406     return;
407
408   bool found = false;
409   ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
410   for (; iter != read_file_transactions_.end(); ++iter) {
411     if (iter->request_file == name) {
412       found = true;
413       break;
414     }
415   }
416   if (!found)
417     return;
418
419   if (error != base::PLATFORM_FILE_OK) {
420     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
421         base::Bind(iter->error_callback, error));
422     read_file_transactions_.erase(iter);
423     return;
424   }
425
426   base::FilePath relative_path(name);
427   std::vector<base::FilePath::StringType> components;
428   relative_path.GetComponents(&components);
429   base::FilePath item_filename = root_path_;
430   for (std::vector<base::FilePath::StringType>::iterator i =
431            components.begin();
432        i != components.end(); ++i) {
433     item_filename = item_filename.Append(*i);
434   }
435
436   base::PlatformFileInfo info = file_info_[item_filename.value()];
437   content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
438       base::Bind(iter->success_callback, info, iter->snapshot_file));
439   read_file_transactions_.erase(iter);
440 }
441
442 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
443     const std::string& file,
444     const base::FilePath& snapshot_filename,
445     CreateSnapshotFileSuccessCallback success_cb,
446     ErrorCallback error_cb)
447     : request_file(file),
448       snapshot_file(snapshot_filename),
449       success_callback(success_cb),
450       error_callback(error_cb) {}
451
452 MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
453
454 MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
455
456 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
457     const base::FilePath& dir,
458     ReadDirectorySuccessCallback success_cb,
459     ErrorCallback error_cb)
460     : directory(dir),
461       success_callback(success_cb),
462       error_callback(error_cb) {}
463
464 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
465
466 void CreateMTPDeviceAsyncDelegate(
467     const base::FilePath::StringType& device_location,
468     const CreateMTPDeviceAsyncDelegateCallback& cb) {
469   std::string device_name = base::FilePath(device_location).BaseName().value();
470   std::string device_id;
471   StorageInfo::Type type;
472   bool cracked = StorageInfo::CrackDeviceId(device_name, &type, &device_id);
473   DCHECK(cracked);
474   DCHECK_EQ(StorageInfo::MAC_IMAGE_CAPTURE, type);
475
476   cb.Run(new MTPDeviceDelegateImplMac(device_id, device_location));
477 }