• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/file_util.h"
11 #include "base/i18n/file_util_icu.h"
12 #include "base/message_loop.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "net/base/net_errors.h"
16 
17 namespace net {
18 
19 static const int kFilesPerEvent = 8;
20 
21 // A task which is used to signal the delegate asynchronously.
22 class DirectoryDataEvent : public Task {
23 public:
DirectoryDataEvent(DirectoryLister * d)24   explicit DirectoryDataEvent(DirectoryLister* d) : lister(d), error(0) {
25     // Allocations of the FindInfo aren't super cheap, so reserve space.
26     data.reserve(64);
27   }
28 
Run()29   void Run() {
30     if (data.empty()) {
31       lister->OnDone(error);
32       return;
33     }
34     lister->OnReceivedData(&data[0], static_cast<int>(data.size()));
35   }
36 
37   scoped_refptr<DirectoryLister> lister;
38   std::vector<DirectoryLister::DirectoryListerData> data;
39   int error;
40 };
41 
DirectoryLister(const FilePath & dir,DirectoryListerDelegate * delegate)42 DirectoryLister::DirectoryLister(const FilePath& dir,
43                                  DirectoryListerDelegate* delegate)
44     : dir_(dir),
45       recursive_(false),
46       delegate_(delegate),
47       sort_(ALPHA_DIRS_FIRST),
48       message_loop_(NULL),
49       thread_(base::kNullThreadHandle) {
50   DCHECK(!dir.value().empty());
51 }
52 
DirectoryLister(const FilePath & dir,bool recursive,SORT_TYPE sort,DirectoryListerDelegate * delegate)53 DirectoryLister::DirectoryLister(const FilePath& dir,
54                                  bool recursive,
55                                  SORT_TYPE sort,
56                                  DirectoryListerDelegate* delegate)
57     : dir_(dir),
58       recursive_(recursive),
59       delegate_(delegate),
60       sort_(sort),
61       message_loop_(NULL),
62       thread_(base::kNullThreadHandle) {
63   DCHECK(!dir.value().empty());
64 }
65 
Start()66 bool DirectoryLister::Start() {
67   // spawn a thread to enumerate the specified directory
68 
69   // pass events back to the current thread
70   message_loop_ = MessageLoop::current();
71   DCHECK(message_loop_) << "calling thread must have a message loop";
72 
73   AddRef();  // the thread will release us when it is done
74 
75   if (!base::PlatformThread::Create(0, this, &thread_)) {
76     Release();
77     return false;
78   }
79 
80   return true;
81 }
82 
Cancel()83 void DirectoryLister::Cancel() {
84   canceled_.Set();
85 
86   if (thread_) {
87     // This is a bug and we should stop joining this thread.
88     // http://crbug.com/65331
89     base::ThreadRestrictions::ScopedAllowIO allow_io;
90     base::PlatformThread::Join(thread_);
91     thread_ = base::kNullThreadHandle;
92   }
93 }
94 
ThreadMain()95 void DirectoryLister::ThreadMain() {
96   DirectoryDataEvent* e = new DirectoryDataEvent(this);
97 
98   if (!file_util::DirectoryExists(dir_)) {
99     e->error = ERR_FILE_NOT_FOUND;
100     message_loop_->PostTask(FROM_HERE, e);
101     Release();
102     return;
103   }
104 
105   int types = file_util::FileEnumerator::FILES |
106               file_util::FileEnumerator::DIRECTORIES;
107   if (!recursive_)
108     types |= file_util::FileEnumerator::INCLUDE_DOT_DOT;
109 
110   file_util::FileEnumerator file_enum(dir_, recursive_,
111       static_cast<file_util::FileEnumerator::FILE_TYPE>(types));
112 
113   FilePath path;
114   while (!canceled_.IsSet() && !(path = file_enum.Next()).empty()) {
115     DirectoryListerData data;
116     file_enum.GetFindInfo(&data.info);
117     data.path = path;
118     e->data.push_back(data);
119 
120     /* TODO(brettw) bug 24107: It would be nice to send incremental updates.
121        We gather them all so they can be sorted, but eventually the sorting
122        should be done from JS to give more flexibility in the page. When we do
123        that, we can uncomment this to send incremental updates to the page.
124     if (++e->count == kFilesPerEvent) {
125       message_loop_->PostTask(FROM_HERE, e);
126       e = new DirectoryDataEvent(this);
127     }
128     */
129   }
130 
131   if (!e->data.empty()) {
132     // Sort the results. See the TODO above (this sort should be removed and we
133     // should do it from JS).
134     if (sort_ == DATE)
135       std::sort(e->data.begin(), e->data.end(), CompareDate);
136     else if (sort_ == FULL_PATH)
137       std::sort(e->data.begin(), e->data.end(), CompareFullPath);
138     else if (sort_ == ALPHA_DIRS_FIRST)
139       std::sort(e->data.begin(), e->data.end(), CompareAlphaDirsFirst);
140     else
141       DCHECK_EQ(NO_SORT, sort_);
142 
143     message_loop_->PostTask(FROM_HERE, e);
144     e = new DirectoryDataEvent(this);
145   }
146 
147   // Notify done
148   Release();
149   message_loop_->PostTask(FROM_HERE, e);
150 }
151 
~DirectoryLister()152 DirectoryLister::~DirectoryLister() {
153   if (thread_) {
154     // This is a bug and we should stop joining this thread.
155     // http://crbug.com/65331
156     base::ThreadRestrictions::ScopedAllowIO allow_io;
157     base::PlatformThread::Join(thread_);
158   }
159 }
160 
161 // Comparator for sorting lister results. This uses the locale aware filename
162 // comparison function on the filenames for sorting in the user's locale.
163 // Static.
CompareAlphaDirsFirst(const DirectoryListerData & a,const DirectoryListerData & b)164 bool DirectoryLister::CompareAlphaDirsFirst(const DirectoryListerData& a,
165                                             const DirectoryListerData& b) {
166   // Parent directory before all else.
167   if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(a.info)))
168     return true;
169   if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(b.info)))
170     return false;
171 
172   // Directories before regular files.
173   bool a_is_directory = file_util::FileEnumerator::IsDirectory(a.info);
174   bool b_is_directory = file_util::FileEnumerator::IsDirectory(b.info);
175   if (a_is_directory != b_is_directory)
176     return a_is_directory;
177 
178   return file_util::LocaleAwareCompareFilenames(
179       file_util::FileEnumerator::GetFilename(a.info),
180       file_util::FileEnumerator::GetFilename(b.info));
181 }
182 
183 // Static.
CompareDate(const DirectoryListerData & a,const DirectoryListerData & b)184 bool DirectoryLister::CompareDate(const DirectoryListerData& a,
185                                   const DirectoryListerData& b) {
186   // Parent directory before all else.
187   if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(a.info)))
188     return true;
189   if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(b.info)))
190     return false;
191 
192   // Directories before regular files.
193   bool a_is_directory = file_util::FileEnumerator::IsDirectory(a.info);
194   bool b_is_directory = file_util::FileEnumerator::IsDirectory(b.info);
195   if (a_is_directory != b_is_directory)
196     return a_is_directory;
197 #if defined(OS_POSIX)
198   return a.info.stat.st_mtime > b.info.stat.st_mtime;
199 #elif defined(OS_WIN)
200   if (a.info.ftLastWriteTime.dwHighDateTime ==
201       b.info.ftLastWriteTime.dwHighDateTime) {
202     return a.info.ftLastWriteTime.dwLowDateTime >
203            b.info.ftLastWriteTime.dwLowDateTime;
204   } else {
205     return a.info.ftLastWriteTime.dwHighDateTime >
206            b.info.ftLastWriteTime.dwHighDateTime;
207   }
208 #endif
209 }
210 
211 // Comparator for sorting find result by paths. This uses the locale-aware
212 // comparison function on the filenames for sorting in the user's locale.
213 // Static.
CompareFullPath(const DirectoryListerData & a,const DirectoryListerData & b)214 bool DirectoryLister::CompareFullPath(const DirectoryListerData& a,
215                                       const DirectoryListerData& b) {
216   return file_util::LocaleAwareCompareFilenames(a.path, b.path);
217 }
218 
OnReceivedData(const DirectoryListerData * data,int count)219 void DirectoryLister::OnReceivedData(const DirectoryListerData* data,
220                                      int count) {
221   // Since the delegate can clear itself during the OnListFile callback, we
222   // need to null check it during each iteration of the loop.  Similarly, it is
223   // necessary to check the canceled_ flag to avoid sending data to a delegate
224   // who doesn't want anymore.
225   for (int i = 0; !canceled_.IsSet() && delegate_ && i < count; ++i)
226     delegate_->OnListFile(data[i]);
227 }
228 
OnDone(int error)229 void DirectoryLister::OnDone(int error) {
230   // If canceled is set, we need to report some kind of error,
231   // but don't overwrite the error condition if it is already set.
232   if (!error && canceled_.IsSet())
233     error = ERR_ABORTED;
234 
235   if (delegate_)
236     delegate_->OnListDone(error);
237 }
238 
239 }  // namespace net
240