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: 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 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 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 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 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 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 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 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. 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. 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. 214 bool DirectoryLister::CompareFullPath(const DirectoryListerData& a, 215 const DirectoryListerData& b) { 216 return file_util::LocaleAwareCompareFilenames(a.path, b.path); 217 } 218 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 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