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.
5 #include "net/base/directory_lister.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_util.h"
12 #include "base/i18n/file_util_icu.h"
13 #include "base/logging.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"
23 bool IsDotDot(const base::FilePath
& path
) {
24 return FILE_PATH_LITERAL("..") == path
.BaseName().value();
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.
30 bool CompareAlphaDirsFirst(const DirectoryLister::DirectoryListerData
& a
,
31 const DirectoryLister::DirectoryListerData
& b
) {
32 // Parent directory before all else.
33 if (IsDotDot(a
.info
.GetName()))
35 if (IsDotDot(b
.info
.GetName()))
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
;
44 return base::i18n::LocaleAwareCompareFilenames(a
.info
.GetName(),
48 bool CompareDate(const DirectoryLister::DirectoryListerData
& a
,
49 const DirectoryLister::DirectoryListerData
& b
) {
50 // Parent directory before all else.
51 if (IsDotDot(a
.info
.GetName()))
53 if (IsDotDot(b
.info
.GetName()))
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();
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.
67 bool CompareFullPath(const DirectoryLister::DirectoryListerData
& a
,
68 const DirectoryLister::DirectoryListerData
& b
) {
69 return base::i18n::LocaleAwareCompareFilenames(a
.path
, b
.path
);
72 void 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
);
83 DCHECK_EQ(DirectoryLister::NO_SORT
, sort_type
);
89 DirectoryLister::DirectoryLister(const base::FilePath
& dir
,
90 DirectoryListerDelegate
* delegate
)
91 : delegate_(delegate
) {
92 core_
= new Core(dir
, false, ALPHA_DIRS_FIRST
, this);
94 DCHECK(!dir
.value().empty());
97 DirectoryLister::DirectoryLister(const base::FilePath
& dir
,
100 DirectoryListerDelegate
* delegate
)
101 : delegate_(delegate
) {
102 core_
= new Core(dir
, recursive
, sort
, this);
104 DCHECK(!dir
.value().empty());
107 DirectoryLister::~DirectoryLister() {
111 bool DirectoryLister::Start() {
112 return base::WorkerPool::PostTask(
114 base::Bind(&Core::Start
, core_
),
118 void DirectoryLister::Cancel() {
119 core_
->CancelOnOriginThread();
122 DirectoryLister::Core::Core(const base::FilePath
& dir
,
125 DirectoryLister
* lister
)
127 recursive_(recursive
),
129 origin_loop_(base::MessageLoopProxy::current()),
135 DirectoryLister::Core::~Core() {}
137 void DirectoryLister::Core::CancelOnOriginThread() {
138 DCHECK(origin_loop_
->BelongsToCurrentThread());
140 base::subtle::NoBarrier_Store(&cancelled_
, 1);
141 // Core must not call into |lister_| after cancellation, as the |lister_| may
142 // have been destroyed. Setting |lister_| to NULL ensures any such access will
147 void DirectoryLister::Core::Start() {
148 scoped_ptr
<DirectoryList
> directory_list(new DirectoryList());
150 if (!base::DirectoryExists(dir_
)) {
151 origin_loop_
->PostTask(
153 base::Bind(&Core::DoneOnOriginThread
, this,
154 base::Passed(directory_list
.Pass()), ERR_FILE_NOT_FOUND
));
158 int types
= base::FileEnumerator::FILES
| base::FileEnumerator::DIRECTORIES
;
160 types
|= base::FileEnumerator::INCLUDE_DOT_DOT
;
162 base::FileEnumerator
file_enum(dir_
, recursive_
, types
);
165 while (!(path
= file_enum
.Next()).empty()) {
166 // Abort on cancellation. This is purely for performance reasons.
167 // Correctness guarantees are made by checks in DoneOnOriginThread.
171 DirectoryListerData data
;
172 data
.info
= file_enum
.GetInfo();
174 directory_list
->push_back(data
);
176 /* TODO(brettw) bug 24107: It would be nice to send incremental updates.
177 We gather them all so they can be sorted, but eventually the sorting
178 should be done from JS to give more flexibility in the page. When we do
179 that, we can uncomment this to send incremental updates to the page.
181 const int kFilesPerEvent = 8;
182 if (file_data.size() < kFilesPerEvent)
185 origin_loop_->PostTask(
187 base::Bind(&DirectoryLister::Core::SendData, file_data));
192 SortData(directory_list
.get(), sort_
);
194 origin_loop_
->PostTask(
196 base::Bind(&Core::DoneOnOriginThread
, this,
197 base::Passed(directory_list
.Pass()), OK
));
200 bool DirectoryLister::Core::IsCancelled() const {
201 return !!base::subtle::NoBarrier_Load(&cancelled_
);
204 void DirectoryLister::Core::DoneOnOriginThread(
205 scoped_ptr
<DirectoryList
> directory_list
, int error
) const {
206 DCHECK(origin_loop_
->BelongsToCurrentThread());
208 // Need to check if the operation was before first callback.
212 for (const auto& lister_data
: *directory_list
) {
213 lister_
->OnListFile(lister_data
);
214 // Need to check if the operation was cancelled during the callback.
218 lister_
->OnListDone(error
);
221 void DirectoryLister::OnListFile(const DirectoryListerData
& data
) {
222 delegate_
->OnListFile(data
);
225 void DirectoryLister::OnListDone(int error
) {
226 delegate_
->OnListDone(error
);