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