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 "net/base/directory_lister.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/files/file_enumerator.h"
12#include "base/files/file_util.h"
13#include "base/i18n/file_util_icu.h"
14#include "base/message_loop/message_loop.h"
15#include "base/threading/thread_restrictions.h"
16#include "base/threading/worker_pool.h"
17#include "net/base/net_errors.h"
18
19namespace net {
20
21namespace {
22
23bool IsDotDot(const base::FilePath& path) {
24  return FILE_PATH_LITERAL("..") == path.BaseName().value();
25}
26
27// Comparator for sorting lister results. This uses the locale aware filename
28// comparison function on the filenames for sorting in the user's locale.
29// Static.
30bool CompareAlphaDirsFirst(const DirectoryLister::DirectoryListerData& a,
31                           const DirectoryLister::DirectoryListerData& b) {
32  // Parent directory before all else.
33  if (IsDotDot(a.info.GetName()))
34    return true;
35  if (IsDotDot(b.info.GetName()))
36    return false;
37
38  // Directories before regular files.
39  bool a_is_directory = a.info.IsDirectory();
40  bool b_is_directory = b.info.IsDirectory();
41  if (a_is_directory != b_is_directory)
42    return a_is_directory;
43
44  return base::i18n::LocaleAwareCompareFilenames(a.info.GetName(),
45                                                 b.info.GetName());
46}
47
48bool CompareDate(const DirectoryLister::DirectoryListerData& a,
49                 const DirectoryLister::DirectoryListerData& b) {
50  // Parent directory before all else.
51  if (IsDotDot(a.info.GetName()))
52    return true;
53  if (IsDotDot(b.info.GetName()))
54    return false;
55
56  // Directories before regular files.
57  bool a_is_directory = a.info.IsDirectory();
58  bool b_is_directory = b.info.IsDirectory();
59  if (a_is_directory != b_is_directory)
60    return a_is_directory;
61  return a.info.GetLastModifiedTime() > b.info.GetLastModifiedTime();
62}
63
64// Comparator for sorting find result by paths. This uses the locale-aware
65// comparison function on the filenames for sorting in the user's locale.
66// Static.
67bool CompareFullPath(const DirectoryLister::DirectoryListerData& a,
68                     const DirectoryLister::DirectoryListerData& b) {
69  return base::i18n::LocaleAwareCompareFilenames(a.path, b.path);
70}
71
72void SortData(std::vector<DirectoryLister::DirectoryListerData>* data,
73              DirectoryLister::SortType sort_type) {
74  // Sort the results. See the TODO below (this sort should be removed and we
75  // should do it from JS).
76  if (sort_type == DirectoryLister::DATE)
77    std::sort(data->begin(), data->end(), CompareDate);
78  else if (sort_type == DirectoryLister::FULL_PATH)
79    std::sort(data->begin(), data->end(), CompareFullPath);
80  else if (sort_type == DirectoryLister::ALPHA_DIRS_FIRST)
81    std::sort(data->begin(), data->end(), CompareAlphaDirsFirst);
82  else
83    DCHECK_EQ(DirectoryLister::NO_SORT, sort_type);
84}
85
86}  // namespace
87
88DirectoryLister::DirectoryLister(const base::FilePath& dir,
89                                 DirectoryListerDelegate* delegate)
90    : core_(new Core(dir, false, ALPHA_DIRS_FIRST, this)),
91      delegate_(delegate) {
92  DCHECK(delegate_);
93  DCHECK(!dir.value().empty());
94}
95
96DirectoryLister::DirectoryLister(const base::FilePath& dir,
97                                 bool recursive,
98                                 SortType sort,
99                                 DirectoryListerDelegate* delegate)
100    : core_(new Core(dir, recursive, sort, this)),
101      delegate_(delegate) {
102  DCHECK(delegate_);
103  DCHECK(!dir.value().empty());
104}
105
106DirectoryLister::~DirectoryLister() {
107  Cancel();
108}
109
110bool DirectoryLister::Start() {
111  return core_->Start();
112}
113
114void DirectoryLister::Cancel() {
115  return core_->Cancel();
116}
117
118DirectoryLister::Core::Core(const base::FilePath& dir,
119                            bool recursive,
120                            SortType sort,
121                            DirectoryLister* lister)
122    : dir_(dir),
123      recursive_(recursive),
124      sort_(sort),
125      lister_(lister) {
126  DCHECK(lister_);
127}
128
129DirectoryLister::Core::~Core() {}
130
131bool DirectoryLister::Core::Start() {
132  origin_loop_ = base::MessageLoopProxy::current();
133
134  return base::WorkerPool::PostTask(
135      FROM_HERE, base::Bind(&Core::StartInternal, this), true);
136}
137
138void DirectoryLister::Core::Cancel() {
139  lister_ = NULL;
140}
141
142void DirectoryLister::Core::StartInternal() {
143
144  if (!base::DirectoryExists(dir_)) {
145    origin_loop_->PostTask(
146        FROM_HERE,
147        base::Bind(&DirectoryLister::Core::OnDone, this, ERR_FILE_NOT_FOUND));
148    return;
149  }
150
151  int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
152  if (!recursive_)
153    types |= base::FileEnumerator::INCLUDE_DOT_DOT;
154
155  base::FileEnumerator file_enum(dir_, recursive_, types);
156
157  base::FilePath path;
158  std::vector<DirectoryListerData> file_data;
159  while (lister_ && !(path = file_enum.Next()).empty()) {
160    DirectoryListerData data;
161    data.info = file_enum.GetInfo();
162    data.path = path;
163    file_data.push_back(data);
164
165    /* TODO(brettw) bug 24107: It would be nice to send incremental updates.
166       We gather them all so they can be sorted, but eventually the sorting
167       should be done from JS to give more flexibility in the page. When we do
168       that, we can uncomment this to send incremental updates to the page.
169
170    const int kFilesPerEvent = 8;
171    if (file_data.size() < kFilesPerEvent)
172      continue;
173
174    origin_loop_->PostTask(
175        FROM_HERE,
176        base::Bind(&DirectoryLister::Core::SendData, file_data));
177    file_data.clear();
178    */
179  }
180
181  SortData(&file_data, sort_);
182  origin_loop_->PostTask(
183      FROM_HERE,
184      base::Bind(&DirectoryLister::Core::SendData, this, file_data));
185
186  origin_loop_->PostTask(
187      FROM_HERE,
188      base::Bind(&DirectoryLister::Core::OnDone, this, OK));
189}
190
191void DirectoryLister::Core::SendData(
192    const std::vector<DirectoryLister::DirectoryListerData>& data) {
193  DCHECK(origin_loop_->BelongsToCurrentThread());
194  // We need to check for cancellation (indicated by NULL'ing of |lister_|)
195  // which can happen during each callback.
196  for (size_t i = 0; lister_ && i < data.size(); ++i)
197    lister_->OnReceivedData(data[i]);
198}
199
200void DirectoryLister::Core::OnDone(int error) {
201  DCHECK(origin_loop_->BelongsToCurrentThread());
202  if (lister_)
203    lister_->OnDone(error);
204}
205
206void DirectoryLister::OnReceivedData(const DirectoryListerData& data) {
207  delegate_->OnListFile(data);
208}
209
210void DirectoryLister::OnDone(int error) {
211  delegate_->OnListDone(error);
212}
213
214}  // namespace net
215