OLD | NEW |
| (Empty) |
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 | |
9 #include "base/bind.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" | |
18 | |
19 namespace net { | |
20 | |
21 namespace { | |
22 | |
23 bool 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. | |
30 bool 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 | |
48 bool 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. | |
67 bool CompareFullPath(const DirectoryLister::DirectoryListerData& a, | |
68 const DirectoryLister::DirectoryListerData& b) { | |
69 return base::i18n::LocaleAwareCompareFilenames(a.path, b.path); | |
70 } | |
71 | |
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); | |
82 } else { | |
83 DCHECK_EQ(DirectoryLister::NO_SORT, sort_type); | |
84 } | |
85 } | |
86 | |
87 } // namespace | |
88 | |
89 DirectoryLister::DirectoryLister(const base::FilePath& dir, | |
90 DirectoryListerDelegate* delegate) | |
91 : delegate_(delegate) { | |
92 core_ = new Core(dir, false, ALPHA_DIRS_FIRST, this); | |
93 DCHECK(delegate_); | |
94 DCHECK(!dir.value().empty()); | |
95 } | |
96 | |
97 DirectoryLister::DirectoryLister(const base::FilePath& dir, | |
98 bool recursive, | |
99 SortType sort, | |
100 DirectoryListerDelegate* delegate) | |
101 : delegate_(delegate) { | |
102 core_ = new Core(dir, recursive, sort, this); | |
103 DCHECK(delegate_); | |
104 DCHECK(!dir.value().empty()); | |
105 } | |
106 | |
107 DirectoryLister::~DirectoryLister() { | |
108 Cancel(); | |
109 } | |
110 | |
111 bool DirectoryLister::Start() { | |
112 return base::WorkerPool::PostTask( | |
113 FROM_HERE, | |
114 base::Bind(&Core::Start, core_), | |
115 true); | |
116 } | |
117 | |
118 void DirectoryLister::Cancel() { | |
119 core_->CancelOnOriginThread(); | |
120 } | |
121 | |
122 DirectoryLister::Core::Core(const base::FilePath& dir, | |
123 bool recursive, | |
124 SortType sort, | |
125 DirectoryLister* lister) | |
126 : dir_(dir), | |
127 recursive_(recursive), | |
128 sort_(sort), | |
129 origin_loop_(base::MessageLoopProxy::current()), | |
130 lister_(lister), | |
131 cancelled_(0) { | |
132 DCHECK(lister_); | |
133 } | |
134 | |
135 DirectoryLister::Core::~Core() {} | |
136 | |
137 void DirectoryLister::Core::CancelOnOriginThread() { | |
138 DCHECK(origin_loop_->BelongsToCurrentThread()); | |
139 | |
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 | |
143 // cause a crash. | |
144 lister_ = nullptr; | |
145 } | |
146 | |
147 void DirectoryLister::Core::Start() { | |
148 scoped_ptr<DirectoryList> directory_list(new DirectoryList()); | |
149 | |
150 if (!base::DirectoryExists(dir_)) { | |
151 origin_loop_->PostTask( | |
152 FROM_HERE, | |
153 base::Bind(&Core::DoneOnOriginThread, this, | |
154 base::Passed(directory_list.Pass()), ERR_FILE_NOT_FOUND)); | |
155 return; | |
156 } | |
157 | |
158 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES; | |
159 if (!recursive_) | |
160 types |= base::FileEnumerator::INCLUDE_DOT_DOT; | |
161 | |
162 base::FileEnumerator file_enum(dir_, recursive_, types); | |
163 | |
164 base::FilePath path; | |
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. | |
168 if (IsCancelled()) | |
169 return; | |
170 | |
171 DirectoryListerData data; | |
172 data.info = file_enum.GetInfo(); | |
173 data.path = path; | |
174 directory_list->push_back(data); | |
175 | |
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. | |
180 | |
181 const int kFilesPerEvent = 8; | |
182 if (file_data.size() < kFilesPerEvent) | |
183 continue; | |
184 | |
185 origin_loop_->PostTask( | |
186 FROM_HERE, | |
187 base::Bind(&DirectoryLister::Core::SendData, file_data)); | |
188 file_data.clear(); | |
189 */ | |
190 } | |
191 | |
192 SortData(directory_list.get(), sort_); | |
193 | |
194 origin_loop_->PostTask( | |
195 FROM_HERE, | |
196 base::Bind(&Core::DoneOnOriginThread, this, | |
197 base::Passed(directory_list.Pass()), OK)); | |
198 } | |
199 | |
200 bool DirectoryLister::Core::IsCancelled() const { | |
201 return !!base::subtle::NoBarrier_Load(&cancelled_); | |
202 } | |
203 | |
204 void DirectoryLister::Core::DoneOnOriginThread( | |
205 scoped_ptr<DirectoryList> directory_list, int error) const { | |
206 DCHECK(origin_loop_->BelongsToCurrentThread()); | |
207 | |
208 // Need to check if the operation was before first callback. | |
209 if (IsCancelled()) | |
210 return; | |
211 | |
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. | |
215 if (IsCancelled()) | |
216 return; | |
217 } | |
218 lister_->OnListDone(error); | |
219 } | |
220 | |
221 void DirectoryLister::OnListFile(const DirectoryListerData& data) { | |
222 delegate_->OnListFile(data); | |
223 } | |
224 | |
225 void DirectoryLister::OnListDone(int error) { | |
226 delegate_->OnListDone(error); | |
227 } | |
228 | |
229 } // namespace net | |
OLD | NEW |