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#include <vector>
9
10#include "base/bind.h"
11#include "base/numerics/safe_conversions.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_split.h"
14#include "base/strings/string_util.h"
15#include "chrome/browser/media_galleries/linux/mtp_device_task_helper.h"
16#include "chrome/browser/media_galleries/linux/mtp_device_task_helper_map_service.h"
17#include "chrome/browser/media_galleries/linux/snapshot_file_details.h"
18#include "net/base/io_buffer.h"
19#include "third_party/cros_system_api/dbus/service_constants.h"
20
21namespace {
22
23// File path separator constant.
24const char kRootPath[] = "/";
25
26// Returns the device relative file path given |file_path|.
27// E.g.: If the |file_path| is "/usb:2,2:12345/DCIM" and |registered_dev_path|
28// is "/usb:2,2:12345", this function returns the device relative path which is
29// "DCIM".
30// In the special case when |registered_dev_path| and |file_path| are the same,
31// return |kRootPath|.
32std::string GetDeviceRelativePath(const base::FilePath& registered_dev_path,
33                                  const base::FilePath& file_path) {
34  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
35  DCHECK(!registered_dev_path.empty());
36  DCHECK(!file_path.empty());
37  std::string result;
38  if (registered_dev_path == file_path) {
39    result = kRootPath;
40  } else {
41    base::FilePath relative_path;
42    if (registered_dev_path.AppendRelativePath(file_path, &relative_path)) {
43      DCHECK(!relative_path.empty());
44      result = relative_path.value();
45    }
46  }
47  return result;
48}
49
50// Returns the MTPDeviceTaskHelper object associated with the MTP device
51// storage.
52//
53// |storage_name| specifies the name of the storage device.
54// Returns NULL if the |storage_name| is no longer valid (e.g. because the
55// corresponding storage device is detached, etc).
56MTPDeviceTaskHelper* GetDeviceTaskHelperForStorage(
57    const std::string& storage_name) {
58  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
59  return MTPDeviceTaskHelperMapService::GetInstance()->GetDeviceTaskHelper(
60      storage_name);
61}
62
63// Opens the storage device for communication.
64//
65// Called on the UI thread to dispatch the request to the
66// MediaTransferProtocolManager.
67//
68// |storage_name| specifies the name of the storage device.
69// |reply_callback| is called when the OpenStorage request completes.
70// |reply_callback| runs on the IO thread.
71void OpenStorageOnUIThread(
72    const std::string& storage_name,
73    const MTPDeviceTaskHelper::OpenStorageCallback& reply_callback) {
74  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
75  MTPDeviceTaskHelper* task_helper =
76      GetDeviceTaskHelperForStorage(storage_name);
77  if (!task_helper) {
78    task_helper =
79        MTPDeviceTaskHelperMapService::GetInstance()->CreateDeviceTaskHelper(
80            storage_name);
81  }
82  task_helper->OpenStorage(storage_name, reply_callback);
83}
84
85// Enumerates the |dir_id| directory file entries.
86//
87// Called on the UI thread to dispatch the request to the
88// MediaTransferProtocolManager.
89//
90// |storage_name| specifies the name of the storage device.
91// |success_callback| is called when the ReadDirectory request succeeds.
92// |error_callback| is called when the ReadDirectory request fails.
93// |success_callback| and |error_callback| runs on the IO thread.
94void ReadDirectoryOnUIThread(
95    const std::string& storage_name,
96    uint32 dir_id,
97    const MTPDeviceTaskHelper::ReadDirectorySuccessCallback& success_callback,
98    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
99  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
100  MTPDeviceTaskHelper* task_helper =
101      GetDeviceTaskHelperForStorage(storage_name);
102  if (!task_helper)
103    return;
104  task_helper->ReadDirectory(dir_id, success_callback, error_callback);
105}
106
107// Gets the |file_path| details.
108//
109// Called on the UI thread to dispatch the request to the
110// MediaTransferProtocolManager.
111//
112// |storage_name| specifies the name of the storage device.
113// |success_callback| is called when the GetFileInfo request succeeds.
114// |error_callback| is called when the GetFileInfo request fails.
115// |success_callback| and |error_callback| runs on the IO thread.
116void GetFileInfoOnUIThread(
117    const std::string& storage_name,
118    uint32 file_id,
119    const MTPDeviceTaskHelper::GetFileInfoSuccessCallback& success_callback,
120    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
121  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
122  MTPDeviceTaskHelper* task_helper =
123      GetDeviceTaskHelperForStorage(storage_name);
124  if (!task_helper)
125    return;
126  task_helper->GetFileInfo(file_id, success_callback, error_callback);
127}
128
129// Copies the contents of |device_file_path| to |snapshot_file_path|.
130//
131// Called on the UI thread to dispatch the request to the
132// MediaTransferProtocolManager.
133//
134// |storage_name| specifies the name of the storage device.
135// |device_file_path| specifies the media device file path.
136// |snapshot_file_path| specifies the platform path of the snapshot file.
137// |file_size| specifies the number of bytes that will be written to the
138// snapshot file.
139// |success_callback| is called when the copy operation succeeds.
140// |error_callback| is called when the copy operation fails.
141// |success_callback| and |error_callback| runs on the IO thread.
142void WriteDataIntoSnapshotFileOnUIThread(
143    const std::string& storage_name,
144    const SnapshotRequestInfo& request_info,
145    const base::File::Info& snapshot_file_info) {
146  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
147  MTPDeviceTaskHelper* task_helper =
148      GetDeviceTaskHelperForStorage(storage_name);
149  if (!task_helper)
150    return;
151  task_helper->WriteDataIntoSnapshotFile(request_info, snapshot_file_info);
152}
153
154// Copies the contents of |device_file_path| to |snapshot_file_path|.
155//
156// Called on the UI thread to dispatch the request to the
157// MediaTransferProtocolManager.
158//
159// |storage_name| specifies the name of the storage device.
160// |request| is a struct containing details about the byte read request.
161void ReadBytesOnUIThread(
162    const std::string& storage_name,
163    const MTPDeviceAsyncDelegate::ReadBytesRequest& request) {
164  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
165  MTPDeviceTaskHelper* task_helper =
166      GetDeviceTaskHelperForStorage(storage_name);
167  if (!task_helper)
168    return;
169  task_helper->ReadBytes(request);
170}
171
172// Closes the device storage specified by the |storage_name| and destroys the
173// MTPDeviceTaskHelper object associated with the device storage.
174//
175// Called on the UI thread to dispatch the request to the
176// MediaTransferProtocolManager.
177void CloseStorageAndDestroyTaskHelperOnUIThread(
178    const std::string& storage_name) {
179  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
180  MTPDeviceTaskHelper* task_helper =
181      GetDeviceTaskHelperForStorage(storage_name);
182  if (!task_helper)
183    return;
184  task_helper->CloseStorage();
185  MTPDeviceTaskHelperMapService::GetInstance()->DestroyDeviceTaskHelper(
186      storage_name);
187}
188
189}  // namespace
190
191MTPDeviceDelegateImplLinux::PendingTaskInfo::PendingTaskInfo(
192    const base::FilePath& path,
193    content::BrowserThread::ID thread_id,
194    const tracked_objects::Location& location,
195    const base::Closure& task)
196    : path(path),
197      thread_id(thread_id),
198      location(location),
199      task(task) {
200}
201
202MTPDeviceDelegateImplLinux::PendingTaskInfo::~PendingTaskInfo() {
203}
204
205// Represents a file on the MTP device.
206// Lives on the IO thread.
207class MTPDeviceDelegateImplLinux::MTPFileNode {
208 public:
209  MTPFileNode(uint32 file_id,
210              const std::string& file_name,
211              MTPFileNode* parent,
212              FileIdToMTPFileNodeMap* file_id_to_node_map);
213  ~MTPFileNode();
214
215  const MTPFileNode* GetChild(const std::string& name) const;
216
217  void EnsureChildExists(const std::string& name, uint32 id);
218
219  // Clears all the children, except those in |children_to_keep|.
220  void ClearNonexistentChildren(
221      const std::set<std::string>& children_to_keep);
222
223  bool DeleteChild(uint32 file_id);
224
225  uint32 file_id() const { return file_id_; }
226  const std::string& file_name() const { return file_name_; }
227  MTPFileNode* parent() { return parent_; }
228
229 private:
230  // Container for holding a node's children.
231  typedef base::ScopedPtrHashMap<std::string, MTPFileNode> ChildNodes;
232
233  const uint32 file_id_;
234  const std::string file_name_;
235
236  ChildNodes children_;
237  MTPFileNode* const parent_;
238  FileIdToMTPFileNodeMap* file_id_to_node_map_;
239
240  DISALLOW_COPY_AND_ASSIGN(MTPFileNode);
241};
242
243MTPDeviceDelegateImplLinux::MTPFileNode::MTPFileNode(
244    uint32 file_id,
245    const std::string& file_name,
246    MTPFileNode* parent,
247    FileIdToMTPFileNodeMap* file_id_to_node_map)
248    : file_id_(file_id),
249      file_name_(file_name),
250      parent_(parent),
251      file_id_to_node_map_(file_id_to_node_map) {
252  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
253  DCHECK(file_id_to_node_map_);
254  DCHECK(!ContainsKey(*file_id_to_node_map_, file_id_));
255  (*file_id_to_node_map_)[file_id_] = this;
256}
257
258MTPDeviceDelegateImplLinux::MTPFileNode::~MTPFileNode() {
259  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
260  size_t erased = file_id_to_node_map_->erase(file_id_);
261  DCHECK_EQ(1U, erased);
262}
263
264const MTPDeviceDelegateImplLinux::MTPFileNode*
265MTPDeviceDelegateImplLinux::MTPFileNode::GetChild(
266    const std::string& name) const {
267  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
268  return children_.get(name);
269}
270
271void MTPDeviceDelegateImplLinux::MTPFileNode::EnsureChildExists(
272    const std::string& name,
273    uint32 id) {
274  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
275  const MTPFileNode* child = GetChild(name);
276  if (child && child->file_id() == id)
277    return;
278
279  children_.set(
280      name,
281      make_scoped_ptr(new MTPFileNode(id, name, this, file_id_to_node_map_)));
282}
283
284void MTPDeviceDelegateImplLinux::MTPFileNode::ClearNonexistentChildren(
285    const std::set<std::string>& children_to_keep) {
286  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
287  std::set<std::string> children_to_erase;
288  for (ChildNodes::const_iterator it = children_.begin();
289       it != children_.end(); ++it) {
290    if (ContainsKey(children_to_keep, it->first))
291      continue;
292    children_to_erase.insert(it->first);
293  }
294  for (std::set<std::string>::iterator it = children_to_erase.begin();
295       it != children_to_erase.end(); ++it) {
296    children_.take_and_erase(*it);
297  }
298}
299
300bool MTPDeviceDelegateImplLinux::MTPFileNode::DeleteChild(uint32 file_id) {
301  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
302  for (ChildNodes::iterator it = children_.begin();
303       it != children_.end(); ++it) {
304    if (it->second->file_id() == file_id) {
305      children_.erase(it);
306      return true;
307    }
308  }
309  return false;
310}
311
312MTPDeviceDelegateImplLinux::MTPDeviceDelegateImplLinux(
313    const std::string& device_location)
314    : init_state_(UNINITIALIZED),
315      task_in_progress_(false),
316      device_path_(device_location),
317      root_node_(new MTPFileNode(mtpd::kRootFileId,
318                                 "",    // Root node has no name.
319                                 NULL,  // And no parent node.
320                                 &file_id_to_node_map_)),
321      weak_ptr_factory_(this) {
322  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
323  DCHECK(!device_path_.empty());
324  base::RemoveChars(device_location, kRootPath, &storage_name_);
325  DCHECK(!storage_name_.empty());
326}
327
328MTPDeviceDelegateImplLinux::~MTPDeviceDelegateImplLinux() {
329  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
330}
331
332void MTPDeviceDelegateImplLinux::GetFileInfo(
333    const base::FilePath& file_path,
334    const GetFileInfoSuccessCallback& success_callback,
335    const ErrorCallback& error_callback) {
336  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
337  DCHECK(!file_path.empty());
338
339  // If a ReadDirectory operation is in progress, the file info may already be
340  // cached.
341  FileInfoCache::const_iterator it = file_info_cache_.find(file_path);
342  if (it != file_info_cache_.end()) {
343    // TODO(thestig): This code is repeated in several places. Combine them.
344    // e.g. c/b/media_galleries/win/mtp_device_operations_util.cc
345    const storage::DirectoryEntry& cached_file_entry = it->second;
346    base::File::Info info;
347    info.size = cached_file_entry.size;
348    info.is_directory = cached_file_entry.is_directory;
349    info.is_symbolic_link = false;
350    info.last_modified = cached_file_entry.last_modified_time;
351    info.creation_time = base::Time();
352
353    success_callback.Run(info);
354    return;
355  }
356  base::Closure closure =
357      base::Bind(&MTPDeviceDelegateImplLinux::GetFileInfoInternal,
358                 weak_ptr_factory_.GetWeakPtr(),
359                 file_path,
360                 success_callback,
361                 error_callback);
362  EnsureInitAndRunTask(PendingTaskInfo(file_path,
363                                       content::BrowserThread::IO,
364                                       FROM_HERE,
365                                       closure));
366}
367
368void MTPDeviceDelegateImplLinux::ReadDirectory(
369    const base::FilePath& root,
370    const ReadDirectorySuccessCallback& success_callback,
371    const ErrorCallback& error_callback) {
372  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
373  DCHECK(!root.empty());
374  base::Closure closure =
375      base::Bind(&MTPDeviceDelegateImplLinux::ReadDirectoryInternal,
376                 weak_ptr_factory_.GetWeakPtr(),
377                 root,
378                 success_callback,
379                 error_callback);
380  EnsureInitAndRunTask(PendingTaskInfo(root,
381                                       content::BrowserThread::IO,
382                                       FROM_HERE,
383                                       closure));
384}
385
386void MTPDeviceDelegateImplLinux::CreateSnapshotFile(
387    const base::FilePath& device_file_path,
388    const base::FilePath& local_path,
389    const CreateSnapshotFileSuccessCallback& success_callback,
390    const ErrorCallback& error_callback) {
391  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
392  DCHECK(!device_file_path.empty());
393  DCHECK(!local_path.empty());
394  base::Closure closure =
395      base::Bind(&MTPDeviceDelegateImplLinux::CreateSnapshotFileInternal,
396                 weak_ptr_factory_.GetWeakPtr(),
397                 device_file_path,
398                 local_path,
399                 success_callback,
400                 error_callback);
401  EnsureInitAndRunTask(PendingTaskInfo(device_file_path,
402                                       content::BrowserThread::IO,
403                                       FROM_HERE,
404                                       closure));
405}
406
407bool MTPDeviceDelegateImplLinux::IsStreaming() {
408  return true;
409}
410
411void MTPDeviceDelegateImplLinux::ReadBytes(
412    const base::FilePath& device_file_path,
413    const scoped_refptr<net::IOBuffer>& buf,
414    int64 offset,
415    int buf_len,
416    const ReadBytesSuccessCallback& success_callback,
417    const ErrorCallback& error_callback) {
418  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
419  DCHECK(!device_file_path.empty());
420  base::Closure closure =
421      base::Bind(&MTPDeviceDelegateImplLinux::ReadBytesInternal,
422                 weak_ptr_factory_.GetWeakPtr(),
423                 device_file_path,
424                 buf,
425                 offset,
426                 buf_len,
427                 success_callback,
428                 error_callback);
429  EnsureInitAndRunTask(PendingTaskInfo(device_file_path,
430                                       content::BrowserThread::IO,
431                                       FROM_HERE,
432                                       closure));
433}
434
435void MTPDeviceDelegateImplLinux::CancelPendingTasksAndDeleteDelegate() {
436  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
437  // To cancel all the pending tasks, destroy the MTPDeviceTaskHelper object.
438  content::BrowserThread::PostTask(
439      content::BrowserThread::UI,
440      FROM_HERE,
441      base::Bind(&CloseStorageAndDestroyTaskHelperOnUIThread, storage_name_));
442  delete this;
443}
444
445void MTPDeviceDelegateImplLinux::GetFileInfoInternal(
446    const base::FilePath& file_path,
447    const GetFileInfoSuccessCallback& success_callback,
448    const ErrorCallback& error_callback) {
449  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
450
451  uint32 file_id;
452  if (CachedPathToId(file_path, &file_id)) {
453    GetFileInfoSuccessCallback success_callback_wrapper =
454        base::Bind(&MTPDeviceDelegateImplLinux::OnDidGetFileInfo,
455                   weak_ptr_factory_.GetWeakPtr(),
456                   success_callback);
457    ErrorCallback error_callback_wrapper =
458        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
459                   weak_ptr_factory_.GetWeakPtr(),
460                   error_callback,
461                   file_id);
462
463
464    base::Closure closure = base::Bind(&GetFileInfoOnUIThread,
465                                       storage_name_,
466                                       file_id,
467                                       success_callback_wrapper,
468                                       error_callback_wrapper);
469    EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
470                                         content::BrowserThread::UI,
471                                         FROM_HERE,
472                                         closure));
473  } else {
474    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
475  }
476  PendingRequestDone();
477}
478
479void MTPDeviceDelegateImplLinux::ReadDirectoryInternal(
480    const base::FilePath& root,
481    const ReadDirectorySuccessCallback& success_callback,
482    const ErrorCallback& error_callback) {
483  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
484
485  uint32 dir_id;
486  if (CachedPathToId(root, &dir_id)) {
487    GetFileInfoSuccessCallback success_callback_wrapper =
488        base::Bind(&MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory,
489                   weak_ptr_factory_.GetWeakPtr(),
490                   dir_id,
491                   success_callback,
492                   error_callback);
493    ErrorCallback error_callback_wrapper =
494        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
495                   weak_ptr_factory_.GetWeakPtr(),
496                   error_callback,
497                   dir_id);
498    base::Closure closure = base::Bind(&GetFileInfoOnUIThread,
499                                       storage_name_,
500                                       dir_id,
501                                       success_callback_wrapper,
502                                       error_callback_wrapper);
503    EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
504                                         content::BrowserThread::UI,
505                                         FROM_HERE,
506                                         closure));
507  } else {
508    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
509  }
510  PendingRequestDone();
511}
512
513void MTPDeviceDelegateImplLinux::CreateSnapshotFileInternal(
514    const base::FilePath& device_file_path,
515    const base::FilePath& local_path,
516    const CreateSnapshotFileSuccessCallback& success_callback,
517    const ErrorCallback& error_callback) {
518  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
519
520  uint32 file_id;
521  if (CachedPathToId(device_file_path, &file_id)) {
522    scoped_ptr<SnapshotRequestInfo> request_info(
523        new SnapshotRequestInfo(file_id,
524                                local_path,
525                                success_callback,
526                                error_callback));
527    GetFileInfoSuccessCallback success_callback_wrapper =
528        base::Bind(
529            &MTPDeviceDelegateImplLinux::OnDidGetFileInfoToCreateSnapshotFile,
530            weak_ptr_factory_.GetWeakPtr(),
531            base::Passed(&request_info));
532    ErrorCallback error_callback_wrapper =
533        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
534                   weak_ptr_factory_.GetWeakPtr(),
535                   error_callback,
536                   file_id);
537    base::Closure closure = base::Bind(&GetFileInfoOnUIThread,
538                                       storage_name_,
539                                       file_id,
540                                       success_callback_wrapper,
541                                       error_callback_wrapper);
542    EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
543                                         content::BrowserThread::UI,
544                                         FROM_HERE,
545                                         closure));
546  } else {
547    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
548  }
549  PendingRequestDone();
550}
551
552void MTPDeviceDelegateImplLinux::ReadBytesInternal(
553    const base::FilePath& device_file_path,
554    net::IOBuffer* buf, int64 offset, int buf_len,
555    const ReadBytesSuccessCallback& success_callback,
556    const ErrorCallback& error_callback) {
557  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
558
559  uint32 file_id;
560  if (CachedPathToId(device_file_path, &file_id)) {
561    ReadBytesRequest request(
562        file_id, buf, offset, buf_len,
563        base::Bind(&MTPDeviceDelegateImplLinux::OnDidReadBytes,
564                   weak_ptr_factory_.GetWeakPtr(),
565                   success_callback),
566        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
567                   weak_ptr_factory_.GetWeakPtr(),
568                   error_callback,
569                   file_id));
570
571    base::Closure closure =
572        base::Bind(base::Bind(&ReadBytesOnUIThread, storage_name_, request));
573    EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
574                                         content::BrowserThread::UI,
575                                         FROM_HERE,
576                                         closure));
577  } else {
578    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
579  }
580  PendingRequestDone();
581}
582
583void MTPDeviceDelegateImplLinux::EnsureInitAndRunTask(
584    const PendingTaskInfo& task_info) {
585  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
586  if ((init_state_ == INITIALIZED) && !task_in_progress_) {
587    RunTask(task_info);
588    return;
589  }
590
591  // Only *Internal functions have empty paths. Since they are the continuation
592  // of the current running task, they get to cut in line.
593  if (task_info.path.empty())
594    pending_tasks_.push_front(task_info);
595  else
596    pending_tasks_.push_back(task_info);
597
598  if (init_state_ == UNINITIALIZED) {
599    init_state_ = PENDING_INIT;
600    task_in_progress_ = true;
601    content::BrowserThread::PostTask(
602        content::BrowserThread::UI,
603        FROM_HERE,
604        base::Bind(&OpenStorageOnUIThread,
605                   storage_name_,
606                   base::Bind(&MTPDeviceDelegateImplLinux::OnInitCompleted,
607                              weak_ptr_factory_.GetWeakPtr())));
608  }
609}
610
611void MTPDeviceDelegateImplLinux::RunTask(const PendingTaskInfo& task_info) {
612  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
613  DCHECK_EQ(INITIALIZED, init_state_);
614  DCHECK(!task_in_progress_);
615  task_in_progress_ = true;
616
617  bool need_to_check_cache = !task_info.path.empty();
618  if (need_to_check_cache) {
619    base::FilePath uncached_path =
620        NextUncachedPathComponent(task_info.path, task_info.cached_path);
621    if (!uncached_path.empty()) {
622      // Save the current task and do a cache lookup first.
623      pending_tasks_.push_front(task_info);
624      FillFileCache(uncached_path);
625      return;
626    }
627  }
628
629  content::BrowserThread::PostTask(task_info.thread_id,
630                          task_info.location,
631                          task_info.task);
632}
633
634void MTPDeviceDelegateImplLinux::WriteDataIntoSnapshotFile(
635    const base::File::Info& file_info) {
636  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
637  DCHECK(current_snapshot_request_info_.get());
638  DCHECK_GT(file_info.size, 0);
639  DCHECK(task_in_progress_);
640  SnapshotRequestInfo request_info(
641      current_snapshot_request_info_->file_id,
642      current_snapshot_request_info_->snapshot_file_path,
643      base::Bind(
644          &MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile,
645          weak_ptr_factory_.GetWeakPtr()),
646      base::Bind(
647          &MTPDeviceDelegateImplLinux::OnWriteDataIntoSnapshotFileError,
648          weak_ptr_factory_.GetWeakPtr()));
649
650  base::Closure task_closure = base::Bind(&WriteDataIntoSnapshotFileOnUIThread,
651                                          storage_name_,
652                                          request_info,
653                                          file_info);
654  content::BrowserThread::PostTask(content::BrowserThread::UI,
655                                   FROM_HERE,
656                                   task_closure);
657}
658
659void MTPDeviceDelegateImplLinux::PendingRequestDone() {
660  DCHECK(task_in_progress_);
661  task_in_progress_ = false;
662  ProcessNextPendingRequest();
663}
664
665void MTPDeviceDelegateImplLinux::ProcessNextPendingRequest() {
666  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
667  DCHECK(!task_in_progress_);
668  if (pending_tasks_.empty())
669    return;
670
671  PendingTaskInfo task_info = pending_tasks_.front();
672  pending_tasks_.pop_front();
673  RunTask(task_info);
674}
675
676void MTPDeviceDelegateImplLinux::OnInitCompleted(bool succeeded) {
677  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
678  init_state_ = succeeded ? INITIALIZED : UNINITIALIZED;
679  PendingRequestDone();
680}
681
682void MTPDeviceDelegateImplLinux::OnDidGetFileInfo(
683    const GetFileInfoSuccessCallback& success_callback,
684    const base::File::Info& file_info) {
685  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
686  success_callback.Run(file_info);
687  PendingRequestDone();
688}
689
690void MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory(
691    uint32 dir_id,
692    const ReadDirectorySuccessCallback& success_callback,
693    const ErrorCallback& error_callback,
694    const base::File::Info& file_info) {
695  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
696  DCHECK(task_in_progress_);
697  if (!file_info.is_directory) {
698    return HandleDeviceFileError(error_callback,
699                                 dir_id,
700                                 base::File::FILE_ERROR_NOT_A_DIRECTORY);
701  }
702
703  base::Closure task_closure =
704      base::Bind(&ReadDirectoryOnUIThread,
705                 storage_name_,
706                 dir_id,
707                 base::Bind(&MTPDeviceDelegateImplLinux::OnDidReadDirectory,
708                            weak_ptr_factory_.GetWeakPtr(),
709                            dir_id,
710                            success_callback),
711                 base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
712                            weak_ptr_factory_.GetWeakPtr(),
713                            error_callback,
714                            dir_id));
715  content::BrowserThread::PostTask(content::BrowserThread::UI,
716                                   FROM_HERE,
717                                   task_closure);
718}
719
720void MTPDeviceDelegateImplLinux::OnDidGetFileInfoToCreateSnapshotFile(
721    scoped_ptr<SnapshotRequestInfo> snapshot_request_info,
722    const base::File::Info& file_info) {
723  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
724  DCHECK(!current_snapshot_request_info_.get());
725  DCHECK(snapshot_request_info.get());
726  DCHECK(task_in_progress_);
727  base::File::Error error = base::File::FILE_OK;
728  if (file_info.is_directory)
729    error = base::File::FILE_ERROR_NOT_A_FILE;
730  else if (file_info.size < 0 || file_info.size > kuint32max)
731    error = base::File::FILE_ERROR_FAILED;
732
733  if (error != base::File::FILE_OK)
734    return HandleDeviceFileError(snapshot_request_info->error_callback,
735                                 snapshot_request_info->file_id,
736                                 error);
737
738  base::File::Info snapshot_file_info(file_info);
739  // Modify the last modified time to null. This prevents the time stamp
740  // verfication in LocalFileStreamReader.
741  snapshot_file_info.last_modified = base::Time();
742
743  current_snapshot_request_info_.reset(snapshot_request_info.release());
744  if (file_info.size == 0) {
745    // Empty snapshot file.
746    return OnDidWriteDataIntoSnapshotFile(
747        snapshot_file_info, current_snapshot_request_info_->snapshot_file_path);
748  }
749  WriteDataIntoSnapshotFile(snapshot_file_info);
750}
751
752void MTPDeviceDelegateImplLinux::OnDidReadDirectory(
753    uint32 dir_id,
754    const ReadDirectorySuccessCallback& success_callback,
755    const storage::AsyncFileUtil::EntryList& file_list,
756    bool has_more) {
757  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
758
759  FileIdToMTPFileNodeMap::iterator it = file_id_to_node_map_.find(dir_id);
760  DCHECK(it != file_id_to_node_map_.end());
761  MTPFileNode* dir_node = it->second;
762
763  // Traverse the MTPFileNode tree to reconstuct the full path for |dir_id|.
764  std::deque<std::string> dir_path_parts;
765  MTPFileNode* parent_node = dir_node;
766  while (parent_node->parent()) {
767    dir_path_parts.push_front(parent_node->file_name());
768    parent_node = parent_node->parent();
769  }
770  base::FilePath dir_path = device_path_;
771  for (size_t i = 0; i < dir_path_parts.size(); ++i)
772    dir_path = dir_path.Append(dir_path_parts[i]);
773
774  storage::AsyncFileUtil::EntryList normalized_file_list;
775  for (size_t i = 0; i < file_list.size(); ++i) {
776    normalized_file_list.push_back(file_list[i]);
777    storage::DirectoryEntry& entry = normalized_file_list.back();
778
779    // |entry.name| has the file id encoded in it. Decode here.
780    size_t separator_idx = entry.name.find_last_of(',');
781    DCHECK_NE(std::string::npos, separator_idx);
782    std::string file_id_str = entry.name.substr(separator_idx);
783    file_id_str = file_id_str.substr(1);  // Get rid of the comma.
784    uint32 file_id = 0;
785    bool ret = base::StringToUint(file_id_str, &file_id);
786    DCHECK(ret);
787    entry.name = entry.name.substr(0, separator_idx);
788
789    // Refresh the in memory tree.
790    dir_node->EnsureChildExists(entry.name, file_id);
791    child_nodes_seen_.insert(entry.name);
792
793    // Add to |file_info_cache_|.
794    file_info_cache_[dir_path.Append(entry.name)] = entry;
795  }
796
797  success_callback.Run(normalized_file_list, has_more);
798  if (has_more)
799    return;  // Wait to be called again.
800
801  // Last call, finish book keeping and continue with the next request.
802  dir_node->ClearNonexistentChildren(child_nodes_seen_);
803  child_nodes_seen_.clear();
804  file_info_cache_.clear();
805
806  PendingRequestDone();
807}
808
809void MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile(
810    const base::File::Info& file_info,
811    const base::FilePath& snapshot_file_path) {
812  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
813  DCHECK(current_snapshot_request_info_.get());
814  current_snapshot_request_info_->success_callback.Run(
815      file_info, snapshot_file_path);
816  current_snapshot_request_info_.reset();
817  PendingRequestDone();
818}
819
820void MTPDeviceDelegateImplLinux::OnWriteDataIntoSnapshotFileError(
821    base::File::Error error) {
822  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
823  DCHECK(current_snapshot_request_info_.get());
824  current_snapshot_request_info_->error_callback.Run(error);
825  current_snapshot_request_info_.reset();
826  PendingRequestDone();
827}
828
829void MTPDeviceDelegateImplLinux::OnDidReadBytes(
830    const ReadBytesSuccessCallback& success_callback,
831    const base::File::Info& file_info, int bytes_read) {
832  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
833  success_callback.Run(file_info, bytes_read);
834  PendingRequestDone();
835}
836
837void MTPDeviceDelegateImplLinux::OnDidFillFileCache(
838    const base::FilePath& path,
839    const storage::AsyncFileUtil::EntryList& /* file_list */,
840    bool has_more) {
841  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
842  DCHECK(path.IsParent(pending_tasks_.front().path));
843  if (has_more)
844    return;  // Wait until all entries have been read.
845  pending_tasks_.front().cached_path = path;
846}
847
848void MTPDeviceDelegateImplLinux::OnFillFileCacheFailed(
849    base::File::Error /* error */) {
850  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
851  // When filling the cache fails for the task at the front of the queue, clear
852  // the path of the task so it will not try to do any more caching. Instead,
853  // the task will just run and fail the CachedPathToId() lookup.
854  pending_tasks_.front().path.clear();
855}
856
857void MTPDeviceDelegateImplLinux::HandleDeviceFileError(
858    const ErrorCallback& error_callback,
859    uint32 file_id,
860    base::File::Error error) {
861  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
862
863  FileIdToMTPFileNodeMap::iterator it = file_id_to_node_map_.find(file_id);
864  if (it != file_id_to_node_map_.end()) {
865    MTPFileNode* parent = it->second->parent();
866    if (parent) {
867      bool ret = parent->DeleteChild(file_id);
868      DCHECK(ret);
869    }
870  }
871  error_callback.Run(error);
872  PendingRequestDone();
873}
874
875base::FilePath MTPDeviceDelegateImplLinux::NextUncachedPathComponent(
876    const base::FilePath& path,
877    const base::FilePath& cached_path) const {
878  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
879  DCHECK(cached_path.empty() || cached_path.IsParent(path));
880
881  base::FilePath uncached_path;
882  std::string device_relpath = GetDeviceRelativePath(device_path_, path);
883  if (!device_relpath.empty() && device_relpath != kRootPath) {
884    uncached_path = device_path_;
885    std::vector<std::string> device_relpath_components;
886    base::SplitString(device_relpath, '/', &device_relpath_components);
887    DCHECK(!device_relpath_components.empty());
888    bool all_components_cached = true;
889    const MTPFileNode* current_node = root_node_.get();
890    for (size_t i = 0; i < device_relpath_components.size(); ++i) {
891      current_node = current_node->GetChild(device_relpath_components[i]);
892      if (!current_node) {
893        // With a cache miss, check if it is a genuine failure. If so, pretend
894        // the entire |path| is cached, so there is no further attempt to do
895        // more caching. The actual operation will then fail.
896        all_components_cached =
897            !cached_path.empty() && (uncached_path == cached_path);
898        break;
899      }
900      uncached_path = uncached_path.Append(device_relpath_components[i]);
901    }
902    if (all_components_cached)
903      uncached_path.clear();
904  }
905  return uncached_path;
906}
907
908void MTPDeviceDelegateImplLinux::FillFileCache(
909    const base::FilePath& uncached_path) {
910  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
911  DCHECK(task_in_progress_);
912
913  ReadDirectorySuccessCallback success_callback =
914      base::Bind(&MTPDeviceDelegateImplLinux::OnDidFillFileCache,
915                 weak_ptr_factory_.GetWeakPtr(),
916                 uncached_path);
917  ErrorCallback error_callback =
918      base::Bind(&MTPDeviceDelegateImplLinux::OnFillFileCacheFailed,
919                 weak_ptr_factory_.GetWeakPtr());
920  ReadDirectoryInternal(uncached_path, success_callback, error_callback);
921}
922
923
924bool MTPDeviceDelegateImplLinux::CachedPathToId(const base::FilePath& path,
925                                                uint32* id) const {
926  DCHECK(id);
927
928  std::string device_relpath = GetDeviceRelativePath(device_path_, path);
929  if (device_relpath.empty())
930    return false;
931  std::vector<std::string> device_relpath_components;
932  if (device_relpath != kRootPath)
933    base::SplitString(device_relpath, '/', &device_relpath_components);
934  const MTPFileNode* current_node = root_node_.get();
935  for (size_t i = 0; i < device_relpath_components.size(); ++i) {
936    current_node = current_node->GetChild(device_relpath_components[i]);
937    if (!current_node)
938      return false;
939  }
940  *id = current_node->file_id();
941  return true;
942}
943
944void CreateMTPDeviceAsyncDelegate(
945    const std::string& device_location,
946    const CreateMTPDeviceAsyncDelegateCallback& callback) {
947  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
948  callback.Run(new MTPDeviceDelegateImplLinux(device_location));
949}
950