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