1// Copyright 2013 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_task_helper.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "base/numerics/safe_conversions.h"
11#include "chrome/browser/media_galleries/linux/mtp_device_object_enumerator.h"
12#include "chrome/browser/media_galleries/linux/mtp_read_file_worker.h"
13#include "chrome/browser/media_galleries/linux/snapshot_file_details.h"
14#include "components/storage_monitor/storage_monitor.h"
15#include "content/public/browser/browser_thread.h"
16#include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
17#include "net/base/io_buffer.h"
18#include "third_party/cros_system_api/dbus/service_constants.h"
19#include "webkit/browser/fileapi/async_file_util.h"
20#include "webkit/common/fileapi/file_system_util.h"
21
22using storage_monitor::StorageMonitor;
23
24namespace {
25
26// Does nothing.
27// This method is used to handle the results of
28// MediaTransferProtocolManager::CloseStorage method call.
29void DoNothing(bool error) {
30}
31
32device::MediaTransferProtocolManager* GetMediaTransferProtocolManager() {
33  return StorageMonitor::GetInstance()->media_transfer_protocol_manager();
34}
35
36base::File::Info FileInfoFromMTPFileEntry(const MtpFileEntry& file_entry) {
37  base::File::Info file_entry_info;
38  file_entry_info.size = file_entry.file_size();
39  file_entry_info.is_directory =
40      file_entry.file_type() == MtpFileEntry::FILE_TYPE_FOLDER;
41  file_entry_info.is_symbolic_link = false;
42  file_entry_info.last_modified =
43      base::Time::FromTimeT(file_entry.modification_time());
44  file_entry_info.last_accessed = file_entry_info.last_modified;
45  file_entry_info.creation_time = base::Time();
46  return file_entry_info;
47}
48
49}  // namespace
50
51MTPDeviceTaskHelper::MTPDeviceTaskHelper()
52    : weak_ptr_factory_(this) {
53  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
54}
55
56MTPDeviceTaskHelper::~MTPDeviceTaskHelper() {
57  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
58}
59
60void MTPDeviceTaskHelper::OpenStorage(const std::string& storage_name,
61                                      const OpenStorageCallback& callback) {
62  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
63  DCHECK(!storage_name.empty());
64  if (!device_handle_.empty()) {
65    content::BrowserThread::PostTask(content::BrowserThread::IO,
66                                     FROM_HERE,
67                                     base::Bind(callback, true));
68    return;
69  }
70  GetMediaTransferProtocolManager()->OpenStorage(
71      storage_name, mtpd::kReadOnlyMode,
72      base::Bind(&MTPDeviceTaskHelper::OnDidOpenStorage,
73                 weak_ptr_factory_.GetWeakPtr(),
74                 callback));
75}
76
77void MTPDeviceTaskHelper::GetFileInfoByPath(
78    const std::string& file_path,
79    const GetFileInfoSuccessCallback& success_callback,
80    const ErrorCallback& error_callback) {
81  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
82  if (device_handle_.empty())
83    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
84
85  GetMediaTransferProtocolManager()->GetFileInfoByPath(
86      device_handle_, file_path,
87      base::Bind(&MTPDeviceTaskHelper::OnGetFileInfo,
88                 weak_ptr_factory_.GetWeakPtr(),
89                 success_callback,
90                 error_callback));
91}
92
93void MTPDeviceTaskHelper::ReadDirectoryByPath(
94    const std::string& dir_path,
95    const ReadDirectorySuccessCallback& success_callback,
96    const ErrorCallback& error_callback) {
97  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
98  if (device_handle_.empty())
99    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
100
101  GetMediaTransferProtocolManager()->ReadDirectoryByPath(
102      device_handle_, dir_path,
103      base::Bind(&MTPDeviceTaskHelper::OnDidReadDirectoryByPath,
104                 weak_ptr_factory_.GetWeakPtr(),
105                 success_callback,
106                 error_callback));
107}
108
109void MTPDeviceTaskHelper::WriteDataIntoSnapshotFile(
110    const SnapshotRequestInfo& request_info,
111    const base::File::Info& snapshot_file_info) {
112  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
113  if (device_handle_.empty()) {
114    return HandleDeviceError(request_info.error_callback,
115                             base::File::FILE_ERROR_FAILED);
116  }
117
118  if (!read_file_worker_)
119    read_file_worker_.reset(new MTPReadFileWorker(device_handle_));
120  read_file_worker_->WriteDataIntoSnapshotFile(request_info,
121                                               snapshot_file_info);
122}
123
124void MTPDeviceTaskHelper::ReadBytes(
125    const MTPDeviceAsyncDelegate::ReadBytesRequest& request) {
126  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
127  if (device_handle_.empty()) {
128    return HandleDeviceError(request.error_callback,
129                             base::File::FILE_ERROR_FAILED);
130  }
131
132  GetMediaTransferProtocolManager()->GetFileInfoByPath(
133      device_handle_, request.device_file_relative_path,
134      base::Bind(&MTPDeviceTaskHelper::OnGetFileInfoToReadBytes,
135                 weak_ptr_factory_.GetWeakPtr(), request));
136}
137
138void MTPDeviceTaskHelper::CloseStorage() const {
139  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
140  if (device_handle_.empty())
141    return;
142  GetMediaTransferProtocolManager()->CloseStorage(device_handle_,
143                                                  base::Bind(&DoNothing));
144}
145
146void MTPDeviceTaskHelper::OnDidOpenStorage(
147    const OpenStorageCallback& completion_callback,
148    const std::string& device_handle,
149    bool error) {
150  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
151  device_handle_ = device_handle;
152  content::BrowserThread::PostTask(content::BrowserThread::IO,
153                                   FROM_HERE,
154                                   base::Bind(completion_callback, !error));
155}
156
157void MTPDeviceTaskHelper::OnGetFileInfo(
158    const GetFileInfoSuccessCallback& success_callback,
159    const ErrorCallback& error_callback,
160    const MtpFileEntry& file_entry,
161    bool error) const {
162  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
163  if (error) {
164    return HandleDeviceError(error_callback,
165                             base::File::FILE_ERROR_NOT_FOUND);
166  }
167
168  content::BrowserThread::PostTask(
169      content::BrowserThread::IO,
170      FROM_HERE,
171      base::Bind(success_callback, FileInfoFromMTPFileEntry(file_entry)));
172}
173
174void MTPDeviceTaskHelper::OnDidReadDirectoryByPath(
175    const ReadDirectorySuccessCallback& success_callback,
176    const ErrorCallback& error_callback,
177    const std::vector<MtpFileEntry>& file_entries,
178    bool error) const {
179  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
180  if (error)
181    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
182
183  fileapi::AsyncFileUtil::EntryList entries;
184  base::FilePath current;
185  MTPDeviceObjectEnumerator file_enum(file_entries);
186  while (!(current = file_enum.Next()).empty()) {
187    fileapi::DirectoryEntry entry;
188    entry.name = fileapi::VirtualPath::BaseName(current).value();
189    entry.is_directory = file_enum.IsDirectory();
190    entry.size = file_enum.Size();
191    entry.last_modified_time = file_enum.LastModifiedTime();
192    entries.push_back(entry);
193  }
194  content::BrowserThread::PostTask(content::BrowserThread::IO,
195                                   FROM_HERE,
196                                   base::Bind(success_callback, entries));
197}
198
199void MTPDeviceTaskHelper::OnGetFileInfoToReadBytes(
200    const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
201    const MtpFileEntry& file_entry,
202    bool error) {
203  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
204  DCHECK(request.buf);
205  DCHECK(request.buf_len >= 0);
206  DCHECK_GE(request.offset, 0);
207  if (error) {
208    return HandleDeviceError(request.error_callback,
209                             base::File::FILE_ERROR_FAILED);
210  }
211
212  base::File::Info file_info = FileInfoFromMTPFileEntry(file_entry);
213  if (file_info.is_directory) {
214    return HandleDeviceError(request.error_callback,
215                             base::File::FILE_ERROR_NOT_A_FILE);
216  } else if (file_info.size < 0 || file_info.size > kuint32max ||
217             request.offset > file_info.size) {
218    return HandleDeviceError(request.error_callback,
219                             base::File::FILE_ERROR_FAILED);
220  } else if (request.offset == file_info.size) {
221    content::BrowserThread::PostTask(content::BrowserThread::IO,
222                                     FROM_HERE,
223                                     base::Bind(request.success_callback,
224                                                file_info, 0u));
225    return;
226  }
227
228  uint32 bytes_to_read = std::min(
229      base::checked_cast<uint32>(request.buf_len),
230      base::saturated_cast<uint32>(file_info.size - request.offset));
231
232  GetMediaTransferProtocolManager()->ReadFileChunkByPath(
233      device_handle_,
234      request.device_file_relative_path,
235      base::checked_cast<uint32>(request.offset),
236      bytes_to_read,
237      base::Bind(&MTPDeviceTaskHelper::OnDidReadBytes,
238                 weak_ptr_factory_.GetWeakPtr(), request, file_info));
239}
240
241void MTPDeviceTaskHelper::OnDidReadBytes(
242    const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
243    const base::File::Info& file_info,
244    const std::string& data,
245    bool error) const {
246  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
247  if (error) {
248    return HandleDeviceError(request.error_callback,
249                             base::File::FILE_ERROR_FAILED);
250  }
251
252  CHECK_LE(base::checked_cast<int>(data.length()), request.buf_len);
253  std::copy(data.begin(), data.end(), request.buf->data());
254
255  content::BrowserThread::PostTask(content::BrowserThread::IO,
256                                   FROM_HERE,
257                                   base::Bind(request.success_callback,
258                                              file_info, data.length()));
259}
260
261void MTPDeviceTaskHelper::HandleDeviceError(
262    const ErrorCallback& error_callback,
263    base::File::Error error) const {
264  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
265  content::BrowserThread::PostTask(content::BrowserThread::IO,
266                                   FROM_HERE,
267                                   base::Bind(error_callback, error));
268}
269