blob: 621461f15330488db5a266848d0235e2e608d53a [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/base/directory_lister.h"
#include <algorithm>
#include <vector>
#include "base/file_util.h"
#include "base/i18n/file_util_icu.h"
#include "base/message_loop.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "net/base/net_errors.h"
namespace net {
static const int kFilesPerEvent = 8;
// A task which is used to signal the delegate asynchronously.
class DirectoryDataEvent : public Task {
public:
explicit DirectoryDataEvent(DirectoryLister* d) : lister(d), error(0) {
// Allocations of the FindInfo aren't super cheap, so reserve space.
data.reserve(64);
}
void Run() {
if (data.empty()) {
lister->OnDone(error);
return;
}
lister->OnReceivedData(&data[0], static_cast<int>(data.size()));
}
scoped_refptr<DirectoryLister> lister;
std::vector<DirectoryLister::DirectoryListerData> data;
int error;
};
DirectoryLister::DirectoryLister(const FilePath& dir,
DirectoryListerDelegate* delegate)
: dir_(dir),
recursive_(false),
delegate_(delegate),
sort_(ALPHA_DIRS_FIRST),
message_loop_(NULL),
thread_(base::kNullThreadHandle) {
DCHECK(!dir.value().empty());
}
DirectoryLister::DirectoryLister(const FilePath& dir,
bool recursive,
SORT_TYPE sort,
DirectoryListerDelegate* delegate)
: dir_(dir),
recursive_(recursive),
delegate_(delegate),
sort_(sort),
message_loop_(NULL),
thread_(base::kNullThreadHandle) {
DCHECK(!dir.value().empty());
}
bool DirectoryLister::Start() {
// spawn a thread to enumerate the specified directory
// pass events back to the current thread
message_loop_ = MessageLoop::current();
DCHECK(message_loop_) << "calling thread must have a message loop";
AddRef(); // the thread will release us when it is done
if (!base::PlatformThread::Create(0, this, &thread_)) {
Release();
return false;
}
return true;
}
void DirectoryLister::Cancel() {
canceled_.Set();
if (thread_) {
// This is a bug and we should stop joining this thread.
// http://crbug.com/65331
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::PlatformThread::Join(thread_);
thread_ = base::kNullThreadHandle;
}
}
void DirectoryLister::ThreadMain() {
DirectoryDataEvent* e = new DirectoryDataEvent(this);
if (!file_util::DirectoryExists(dir_)) {
e->error = ERR_FILE_NOT_FOUND;
message_loop_->PostTask(FROM_HERE, e);
Release();
return;
}
int types = file_util::FileEnumerator::FILES |
file_util::FileEnumerator::DIRECTORIES;
if (!recursive_)
types |= file_util::FileEnumerator::INCLUDE_DOT_DOT;
file_util::FileEnumerator file_enum(dir_, recursive_,
static_cast<file_util::FileEnumerator::FILE_TYPE>(types));
FilePath path;
while (!canceled_.IsSet() && !(path = file_enum.Next()).empty()) {
DirectoryListerData data;
file_enum.GetFindInfo(&data.info);
data.path = path;
e->data.push_back(data);
/* TODO(brettw) bug 24107: It would be nice to send incremental updates.
We gather them all so they can be sorted, but eventually the sorting
should be done from JS to give more flexibility in the page. When we do
that, we can uncomment this to send incremental updates to the page.
if (++e->count == kFilesPerEvent) {
message_loop_->PostTask(FROM_HERE, e);
e = new DirectoryDataEvent(this);
}
*/
}
if (!e->data.empty()) {
// Sort the results. See the TODO above (this sort should be removed and we
// should do it from JS).
if (sort_ == DATE)
std::sort(e->data.begin(), e->data.end(), CompareDate);
else if (sort_ == FULL_PATH)
std::sort(e->data.begin(), e->data.end(), CompareFullPath);
else if (sort_ == ALPHA_DIRS_FIRST)
std::sort(e->data.begin(), e->data.end(), CompareAlphaDirsFirst);
else
DCHECK_EQ(NO_SORT, sort_);
message_loop_->PostTask(FROM_HERE, e);
e = new DirectoryDataEvent(this);
}
// Notify done
Release();
message_loop_->PostTask(FROM_HERE, e);
}
DirectoryLister::~DirectoryLister() {
if (thread_) {
// This is a bug and we should stop joining this thread.
// http://crbug.com/65331
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::PlatformThread::Join(thread_);
}
}
// Comparator for sorting lister results. This uses the locale aware filename
// comparison function on the filenames for sorting in the user's locale.
// Static.
bool DirectoryLister::CompareAlphaDirsFirst(const DirectoryListerData& a,
const DirectoryListerData& b) {
// Parent directory before all else.
if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(a.info)))
return true;
if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(b.info)))
return false;
// Directories before regular files.
bool a_is_directory = file_util::FileEnumerator::IsDirectory(a.info);
bool b_is_directory = file_util::FileEnumerator::IsDirectory(b.info);
if (a_is_directory != b_is_directory)
return a_is_directory;
return file_util::LocaleAwareCompareFilenames(
file_util::FileEnumerator::GetFilename(a.info),
file_util::FileEnumerator::GetFilename(b.info));
}
// Static.
bool DirectoryLister::CompareDate(const DirectoryListerData& a,
const DirectoryListerData& b) {
// Parent directory before all else.
if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(a.info)))
return true;
if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(b.info)))
return false;
// Directories before regular files.
bool a_is_directory = file_util::FileEnumerator::IsDirectory(a.info);
bool b_is_directory = file_util::FileEnumerator::IsDirectory(b.info);
if (a_is_directory != b_is_directory)
return a_is_directory;
#if defined(OS_POSIX)
return a.info.stat.st_mtime > b.info.stat.st_mtime;
#elif defined(OS_WIN)
if (a.info.ftLastWriteTime.dwHighDateTime ==
b.info.ftLastWriteTime.dwHighDateTime) {
return a.info.ftLastWriteTime.dwLowDateTime >
b.info.ftLastWriteTime.dwLowDateTime;
} else {
return a.info.ftLastWriteTime.dwHighDateTime >
b.info.ftLastWriteTime.dwHighDateTime;
}
#endif
}
// Comparator for sorting find result by paths. This uses the locale-aware
// comparison function on the filenames for sorting in the user's locale.
// Static.
bool DirectoryLister::CompareFullPath(const DirectoryListerData& a,
const DirectoryListerData& b) {
return file_util::LocaleAwareCompareFilenames(a.path, b.path);
}
void DirectoryLister::OnReceivedData(const DirectoryListerData* data,
int count) {
// Since the delegate can clear itself during the OnListFile callback, we
// need to null check it during each iteration of the loop. Similarly, it is
// necessary to check the canceled_ flag to avoid sending data to a delegate
// who doesn't want anymore.
for (int i = 0; !canceled_.IsSet() && delegate_ && i < count; ++i)
delegate_->OnListFile(data[i]);
}
void DirectoryLister::OnDone(int error) {
// If canceled is set, we need to report some kind of error,
// but don't overwrite the error condition if it is already set.
if (!error && canceled_.IsSet())
error = ERR_ABORTED;
if (delegate_)
delegate_->OnListDone(error);
}
} // namespace net