I'm working on wrapping up the ugly innards of the FindFirstFile
/FindNextFile
loop (though my question applies to other similar APIs, such as RegEnumKeyEx
or RegEnumValue
, etc.) inside iterators that work in a manner similar to the Standard Template Library's istream_iterator
s.
I have two problems here. The first is with the termination condition of most "foreach" style loops. STL style iterators typically use operator!=
inside the exit condition of the for, i.e.
std::vector<int> test;
for(std::vector<int>::iterator it = test.begin(); it != test.end(); it++) {
//Do stuff
}
My problem is I'm unsure how to implement operator!=
with such a directory enumeration, because I do not know when the enumeration is complete until I've actually finished with it. I have sort of a hack together solution in place now that enumerates the entire directory at once, where each iterator simply tracks a reference counted vector, but this seems like a kludge which can be done a better way.
The second problem I have is that there are multiple pieces of data returned by the FindXFile APIs. For that reason, there's no obvious way to overload operator*
as required for iterator semantics. When I overload that item, do I return the file name? The size? The modified date? How might I convey the multiple pieces of data to which such an iterator must refer to later in an ideomatic way? I've tried ripping off the C# style MoveNext
design but I'm concerned about not following the standard idioms here.
class SomeIterator {
public:
bool next(); //Advances the iterator and returns true if successful, false if the iterator is at the end.
std::wstring fileName() const;
//other kinds of data....
};
EDIT: And the caller would look like:
SomeIterator x = ??; //Construct somehow
while(x.next()) {
//Do stuff
}
Thanks!
Billy3
EDIT2: I have fixed some bugs and written some tests.
Implementation:
#pragma once
#include <queue>
#include <string>
#include <boost/noncopyable.hpp>
#include <boost/make_shared.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include "../Exception.hpp"
namespace WindowsAPI { namespace FileSystem {
template <typename Filter_T = AllResults, typename Recurse_T = NonRecursiveEnumeration>
class DirectoryIterator;
//For unit testing
struct RealFindXFileFunctions
{
static HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) {
return FindFirstFile(lpFileName, lpFindFileData);
};
static BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData) {
return FindNextFile(hFindFile, lpFindFileData);
};
static BOOL Close(HANDLE hFindFile) {
return FindClose(hFindFile);
};
};
inline std::wstring::const_iterator GetLastSlash(std::wstring const&pathSpec) {
return std::find(pathSpec.rbegin(), pathSpec.rend(), L'\').base();
}
class Win32FindData {
WIN32_FIND_DATA internalData;
std::wstring rootPath;
public:
Win32FindData(const std::wstring& root, const WIN32_FIND_DATA& data) :
rootPath(root), internalData(data) {};
DWORD GetAttributes() const {
return internalData.dwFileAttributes;
};
bool IsDirectory() const {
return (internalData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
};
bool IsFile() const {
return !IsDirectory();
};
unsigned __int64 GetSize() const {
ULARGE_INTEGER intValue;
intValue.LowPart = internalData.nFileSizeLow;
intValue.HighPart = internalData.nFileSizeHigh;
return intValue.QuadPart;
};
std::wstring GetFolderPath() const {
return rootPath;
};
std::wstring GetFileName() const {
return internalData.cFileName;
};
std::wstring GetFullFileName() const {
return rootPath + L"" + internalData.cFileName;
};
std::wstring GetShortFileName() const {
return internalData.cAlternateFileName;
};
FILETIME GetCreationTime() const {
return internalData.ftCreationTime;
};
FILETIME GetLastAccessTime() const {
return internalData.ftLastAccessTime;
};
FILETIME GetLastWriteTime() const {
return internalData.ftLastWriteTime;
};
};
template <typename FindXFileFunctions_T>
class BasicNonRecursiveEnumeration : public boost::noncopyable
{
WIN32_FIND_DATAW currentData;
HANDLE hFind;
std::wstring currentDirectory;
void IncrementCurrentDirectory() {
if (hFind == INVALID_HANDLE_VALUE) return;
BOOL success =
FindXFileFunctions_T::FindNext(hFind, ¤tData);
if (success)
return;
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_FILES) {
FindXFileFunctions_T::Close(hFind);
hFind = INVALID_HANDLE_VALUE;
} else {
WindowsApiException::Throw(error);
}
};
bool IsValidDotDirectory()
{
return !Valid() &&
(!wcscmp(currentData.cFileName, L".") || !wcscmp(currentData.cFileName, L".."));
};
void IncrementPastDotDirectories() {
while (IsValidDotDirectory()) {
IncrementCurrentDirectory();
}
};
void PerformFindFirstFile(std::wstring const&pathSpec)
{
hFind = FindXFileFunctions_T::FindFirst(pathSpec.c_str(), ¤tData);
if (Valid()
&& GetLastError() != ERROR_PATH_NOT_FOUND
&& GetLastError() != ERROR_FILE_NOT_FOUND)
WindowsApiException::ThrowFromLastError();
};
public:
BasicNonRecursiveEnumeration() : hFind(INVALID_HANDLE_VALUE) {};
BasicNonRecursiveEnumeration(const std::wstring& pathSpec) :
hFind(INVALID_HANDLE_VALUE) {
std::wstring::const_iterator lastSlash = GetLastSlash(pathSpec);
if (lastSlash != pathSpec.begin())
currentDirectory.assign(pathSpec.begin(), lastSlash-1);
PerformFindFirstFile(pathSpec);
IncrementPastDotDirectories();
};
bool equal(const BasicNonRecursiveEnumeration<FindXFileFunctions_T>& other) const {
if (this == &other)
return true;
return hFind == other.hFind;
};
Win32FindData dereference() {
return Win32FindData(currentDirectory, currentData);
};
void increment() {
IncrementCurrentDirectory();
};
bool Valid() {
return hFind == INVALID_HANDLE_VALUE;
};
virtual ~BasicNonRecursiveEnumeration() {
if (!Valid())
FindXFileFunctions_T::Close(hFind);
};
};
typedef BasicNonRecursiveEnumeration<RealFindXFileFunctions> NonRecursiveEnumeration;
template <typename FindXFileFunctions_T>
class BasicRecursiveEnumeration : public boost::noncopyable
{
std::wstring fileSpec;
std::deque<std::deque<Win32FindData> > enumeratedData;
void EnumerateDirectory(const std::wstring& nextPathSpec) {
std::deque<Win32FindData> newDeck;
BasicNonRecursiveEnumeration<FindXFileFunctions_T> begin(nextPathSpec), end;
for(; !begin.equal(end); begin.increment()) {
newDeck.push_back(begin.dereference());
}
if (!newDeck.empty()) {
enumeratedData.push_back(std::deque<Win32FindData>()); //Swaptimization
enumeratedData.back().swap(newDeck);
}
};
void PerformIncrement() {
if (enumeratedData.empty()) return;
if (enumeratedData.back().front().IsDirectory()) {
std::wstring nextSpec(enumeratedData.back().front().GetFullFileName());
nextSpec.append(L"\*");
enumeratedData.back().pop_front();
EnumerateDirectory(nextSpec);
} else {
enumeratedData.back().pop_front();
}
while (Valid() && enumeratedData.back().empty())
enumeratedData.pop_back();
}
bool CurrentPositionNoMatchFileSpec() const
{
return !enumeratedData.empty() && !PathMatchSpecW(enumeratedData.back().front().GetFileName().c_str(), fileSpec.c_str());
}
public:
BasicRecursiveEnumeration() {};
BasicRecursiveEnumeration(const std::wstring& pathSpec) {
std::wstring::const_iterator lastSlash = GetLastSlash(pathSpec);
if (lastSlash == pathSpec.begin()) {
fileSpec = pathSpec;
EnumerateDirectory(L"*");
} else {
fileSpec.assign(lastSlash, pathSpec.end());
std::wstring firstQuery(pathSpec.begin(), lastSlash);
firstQuery.push_back(L'*');
EnumerateDirectory(firstQuery);
while (CurrentPositionNoMatchFileSpec())
PerformIncrement();
}
};
void increment() {
do
{
PerformIncrement();
} while (CurrentPositionNoMatchFileSpec());
};
bool equal(const BasicRecursiveEnumeration<FindXFileFunctions_T>& other) const {
if (!Valid())
return !other.Valid();
if (!other.Valid())
return false;
return this == &other;
};
Win32FindData dereference() const {
return enumeratedData.back().front();
};
bool Valid() const {
return !enumeratedData.empty();
};
};
typedef BasicRecursiveEnumeration<RealFindXFileFunctions> RecursiveEnumeration;
struct AllResults
{
bool operator()(const Win32FindData&) {
return true;
};
};
struct FilesOnly
{
bool operator()(const Win32FindData& arg) {
return arg.IsFile();
};
};
template <typename Filter_T, typename Recurse_T>
class DirectoryIterator :
public boost::iterator_facade<
DirectoryIterator<Filter_T, Recurse_T>,
Win32FindData,
std::input_iterator_tag,
Win32FindData
>
{
friend class boost::iterator_core_access;
boost::shared_ptr<Recurse_T> impl;
Filter_T filter;
void increment() {
do {
impl->increment();
} while (impl->Valid() && !filter(impl->dereference()));
};
bool equal(const DirectoryIterator& other) const {
return impl->equal(*other.impl);
};
Win32FindData dereference() const {
return impl->dereference();
};
public:
DirectoryIterator(Filter_T functor = Filter_T()) :
impl(boost::make_shared<Recurse_T>()),
filter(functor) {
};
explicit DirectoryIterator(const std::wstring& pathSpec, Filter_T functor = Filter_T()) :
impl(boost::make_shared<Recurse_T>(pathSpec)),
filter(functor) {
};
};
}}
Tests:
#include <queue>
#include "../WideCharacterOutput.hpp"
#include <boost/test/unit_test.hpp>
#include "../../WindowsAPI++/FileSystem/Enumerator.hpp"
using namespace WindowsAPI::FileSystem;
struct SimpleFakeFindXFileFunctions
{
static std::deque<WIN32_FIND_DATAW> fakeData;
static std::wstring insertedFileName;
static HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) {
insertedFileName.assign(lpFileName);
if (fakeData.empty()) {
SetLastError(ERROR_PATH_NOT_FOUN