¿Cómo podría ajustar las API de estilo FindXFile al patrón de iterador de estilo STL en C++?

21 minutos de lectura

¿Cómo podría ajustar las API de estilo FindXFile al patrón de iterador de estilo STL en C++?
Billy ONeal

Estoy trabajando para envolver las feas entrañas del FindFirstFile/FindNextFile bucle (aunque mi pregunta se aplica a otras API similares, como RegEnumKeyEx o RegEnumValueetc.) dentro de iteradores que funcionan de manera similar a la Biblioteca de plantillas estándar istream_iterators.

Tengo dos problemas aquí. El primero es con la condición de terminación de la mayoría de los bucles de estilo “foreach”. Los iteradores de estilo STL suelen utilizar operator!= dentro de la condición de salida del for, es decir

std::vector<int> test;
for(std::vector<int>::iterator it = test.begin(); it != test.end(); it++) {
 //Do stuff
}

Mi problema es que no estoy seguro de cómo implementar operator!= con tal enumeración de directorio, porque no sé cuándo se completa la enumeración hasta que realmente he terminado con ella. Ahora tengo una especie de solución de pirateo que enumera todo el directorio a la vez, donde cada iterador simplemente rastrea un vector contado de referencia, pero esto parece una chapuza que se puede hacer de una mejor manera.

El segundo problema que tengo es que las API de FindXFile devuelven varios datos. Por esa razón, no hay una forma obvia de sobrecargar operator* como se requiere para la semántica del iterador. Cuando sobrecargo ese elemento, ¿devuelvo el nombre del archivo? ¿El tamaño? ¿La fecha de modificación? ¿Cómo podría transmitir las múltiples piezas de datos a las que dicho iterador debe referirse más adelante de manera ideomática? He intentado copiar el estilo C# MoveNext diseño, pero me preocupa no seguir los modismos estándar aquí.

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....
};

EDITAR: Y la persona que llama se vería así:

SomeIterator x = ??; //Construct somehow
while(x.next()) {
    //Do stuff
}

¡Gracias!

Billy3

EDIT2: he solucionado algunos errores y escrito algunas pruebas.

Implementación:

#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, &currentData);
        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(), &currentData);
        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) {
    };
};

}}

Pruebas:

#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_FOUND);
            return INVALID_HANDLE_VALUE;
        }
        *lpFindFileData = fakeData.front();
        fakeData.pop_front();
        return reinterpret_cast<HANDLE>(42);
    };
    static BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        if (fakeData.empty()) {
            SetLastError(ERROR_NO_MORE_FILES);
            return 0;
        }
        *lpFindFileData = fakeData.front();
        fakeData.pop_front();
        return 1;
    };
    static BOOL Close(HANDLE hFindFile) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        return 1;
    };

};

std::deque<WIN32_FIND_DATAW> SimpleFakeFindXFileFunctions::fakeData;
std::wstring SimpleFakeFindXFileFunctions::insertedFileName;

struct ErroneousFindXFileFunctionFirst
{
    static HANDLE FindFirst(LPCWSTR, LPWIN32_FIND_DATAW) {
        SetLastError(ERROR_ACCESS_DENIED);
        return INVALID_HANDLE_VALUE;
    };
    static BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        return 1;
    };
    static BOOL Close(HANDLE hFindFile) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        return 1;
    };
};

struct ErroneousFindXFileFunctionNext
{
    static HANDLE FindFirst(LPCWSTR, LPWIN32_FIND_DATAW) {
        return reinterpret_cast<HANDLE>(42);
    };
    static  BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        SetLastError(ERROR_INVALID_PARAMETER);
        return 0;
    };
    static BOOL Close(HANDLE hFindFile) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        return 1;
    };
};

struct DirectoryIteratorTestsFixture
{
    typedef SimpleFakeFindXFileFunctions fakeFunctor;
    DirectoryIteratorTestsFixture() {
        WIN32_FIND_DATAW test;
        wcscpy_s(test.cFileName, L".");
        wcscpy_s(test.cAlternateFileName, L".");
        test.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        GetSystemTimeAsFileTime(&test.ftCreationTime);
        test.ftLastWriteTime = test.ftCreationTime;
        test.ftLastAccessTime = test.ftCreationTime;
        test.nFileSizeHigh = 0;
        test.nFileSizeLow = 0;
        fakeFunctor::fakeData.push_back(test);

        wcscpy_s(test.cFileName, L"..");
        wcscpy_s(test.cAlternateFileName, L"..");
        fakeFunctor::fakeData.push_back(test);

        wcscpy_s(test.cFileName, L"File.txt");
        wcscpy_s(test.cAlternateFileName, L"FILE.TXT");
        test.nFileSizeLow = 1024;
        test.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
        fakeFunctor::fakeData.push_back(test);

        wcscpy_s(test.cFileName, L"System32");
        wcscpy_s(test.cAlternateFileName, L"SYSTEM32");
        test.nFileSizeLow = 0;
        test.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        fakeFunctor::fakeData.push_back(test);
    };
    ~DirectoryIteratorTestsFixture() {
        fakeFunctor::fakeData.clear();
    };
};

BOOST_FIXTURE_TEST_SUITE( DirectoryIteratorTests, DirectoryIteratorTestsFixture )

template<typename fakeFunctor>
static void NonRecursiveIteratorAssertions()
{
    typedef DirectoryIterator<AllResults
        ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*");
    testType end;
    BOOST_CHECK_EQUAL(fakeFunctor::insertedFileName, L"C:\Windows\*");
    BOOST_CHECK(begin->GetFolderPath() == L"C:\Windows");
    BOOST_CHECK(begin->GetFileName() == L"File.txt");
    BOOST_CHECK(begin->GetFullFileName() == L"C:\Windows\File.txt");
    BOOST_CHECK(begin->GetShortFileName() == L"FILE.TXT");
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024);
    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK(begin != end);
    begin++;
    BOOST_CHECK(begin->GetFileName() == L"System32");
    BOOST_CHECK(begin->GetFullFileName() == L"C:\Windows\System32");
    BOOST_CHECK(begin->GetShortFileName() == L"SYSTEM32");
    BOOST_CHECK_EQUAL(begin->GetSize(), 0);
    BOOST_CHECK(begin->IsDirectory());
    begin++;
    BOOST_CHECK(begin == end);
}

BOOST_AUTO_TEST_CASE( BasicEnumeration )
{
    NonRecursiveIteratorAssertions<fakeFunctor>();
}

BOOST_AUTO_TEST_CASE( NoRootDirectories )
{
    fakeFunctor::fakeData.pop_front();
    fakeFunctor::fakeData.pop_front();
    NonRecursiveIteratorAssertions<fakeFunctor>();
}

static void EmptyIteratorAssertions() {
    typedef DirectoryIterator<AllResults
        ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*");
    testType end;
    BOOST_CHECK(begin == end);
}

BOOST_AUTO_TEST_CASE( Empty1 )
{
    fakeFunctor::fakeData.clear();
    EmptyIteratorAssertions();
}

BOOST_AUTO_TEST_CASE( Empty2 )
{
    fakeFunctor::fakeData.erase(fakeFunctor::fakeData.begin() + 2, fakeFunctor::fakeData.end());
    EmptyIteratorAssertions();
}

BOOST_AUTO_TEST_CASE( CorrectDestruction )
{
    typedef DirectoryIterator<AllResults
        ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*");
    testType end;
}

BOOST_AUTO_TEST_CASE( Exceptions )
{
    typedef DirectoryIterator<AllResults,BasicNonRecursiveEnumeration<ErroneousFindXFileFunctionFirst> >
        firstFailType;
    BOOST_CHECK_THROW(firstFailType(L"C:\Windows\*"), WindowsAPI::ErrorAccessDeniedException);
    typedef DirectoryIterator<AllResults,BasicNonRecursiveEnumeration<ErroneousFindXFileFunctionNext> >
        nextFailType;
    nextFailType constructedOkay(L"C:\Windows\*");
    BOOST_CHECK_THROW(constructedOkay++, WindowsAPI::ErrorInvalidParameterException);
}

BOOST_AUTO_TEST_SUITE_END()

struct RecursiveFakeFindXFileFunctions
{
    static std::deque<std::pair<std::deque<WIN32_FIND_DATA> , std::wstring> >  fakeData;
    static std::size_t openHandles;
    static HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) {
        BOOST_REQUIRE(!fakeData.empty());
        BOOST_REQUIRE_EQUAL(lpFileName, fakeData.front().second);
        openHandles++;
        BOOST_REQUIRE_EQUAL(openHandles, 1);
        if (fakeData.front().first.empty()) {
            openHandles--;
            SetLastError(ERROR_PATH_NOT_FOUND);
            return INVALID_HANDLE_VALUE;
        }
        *lpFindFileData = fakeData.front().first.front();
        fakeData.front().first.pop_front();
        return reinterpret_cast<HANDLE>(42);
    };
    static BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        if (fakeData.front().first.empty()) {
            SetLastError(ERROR_NO_MORE_FILES);
            return 0;
        }
        *lpFindFileData = fakeData.front().first.front();
        fakeData.front().first.pop_front();
        return 1;
    };
    static BOOL Close(HANDLE hFindFile) {
        BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile);
        openHandles--;
        BOOST_REQUIRE_EQUAL(openHandles, 0);
        fakeData.pop_front();
        return 1;
    };
};

std::deque<std::pair<std::deque<WIN32_FIND_DATA> , std::wstring> > RecursiveFakeFindXFileFunctions::fakeData;
std::size_t RecursiveFakeFindXFileFunctions::openHandles;

struct RecursiveDirectoryFixture
{
    RecursiveDirectoryFixture() {
        WIN32_FIND_DATAW tempData;
        ZeroMemory(&tempData, sizeof(tempData));
        std::deque<WIN32_FIND_DATAW> dequeData;

        wcscpy_s(tempData.cFileName, L".");
        wcscpy_s(tempData.cAlternateFileName, L".");
        tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        GetSystemTimeAsFileTime(&tempData.ftCreationTime);
        tempData.ftLastWriteTime = tempData.ftCreationTime;
        tempData.ftLastAccessTime = tempData.ftCreationTime;
        dequeData.push_back(tempData);

        wcscpy_s(tempData.cFileName, L"..");
        wcscpy_s(tempData.cAlternateFileName, L"..");
        dequeData.push_back(tempData);

        wcscpy_s(tempData.cFileName, L"MySubDirectory");
        wcscpy_s(tempData.cAlternateFileName, L"MYSUBD~1");
        dequeData.push_back(tempData);

        wcscpy_s(tempData.cFileName, L"MyFile.txt");
        wcscpy_s(tempData.cAlternateFileName, L"MYFILE.TXT");
        tempData.nFileSizeLow = 500;
        tempData.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
        dequeData.push_back(tempData);

        RecursiveFakeFindXFileFunctions::fakeData.push_back
            (std::make_pair(dequeData, L"C:\Windows\*"));

        dequeData.clear();

        wcscpy_s(tempData.cFileName, L".");
        wcscpy_s(tempData.cAlternateFileName, L".");
        tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        GetSystemTimeAsFileTime(&tempData.ftCreationTime);
        tempData.ftLastWriteTime = tempData.ftCreationTime;
        tempData.ftLastAccessTime = tempData.ftCreationTime;
        dequeData.push_back(tempData);

        wcscpy_s(tempData.cFileName, L"..");
        wcscpy_s(tempData.cAlternateFileName, L"..");
        dequeData.push_back(tempData);

        wcscpy_s(tempData.cFileName, L"MyFile2.txt");
        wcscpy_s(tempData.cAlternateFileName, L"NYFILE2.TXT");
        tempData.nFileSizeLow = 1024;
        tempData.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
        dequeData.push_back(tempData);

        RecursiveFakeFindXFileFunctions::fakeData.push_back
            (std::make_pair(dequeData, L"C:\Windows\MySubDirectory\*"));
    };
    ~RecursiveDirectoryFixture() {
        RecursiveFakeFindXFileFunctions::fakeData.clear();
    };
};

BOOST_AUTO_TEST_SUITE( RecursiveDirectoryIteratorTests )

BOOST_AUTO_TEST_CASE( BasicEnumerationTxt )
{
    RecursiveDirectoryFixture DataFixture;
    typedef DirectoryIterator<AllResults
        ,BasicRecursiveEnumeration<RecursiveFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*.txt");
    testType end;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows\MySubDirectory");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile2.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MySubDirectory\MyFile2.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 500);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MyFile.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin == end);
}

BOOST_AUTO_TEST_CASE( BasicEnumerationAll )
{
    RecursiveDirectoryFixture DataFixture;
    typedef DirectoryIterator<AllResults
        ,BasicRecursiveEnumeration<RecursiveFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*");
    testType end;

    BOOST_CHECK(begin->IsDirectory());
    BOOST_CHECK_EQUAL(begin->GetSize(), 0);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MySubDirectory");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MySubDirectory");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows\MySubDirectory");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile2.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MySubDirectory\MyFile2.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 500);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MyFile.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin == end);
}

BOOST_AUTO_TEST_CASE( RecursionOrderMaintained )
{
    WIN32_FIND_DATAW tempData;
    ZeroMemory(&tempData, sizeof(tempData));
    std::deque<WIN32_FIND_DATAW> dequeData;

    wcscpy_s(tempData.cFileName, L".");
    wcscpy_s(tempData.cAlternateFileName, L".");
    tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
    GetSystemTimeAsFileTime(&tempData.ftCreationTime);
    tempData.ftLastWriteTime = tempData.ftCreationTime;
    tempData.ftLastAccessTime = tempData.ftCreationTime;
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"..");
    wcscpy_s(tempData.cAlternateFileName, L"..");
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"MySubDirectory");
    wcscpy_s(tempData.cAlternateFileName, L"MYSUBD~1");
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"MyFile.txt");
    wcscpy_s(tempData.cAlternateFileName, L"MYFILE.TXT");
    tempData.nFileSizeLow = 500;
    tempData.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"Zach");
    wcscpy_s(tempData.cAlternateFileName, L"ZACH");
    tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
    tempData.nFileSizeLow = 0;
    dequeData.push_back(tempData);

    RecursiveFakeFindXFileFunctions::fakeData.push_back
        (std::make_pair(dequeData, L"C:\Windows\*"));

    dequeData.clear();

    wcscpy_s(tempData.cFileName, L".");
    wcscpy_s(tempData.cAlternateFileName, L".");
    tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
    GetSystemTimeAsFileTime(&tempData.ftCreationTime);
    tempData.ftLastWriteTime = tempData.ftCreationTime;
    tempData.ftLastAccessTime = tempData.ftCreationTime;
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"..");
    wcscpy_s(tempData.cAlternateFileName, L"..");
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"MyFile2.txt");
    wcscpy_s(tempData.cAlternateFileName, L"NYFILE2.TXT");
    tempData.nFileSizeLow = 1024;
    tempData.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
    dequeData.push_back(tempData);

    RecursiveFakeFindXFileFunctions::fakeData.push_back
        (std::make_pair(dequeData, L"C:\Windows\MySubDirectory\*"));

    dequeData.clear();
    wcscpy_s(tempData.cFileName, L".");
    wcscpy_s(tempData.cAlternateFileName, L".");
    tempData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
    GetSystemTimeAsFileTime(&tempData.ftCreationTime);
    tempData.ftLastWriteTime = tempData.ftCreationTime;
    tempData.ftLastAccessTime = tempData.ftCreationTime;
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"..");
    wcscpy_s(tempData.cAlternateFileName, L"..");
    dequeData.push_back(tempData);

    wcscpy_s(tempData.cFileName, L"ZachFile.txt");
    wcscpy_s(tempData.cAlternateFileName, L"ZACHFILE.TXT");
    tempData.nFileSizeLow = 1024;
    tempData.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
    dequeData.push_back(tempData);

    RecursiveFakeFindXFileFunctions::fakeData.push_back
        (std::make_pair(dequeData, L"C:\Windows\Zach\*"));

    typedef DirectoryIterator<AllResults
        ,BasicRecursiveEnumeration<RecursiveFakeFindXFileFunctions> > testType;
    testType begin(L"C:\Windows\*");
    testType end;

    BOOST_CHECK(begin->IsDirectory());
    BOOST_CHECK_EQUAL(begin->GetSize(), 0);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MySubDirectory");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MySubDirectory");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows\MySubDirectory");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile2.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MySubDirectory\MyFile2.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 500);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"MyFile.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\MyFile.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsDirectory());
    BOOST_CHECK_EQUAL(begin->GetSize(), 0);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"Zach");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\Zach");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin->IsFile());
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024);
    BOOST_CHECK_EQUAL(begin->GetFolderPath(), L"C:\Windows\Zach");
    BOOST_CHECK_EQUAL(begin->GetFileName(), L"ZachFile.txt");
    BOOST_CHECK_EQUAL(begin->GetFullFileName(), L"C:\Windows\Zach\ZachFile.txt");
    BOOST_CHECK(begin != end);

    begin++;

    BOOST_CHECK(begin == end);
}

BOOST_AUTO_TEST_CASE( Exceptions )
{
    typedef DirectoryIterator<AllResults,BasicRecursiveEnumeration<ErroneousFindXFileFunctionFirst> >
        firstFailType;
    BOOST_CHECK_THROW(firstFailType(L"C:\Windows\*"), WindowsAPI::ErrorAccessDeniedException);
    typedef DirectoryIterator<AllResults,BasicRecursiveEnumeration<ErroneousFindXFileFunctionNext> >
        nextFailType;
    BOOST_CHECK_THROW(nextFailType(L"C:\Windows\*"), WindowsAPI::ErrorInvalidParameterException);
}

BOOST_AUTO_TEST_SUITE_END()

  • ¿No boost ya hace esto con la biblioteca boost::filesystem?

    – Chris K.

    08 abr.

  • @Chris Kaminski: Los objetivos de diseño de este iterador y boost son diferentes. Para esto, quería algo que fuera rápido y que generara poca o ninguna sobrecarga en las llamadas directas a FindXFile (este programa dedica MUCHO tiempo a la enumeración de directorios). El objetivo de Boost::Filesystem es más para la compatibilidad entre plataformas. Por ejemplo, incluye su propio analizador de ruta y otras cosas asociadas que no quiero. Por lo tanto, la razón para implementar esto yo mismo.

    – Billy ONeal

    08 abr.

Para resolver el primer problema, puedes tener end() devuelva algún valor centinela, luego en la función de incremento de su iterador, establezca el iterador igual a ese valor centinela cuando llegue al final del contenedor. Esto es efectivamente lo que hace el iterador de directorios en Boost.Filesystem.

Para el segundo problema, no estoy del todo familiarizado con el FindXFile API, pero una opción sería devolver alguna estructura que contenga todos los datos que necesita o que tenga funciones miembro para obtener cada uno de los datos que desee.

  • Usando un WIN32_FIND_DATA con .dwAttributes ajustado a DWORD(-1) funciona bastante bien como centinela. Normalmente, FILE_ATTRIBUTE_NORMAL=0x0080 no se puede combinar con otras banderas como FILE_ATTRIBUTE_DIRECTORY==0x0010.

    – MSalters

    29 mar. 10 a las 15:46

¿Cómo podría ajustar las API de estilo FindXFile al patrón de iterador de estilo STL en C++?
ataúd de jerry

Curiosamente, tuve la misma idea hace algún tiempo. Esto es lo que escribí:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include <iterator>
#include <exception>

#ifndef DIR_ITERATOR_H_INC
#define DIR_ITERATOR_H_INC

class dir_iterator

#if (!defined(_MSC_VER)) || (_MSC_VER > 1200)
    : public std::iterator<std::input_iterator_tag, 
                            std::string, 
                            int, 
                            std::string *, 
                            std::string &> 
#endif
{ 
    mutable HANDLE it;
    std::string mask;
    std::string path;
    WIN32_FIND_DATA data;
    bool done;
    DWORD require;
    DWORD prohibit;
public:
    WIN32_FIND_DATA operator*() { 
        return data;
    }

dir_iterator(dir_iterator const &other) :
    it(other.it),
    mask(other.mask),
    path(other.path),
    data(other.data),
    done(other.done),
    require(other.require),
    prohibit(other.prohibit)
{
    // Transfer the handle instead of just copying it.
    other.it=INVALID_HANDLE_VALUE;
}

    dir_iterator(std::string const &s, 
        DWORD must = 0,
        DWORD cant = FILE_ATTRIBUTE_DIRECTORY)
        : mask(s),
        require(must),
        prohibit(cant & ~must),
        done(false),
        it(INVALID_HANDLE_VALUE) // To fix bug spotted by Billy ONeal.
    { 
        int pos;
        if (std::string::npos != (pos=mask.find_last_of("\/"))) 
            path = std::string(mask, 0, pos+1);

        it = FindFirstFile(mask.c_str(), &data);
        if (it == INVALID_HANDLE_VALUE)
            throw std::invalid_argument("Directory Inaccessible");

        while (!(((data.dwFileAttributes & require) == require) &&
                ((data.dwFileAttributes & prohibit) ==  0)))
        {
            if (done = (FindNextFile(it, &data)==0))
                break;
        }
    }

    dir_iterator() : done(true) {}

    dir_iterator &operator++() {
        do { 
            if (done = (FindNextFile(it, &data)==0))
                break;
        } while (!(((data.dwFileAttributes & require) == require) &&
            (data.dwFileAttributes & prohibit) == 0));

        return *this;
    }

    bool operator!=(dir_iterator const &other) { 
        return done != other.done;
    }
    bool operator==(dir_iterator const &other) { 
        return done == other.done;
    }

    ~dir_iterator() { 
        // The rest of the bug fix -- only close handle if it's open.
        if (it!=INVALID_HANDLE_VALUE)
            FindClose(it);
    }
};

#endif

Y una demostración rápida de ello:

#include "dir_iterator.h"
#include <iostream>
#include <algorithm>

namespace std { 
    std::ostream &operator<<(std::ostream &os, WIN32_FIND_DATA const &d) { 
        return os << d.cFileName;
    }
}

int main() { 
    std::copy(dir_iterator("*"), dir_iterator(), 
        std::ostream_iterator<WIN32_FIND_DATA>(std::cout, "n"));

    std::cout << "nDirectories:n";
    std::copy(dir_iterator("*", FILE_ATTRIBUTE_DIRECTORY), dir_iterator(), 
        std::ostream_iterator<WIN32_FIND_DATA>(std::cout, "n"));

    return 0;
}

  • +1, aunque corríjame si me equivoco aquí, ¿eso no llama a FindClose () en datos basura cuando se destruye el iterador construido predeterminado?

    – Billy ONeal

    28 mar.

  • @Billy: ahora que lo mencionas, sí, creo que sí. Tendré que arreglar eso. Uno de los beneficios de publicar código es detectar un error de vez en cuando, gracias.

    – Jerry Ataúd

    28 mar.

  • @Jerry Coffin: un error más: si se copia el iterador, se llamará a FindClose() dos veces en el mismo identificador. ¡Simplemente no llames a los algoritmos STL en este LOL! Dándole la marca de verificación para la demostración.

    – Billy ONeal

    28 mar.

  • @BillyONeal: al menos en teoría, tienes razón. Sin embargo, hasta ahora, nunca he visto un problema (o el otro error); supongo que FindClose es lo suficientemente inteligente como para no hacer nada si lo que pasa no es un identificador de búsqueda válido. En teoría, la respuesta correcta probablemente sería un objeto de implementación con un envoltorio con recuento de referencias.

    – Jerry Ataúd

    28 mar.

  • @BillyONeal: Pensándolo un poco más, en este caso, la semántica de transferencia de propiedad probablemente esté bien; editaré un poco el código para hacer eso.

    – Jerry Ataúd

    28 mar.

Matthew Wilson ha escrito varios artículos sobre la adaptación de la enumeración de directorios a los iteradores STL. Desafortunadamente, el artículo que aborda directamente las API de Windows ya no parece estar en línea. Sin embargo, me imagino que las ideas que se discuten en algunos de sus otros artículos probablemente sigan siendo muy relevantes, y también proporciona una biblioteca de código abierto (WinSTL – http://winstl.org/) con la implementación de Windows.

Además, estoy seguro de que el Boost::Fuente y documentación del sistema de archivos es una gran fuente de ideas.

¿Cómo podría ajustar las API de estilo FindXFile al patrón de iterador de estilo STL en C++?
dcw

A menos que lo estés haciendo para aprender, mi respuesta sería: no lo hagas, ya se ha hecho. Las bibliotecas de STLSoft contienen la winstl::basic_findfile_secuencia plantilla de clase que maneja la mayoría/todos los casos de uso para buscar en un directorio de Windows, incluidos comodines, patrones de varias partes (p. ej., “.xls|.doc”), puntos de análisis y más. Está documentado con gran detalle en matthew wilsonEl tratado en profundidad de STL sobre la extensión STL “Extended STL, volumen 1: Colecciones e iteradores”. El libro es una lectura importante, pero contiene todas las cosas que querrá saber (así como muchas que quizás no) sobre cómo escribir extensiones STL.

Si necesita una búsqueda recursiva, entonces considere la recuerdos biblioteca (otra de Wilson), que también proporciona una interfaz STL, a través de la clase recls::search_sequence. Ver aquí para un montón de ejemplos de un articulo reciente al respecto.

Pruebe la biblioteca recls, discutido aquí. (Su también disponible para .NETvaya.)

¿Cómo podría ajustar las API de estilo FindXFile al patrón de iterador de estilo STL en C++?
gast128

Boost.Filesystem tiene STL independientes de la plataforma como iteradores. Sin embargo, tenga en cuenta que los iteradores pueden lanzar si un archivo es inaccesible.

Ejemplo (se puede proxeneta usando count_if y lambda):

namespace fs = boost::filesystem;

size_t nTotal = 0; 

try
{
   const fs::path pth("c:\temp");

   //default construction yields past-the-end
   fs::recursive_directory_iterator itEnd;

   for (fs::recursive_directory_iterator it(pth); it != itEnd; ++it)
   {
      const fs::directory_entry& rEntry = *it;

      if (fs::is_regular_file(rEntry))
      {
         nTotal += fs::file_size(rEntry);
      }
   }
}
catch (const fs::filesystem_error& re)
{
   //when directoy or file is not found
}

.

¿Ha sido útil esta solución?