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/linux/mtp_device_delegate_impl_linux.h"
6
7#include "base/bind.h"
8#include "base/files/file_path.h"
9#include "base/strings/string_util.h"
10#include "chrome/browser/media_galleries/linux/mtp_device_task_helper.h"
11#include "chrome/browser/media_galleries/linux/mtp_device_task_helper_map_service.h"
12#include "chrome/browser/media_galleries/linux/snapshot_file_details.h"
13#include "content/public/browser/browser_thread.h"
14
15namespace {
16
17// File path separator constant.
18const char kRootPath[] = "/";
19
20// Returns the device relative file path given |file_path|.
21// E.g.: If the |file_path| is "/usb:2,2:12345/DCIM" and |registered_dev_path|
22// is "/usb:2,2:12345", this function returns the device relative path which is
23// "/DCIM".
24std::string GetDeviceRelativePath(const base::FilePath& registered_dev_path,
25                                  const base::FilePath& file_path) {
26  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
27  DCHECK(!registered_dev_path.empty());
28  DCHECK(!file_path.empty());
29  if (registered_dev_path == file_path)
30    return kRootPath;
31
32  base::FilePath relative_path;
33  if (!registered_dev_path.AppendRelativePath(file_path, &relative_path))
34    return std::string();
35  DCHECK(!relative_path.empty());
36  return relative_path.value();
37}
38
39// Returns the MTPDeviceTaskHelper object associated with the MTP device
40// storage.
41//
42// |storage_name| specifies the name of the storage device.
43// Returns NULL if the |storage_name| is no longer valid (e.g. because the
44// corresponding storage device is detached, etc).
45MTPDeviceTaskHelper* GetDeviceTaskHelperForStorage(
46    const std::string& storage_name) {
47  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
48  return MTPDeviceTaskHelperMapService::GetInstance()->GetDeviceTaskHelper(
49      storage_name);
50}
51
52// Opens the storage device for communication.
53//
54// Called on the UI thread to dispatch the request to the
55// MediaTransferProtocolManager.
56//
57// |storage_name| specifies the name of the storage device.
58// |reply_callback| is called when the OpenStorage request completes.
59// |reply_callback| runs on the IO thread.
60void OpenStorageOnUIThread(
61    const std::string& storage_name,
62    const base::Callback<void(bool)>& reply_callback) {
63  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
64  MTPDeviceTaskHelper* task_helper =
65      GetDeviceTaskHelperForStorage(storage_name);
66  if (!task_helper) {
67    task_helper =
68        MTPDeviceTaskHelperMapService::GetInstance()->CreateDeviceTaskHelper(
69            storage_name);
70  }
71  task_helper->OpenStorage(storage_name, reply_callback);
72}
73
74// Enumerates the |root| directory file entries.
75//
76// Called on the UI thread to dispatch the request to the
77// MediaTransferProtocolManager.
78//
79// |storage_name| specifies the name of the storage device.
80// |success_callback| is called when the ReadDirectory request succeeds.
81// |error_callback| is called when the ReadDirectory request fails.
82// |success_callback| and |error_callback| runs on the IO thread.
83void ReadDirectoryOnUIThread(
84    const std::string& storage_name,
85    const std::string& root,
86    const base::Callback<
87        void(const fileapi::AsyncFileUtil::EntryList&)>& success_callback,
88    const base::Callback<void(base::PlatformFileError)>& error_callback) {
89  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
90  MTPDeviceTaskHelper* task_helper =
91      GetDeviceTaskHelperForStorage(storage_name);
92  if (!task_helper)
93    return;
94  task_helper->ReadDirectoryByPath(root, success_callback, error_callback);
95}
96
97// Gets the |file_path| details.
98//
99// Called on the UI thread to dispatch the request to the
100// MediaTransferProtocolManager.
101//
102// |storage_name| specifies the name of the storage device.
103// |success_callback| is called when the GetFileInfo request succeeds.
104// |error_callback| is called when the GetFileInfo request fails.
105// |success_callback| and |error_callback| runs on the IO thread.
106void GetFileInfoOnUIThread(
107    const std::string& storage_name,
108    const std::string& file_path,
109    const base::Callback<void(const base::PlatformFileInfo&)>& success_callback,
110    const base::Callback<void(base::PlatformFileError)>& error_callback) {
111  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
112  MTPDeviceTaskHelper* task_helper =
113      GetDeviceTaskHelperForStorage(storage_name);
114  if (!task_helper)
115    return;
116  task_helper->GetFileInfoByPath(file_path, success_callback, error_callback);
117}
118
119// Copies the contents of |device_file_path| to |snapshot_file_path|.
120//
121// Called on the UI thread to dispatch the request to the
122// MediaTransferProtocolManager.
123//
124// |storage_name| specifies the name of the storage device.
125// |device_file_path| specifies the media device file path.
126// |snapshot_file_path| specifies the platform path of the snapshot file.
127// |file_size| specifies the number of bytes that will be written to the
128// snapshot file.
129// |success_callback| is called when the copy operation succeeds.
130// |error_callback| is called when the copy operation fails.
131// |success_callback| and |error_callback| runs on the IO thread.
132void WriteDataIntoSnapshotFileOnUIThread(
133    const std::string& storage_name,
134    const SnapshotRequestInfo& request_info,
135    const base::PlatformFileInfo& snapshot_file_info) {
136  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
137  MTPDeviceTaskHelper* task_helper =
138      GetDeviceTaskHelperForStorage(storage_name);
139  if (!task_helper)
140    return;
141  task_helper->WriteDataIntoSnapshotFile(request_info, snapshot_file_info);
142}
143
144// Closes the device storage specified by the |storage_name| and destroys the
145// MTPDeviceTaskHelper object associated with the device storage.
146//
147// Called on the UI thread to dispatch the request to the
148// MediaTransferProtocolManager.
149void CloseStorageAndDestroyTaskHelperOnUIThread(
150    const std::string& storage_name) {
151  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
152  MTPDeviceTaskHelper* task_helper =
153      GetDeviceTaskHelperForStorage(storage_name);
154  if (!task_helper)
155    return;
156  task_helper->CloseStorage();
157  MTPDeviceTaskHelperMapService::GetInstance()->DestroyDeviceTaskHelper(
158      storage_name);
159}
160
161}  // namespace
162
163MTPDeviceDelegateImplLinux::PendingTaskInfo::PendingTaskInfo(
164    const tracked_objects::Location& location,
165    const base::Closure& task)
166    : location(location),
167      task(task) {
168}
169
170MTPDeviceDelegateImplLinux::PendingTaskInfo::~PendingTaskInfo() {
171}
172
173MTPDeviceDelegateImplLinux::MTPDeviceDelegateImplLinux(
174    const std::string& device_location)
175    : init_state_(UNINITIALIZED),
176      task_in_progress_(false),
177      device_path_(device_location),
178      weak_ptr_factory_(this) {
179  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
180  DCHECK(!device_path_.empty());
181  base::RemoveChars(device_location, kRootPath, &storage_name_);
182  DCHECK(!storage_name_.empty());
183}
184
185MTPDeviceDelegateImplLinux::~MTPDeviceDelegateImplLinux() {
186  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
187}
188
189void MTPDeviceDelegateImplLinux::GetFileInfo(
190    const base::FilePath& file_path,
191    const GetFileInfoSuccessCallback& success_callback,
192    const ErrorCallback& error_callback) {
193  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
194  DCHECK(!file_path.empty());
195  base::Closure call_closure =
196      base::Bind(&GetFileInfoOnUIThread,
197                 storage_name_,
198                 GetDeviceRelativePath(device_path_, file_path),
199                 base::Bind(&MTPDeviceDelegateImplLinux::OnDidGetFileInfo,
200                            weak_ptr_factory_.GetWeakPtr(),
201                            success_callback),
202                 base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
203                            weak_ptr_factory_.GetWeakPtr(),
204                            error_callback));
205  EnsureInitAndRunTask(PendingTaskInfo(FROM_HERE, call_closure));
206}
207
208void MTPDeviceDelegateImplLinux::ReadDirectory(
209    const base::FilePath& root,
210    const ReadDirectorySuccessCallback& success_callback,
211    const ErrorCallback& error_callback) {
212  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
213  DCHECK(!root.empty());
214  std::string device_file_relative_path = GetDeviceRelativePath(device_path_,
215                                                                root);
216  base::Closure call_closure =
217      base::Bind(
218          &GetFileInfoOnUIThread,
219          storage_name_,
220          device_file_relative_path,
221          base::Bind(
222              &MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory,
223              weak_ptr_factory_.GetWeakPtr(),
224              device_file_relative_path,
225              success_callback,
226              error_callback),
227          base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
228                     weak_ptr_factory_.GetWeakPtr(),
229                     error_callback));
230  EnsureInitAndRunTask(PendingTaskInfo(FROM_HERE, call_closure));
231}
232
233void MTPDeviceDelegateImplLinux::CreateSnapshotFile(
234    const base::FilePath& device_file_path,
235    const base::FilePath& snapshot_file_path,
236    const CreateSnapshotFileSuccessCallback& success_callback,
237    const ErrorCallback& error_callback) {
238  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
239  DCHECK(!device_file_path.empty());
240  DCHECK(!snapshot_file_path.empty());
241  std::string device_file_relative_path =
242      GetDeviceRelativePath(device_path_, device_file_path);
243  scoped_ptr<SnapshotRequestInfo> request_info(
244      new SnapshotRequestInfo(device_file_relative_path,
245                              snapshot_file_path,
246                              success_callback,
247                              error_callback));
248  base::Closure call_closure =
249      base::Bind(
250          &GetFileInfoOnUIThread,
251          storage_name_,
252          device_file_relative_path,
253          base::Bind(
254              &MTPDeviceDelegateImplLinux::OnDidGetFileInfoToCreateSnapshotFile,
255              weak_ptr_factory_.GetWeakPtr(),
256              base::Passed(&request_info)),
257          base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
258                     weak_ptr_factory_.GetWeakPtr(),
259                     error_callback));
260  EnsureInitAndRunTask(PendingTaskInfo(FROM_HERE, call_closure));
261}
262
263void MTPDeviceDelegateImplLinux::CancelPendingTasksAndDeleteDelegate() {
264  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
265  // To cancel all the pending tasks, destroy the MTPDeviceTaskHelper object.
266  content::BrowserThread::PostTask(
267      content::BrowserThread::UI,
268      FROM_HERE,
269      base::Bind(&CloseStorageAndDestroyTaskHelperOnUIThread, storage_name_));
270  delete this;
271}
272
273void MTPDeviceDelegateImplLinux::EnsureInitAndRunTask(
274    const PendingTaskInfo& task_info) {
275  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
276  if ((init_state_ == INITIALIZED) && !task_in_progress_) {
277    task_in_progress_ = true;
278    content::BrowserThread::PostTask(content::BrowserThread::UI,
279                                     task_info.location,
280                                     task_info.task);
281    return;
282  }
283  pending_tasks_.push(task_info);
284  if (init_state_ == UNINITIALIZED) {
285    init_state_ = PENDING_INIT;
286    task_in_progress_ = true;
287    content::BrowserThread::PostTask(
288        content::BrowserThread::UI,
289        FROM_HERE,
290        base::Bind(&OpenStorageOnUIThread,
291                   storage_name_,
292                   base::Bind(&MTPDeviceDelegateImplLinux::OnInitCompleted,
293                              weak_ptr_factory_.GetWeakPtr())));
294  }
295}
296
297void MTPDeviceDelegateImplLinux::WriteDataIntoSnapshotFile(
298    const base::PlatformFileInfo& file_info) {
299  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
300  DCHECK(current_snapshot_request_info_.get());
301  DCHECK_GT(file_info.size, 0);
302  task_in_progress_ = true;
303  SnapshotRequestInfo request_info(
304      current_snapshot_request_info_->device_file_path,
305      current_snapshot_request_info_->snapshot_file_path,
306      base::Bind(
307          &MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile,
308          weak_ptr_factory_.GetWeakPtr()),
309      base::Bind(
310          &MTPDeviceDelegateImplLinux::OnWriteDataIntoSnapshotFileError,
311          weak_ptr_factory_.GetWeakPtr()));
312
313  base::Closure task_closure = base::Bind(&WriteDataIntoSnapshotFileOnUIThread,
314                                          storage_name_,
315                                          request_info,
316                                          file_info);
317  content::BrowserThread::PostTask(content::BrowserThread::UI,
318                                   FROM_HERE,
319                                   task_closure);
320}
321
322void MTPDeviceDelegateImplLinux::ProcessNextPendingRequest() {
323  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
324  DCHECK(!task_in_progress_);
325  if (pending_tasks_.empty())
326    return;
327
328  task_in_progress_ = true;
329  const PendingTaskInfo& task_info = pending_tasks_.front();
330  content::BrowserThread::PostTask(content::BrowserThread::UI,
331                                   task_info.location,
332                                   task_info.task);
333  pending_tasks_.pop();
334}
335
336void MTPDeviceDelegateImplLinux::OnInitCompleted(bool succeeded) {
337  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
338  init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
339  task_in_progress_ = false;
340  ProcessNextPendingRequest();
341}
342
343void MTPDeviceDelegateImplLinux::OnDidGetFileInfo(
344    const GetFileInfoSuccessCallback& success_callback,
345    const base::PlatformFileInfo& file_info) {
346  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
347  success_callback.Run(file_info);
348  task_in_progress_ = false;
349  ProcessNextPendingRequest();
350}
351
352void MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory(
353    const std::string& root,
354    const ReadDirectorySuccessCallback& success_callback,
355    const ErrorCallback& error_callback,
356    const base::PlatformFileInfo& file_info) {
357  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
358  DCHECK(task_in_progress_);
359  if (!file_info.is_directory) {
360    return HandleDeviceFileError(error_callback,
361                                 base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY);
362  }
363
364  base::Closure task_closure =
365      base::Bind(&ReadDirectoryOnUIThread,
366                 storage_name_,
367                 root,
368                 base::Bind(&MTPDeviceDelegateImplLinux::OnDidReadDirectory,
369                            weak_ptr_factory_.GetWeakPtr(),
370                            success_callback),
371                 base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
372                            weak_ptr_factory_.GetWeakPtr(),
373                            error_callback));
374  content::BrowserThread::PostTask(content::BrowserThread::UI,
375                                   FROM_HERE,
376                                   task_closure);
377}
378
379void MTPDeviceDelegateImplLinux::OnDidGetFileInfoToCreateSnapshotFile(
380    scoped_ptr<SnapshotRequestInfo> snapshot_request_info,
381    const base::PlatformFileInfo& file_info) {
382  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
383  DCHECK(!current_snapshot_request_info_.get());
384  DCHECK(snapshot_request_info.get());
385  DCHECK(task_in_progress_);
386  base::PlatformFileError error = base::PLATFORM_FILE_OK;
387  if (file_info.is_directory)
388    error = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
389  else if (file_info.size < 0 || file_info.size > kuint32max)
390    error = base::PLATFORM_FILE_ERROR_FAILED;
391
392  if (error != base::PLATFORM_FILE_OK)
393    return HandleDeviceFileError(snapshot_request_info->error_callback, error);
394
395  base::PlatformFileInfo snapshot_file_info(file_info);
396  // Modify the last modified time to null. This prevents the time stamp
397  // verfication in LocalFileStreamReader.
398  snapshot_file_info.last_modified = base::Time();
399
400  current_snapshot_request_info_.reset(snapshot_request_info.release());
401  if (file_info.size == 0) {
402    // Empty snapshot file.
403    return OnDidWriteDataIntoSnapshotFile(
404        snapshot_file_info, current_snapshot_request_info_->snapshot_file_path);
405  }
406  WriteDataIntoSnapshotFile(snapshot_file_info);
407}
408
409void MTPDeviceDelegateImplLinux::OnDidReadDirectory(
410    const ReadDirectorySuccessCallback& success_callback,
411    const fileapi::AsyncFileUtil::EntryList& file_list) {
412  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
413  success_callback.Run(file_list, false /*no more entries*/);
414  task_in_progress_ = false;
415  ProcessNextPendingRequest();
416}
417
418void MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile(
419    const base::PlatformFileInfo& file_info,
420    const base::FilePath& snapshot_file_path) {
421  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
422  DCHECK(current_snapshot_request_info_.get());
423  DCHECK(task_in_progress_);
424  current_snapshot_request_info_->success_callback.Run(
425      file_info, snapshot_file_path);
426  task_in_progress_ = false;
427  current_snapshot_request_info_.reset();
428  ProcessNextPendingRequest();
429}
430
431void MTPDeviceDelegateImplLinux::OnWriteDataIntoSnapshotFileError(
432    base::PlatformFileError error) {
433  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
434  DCHECK(current_snapshot_request_info_.get());
435  DCHECK(task_in_progress_);
436  current_snapshot_request_info_->error_callback.Run(error);
437  task_in_progress_ = false;
438  current_snapshot_request_info_.reset();
439  ProcessNextPendingRequest();
440}
441
442void MTPDeviceDelegateImplLinux::HandleDeviceFileError(
443    const ErrorCallback& error_callback,
444    base::PlatformFileError error) {
445  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
446  error_callback.Run(error);
447  task_in_progress_ = false;
448  ProcessNextPendingRequest();
449}
450
451void CreateMTPDeviceAsyncDelegate(
452    const std::string& device_location,
453    const CreateMTPDeviceAsyncDelegateCallback& callback) {
454  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
455  callback.Run(new MTPDeviceDelegateImplLinux(device_location));
456}
457