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/files/file_enumerator.h" 12 #include "base/files/file_util.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 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 } // namespace 87 88 DirectoryLister::DirectoryLister(const base::FilePath& dir, 89 DirectoryListerDelegate* delegate) 90 : core_(new Core(dir, false, ALPHA_DIRS_FIRST, this)), 91 delegate_(delegate) { 92 DCHECK(delegate_); 93 DCHECK(!dir.value().empty()); 94 } 95 96 DirectoryLister::DirectoryLister(const base::FilePath& dir, 97 bool recursive, 98 SortType sort, 99 DirectoryListerDelegate* delegate) 100 : core_(new Core(dir, recursive, sort, this)), 101 delegate_(delegate) { 102 DCHECK(delegate_); 103 DCHECK(!dir.value().empty()); 104 } 105 106 DirectoryLister::~DirectoryLister() { 107 Cancel(); 108 } 109 110 bool DirectoryLister::Start() { 111 return core_->Start(); 112 } 113 114 void DirectoryLister::Cancel() { 115 return core_->Cancel(); 116 } 117 118 DirectoryLister::Core::Core(const base::FilePath& dir, 119 bool recursive, 120 SortType sort, 121 DirectoryLister* lister) 122 : dir_(dir), 123 recursive_(recursive), 124 sort_(sort), 125 lister_(lister) { 126 DCHECK(lister_); 127 } 128 129 DirectoryLister::Core::~Core() {} 130 131 bool DirectoryLister::Core::Start() { 132 origin_loop_ = base::MessageLoopProxy::current(); 133 134 return base::WorkerPool::PostTask( 135 FROM_HERE, base::Bind(&Core::StartInternal, this), true); 136 } 137 138 void DirectoryLister::Core::Cancel() { 139 lister_ = NULL; 140 } 141 142 void DirectoryLister::Core::StartInternal() { 143 144 if (!base::DirectoryExists(dir_)) { 145 origin_loop_->PostTask( 146 FROM_HERE, 147 base::Bind(&DirectoryLister::Core::OnDone, this, ERR_FILE_NOT_FOUND)); 148 return; 149 } 150 151 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES; 152 if (!recursive_) 153 types |= base::FileEnumerator::INCLUDE_DOT_DOT; 154 155 base::FileEnumerator file_enum(dir_, recursive_, types); 156 157 base::FilePath path; 158 std::vector<DirectoryListerData> file_data; 159 while (lister_ && !(path = file_enum.Next()).empty()) { 160 DirectoryListerData data; 161 data.info = file_enum.GetInfo(); 162 data.path = path; 163 file_data.push_back(data); 164 165 /* TODO(brettw) bug 24107: It would be nice to send incremental updates. 166 We gather them all so they can be sorted, but eventually the sorting 167 should be done from JS to give more flexibility in the page. When we do 168 that, we can uncomment this to send incremental updates to the page. 169 170 const int kFilesPerEvent = 8; 171 if (file_data.size() < kFilesPerEvent) 172 continue; 173 174 origin_loop_->PostTask( 175 FROM_HERE, 176 base::Bind(&DirectoryLister::Core::SendData, file_data)); 177 file_data.clear(); 178 */ 179 } 180 181 SortData(&file_data, sort_); 182 origin_loop_->PostTask( 183 FROM_HERE, 184 base::Bind(&DirectoryLister::Core::SendData, this, file_data)); 185 186 origin_loop_->PostTask( 187 FROM_HERE, 188 base::Bind(&DirectoryLister::Core::OnDone, this, OK)); 189 } 190 191 void DirectoryLister::Core::SendData( 192 const std::vector<DirectoryLister::DirectoryListerData>& data) { 193 DCHECK(origin_loop_->BelongsToCurrentThread()); 194 // We need to check for cancellation (indicated by NULL'ing of |lister_|) 195 // which can happen during each callback. 196 for (size_t i = 0; lister_ && i < data.size(); ++i) 197 lister_->OnReceivedData(data[i]); 198 } 199 200 void DirectoryLister::Core::OnDone(int error) { 201 DCHECK(origin_loop_->BelongsToCurrentThread()); 202 if (lister_) 203 lister_->OnDone(error); 204 } 205 206 void DirectoryLister::OnReceivedData(const DirectoryListerData& data) { 207 delegate_->OnListFile(data); 208 } 209 210 void DirectoryLister::OnDone(int error) { 211 delegate_->OnListDone(error); 212 } 213 214 } // namespace net 215