Home | History | Annotate | Download | only in base
      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