// Copyright (C) 2003 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_DIR_NAV_KERNEl_1_ #define DLIB_DIR_NAV_KERNEl_1_ #ifdef DLIB_ISO_CPP_ONLY #error "DLIB_ISO_CPP_ONLY is defined so you can't use this OS dependent code. Turn DLIB_ISO_CPP_ONLY off if you want to use it." #endif #include "../platform.h" #include "dir_nav_kernel_abstract.h" #include <string> #include "../uintn.h" #include "../algs.h" #include "../windows_magic.h" #include <windows.h> #include <vector> #include "../stl_checked.h" #include "../enable_if.h" #include "../queue.h" #include <chrono> namespace dlib { // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // file object // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class file { /*! INITIAL VALUES state.name == name() state.full_name == full_name() state.file_size == size() state.last_modified == last_modified() CONVENTION state.name == name() state.full_name == full_name() state.file_size == size() state.last_modified == last_modified() !*/ friend class directory; struct data { uint64 file_size; std::string name; std::string full_name; std::chrono::time_point<std::chrono::system_clock> last_modified; }; void init ( const std::string& name); public: struct private_constructor{}; inline file ( const std::string& name, const std::string& full_name, const uint64 file_size, const std::chrono::time_point<std::chrono::system_clock>& last_modified, private_constructor ) { state.file_size = file_size; state.name = name; state.full_name = full_name; state.last_modified = last_modified; } class file_not_found : public error { public: file_not_found(const std::string& s): error(s){} }; inline file ( ) { state.file_size = 0; } file ( const std::string& name ) { init(name); } file ( const char* name ) { init(name); } inline const std::string& name ( ) const { return state.name; } inline const std::string& full_name ( ) const { return state.full_name; } operator std::string ( ) const { return full_name(); } inline uint64 size ( ) const { return state.file_size; } inline std::chrono::time_point<std::chrono::system_clock> last_modified ( ) const { return state.last_modified; } bool operator == ( const file& rhs ) const; bool operator != ( const file& rhs ) const { return !(*this == rhs); } inline bool operator < ( const file& item ) const { return full_name() < item.full_name(); } inline void swap ( file& item ) { exchange(state,item.state); } private: data state; }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // directory object // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class directory { /*! INITIAL VALUES state.name == name() state.full_name == full_name() CONVENTION state.name == name() state.full_name == full_name() is_root() == state.name.size() == 0 !*/ void init (const std::string& name); public: struct data { std::string name; std::string full_name; }; /* The reason we don't just make this constructor actually private is because doing it this way avoids a bug that sometimes occurs in visual studio 7.1. The bug has something to do with templated friend functions such as the get_filesystem_roots() function below if it was declared as a friend template of this class. */ struct private_constructor{}; inline directory ( const std::string& name, const std::string& full_name, private_constructor ) { state.name = name; state.full_name = full_name; } class dir_not_found : public error { public: dir_not_found(const std::string& s):error(s){} }; class listing_error : public error { public: listing_error(const std::string& s):error(s){} }; inline directory ( ) { } directory ( const std::string& name ) { init(name); } directory ( const char* name ) { init(name); } static char get_separator ( ); template < typename queue_of_files > void get_files ( queue_of_files& files ) const; template < typename queue_of_dirs > void get_dirs ( queue_of_dirs& dirs ) const; std::vector<file> get_files ( ) const { std::vector<file> temp_vector; get_files(temp_vector); return temp_vector; } std::vector<directory> get_dirs ( ) const { std::vector<directory> temp_vector; get_dirs(temp_vector); return temp_vector; } const directory get_parent ( ) const; inline bool is_root ( ) const { return state.name.size() == 0; } inline const std::string& name ( ) const { return state.name; } inline const std::string& full_name ( ) const { return state.full_name; } operator std::string ( ) const { return full_name(); } bool operator == ( const directory& rhs ) const; bool operator != ( const directory& rhs ) const { return !(*this == rhs); } inline bool operator < ( const directory& item ) const { return full_name() < item.full_name(); } inline void swap ( directory& item ) { exchange(state,item.state); } private: // member data data state; bool is_root_path ( const std::string& path ) const; /*! ensures - returns true if path is a root path. Note that this function considers root paths that don't have a trailing separator to also be valid. !*/ }; // ---------------------------------------------------------------------------------------- inline std::ostream& operator<< ( std::ostream& out, const directory& item ) { out << (std::string)item; return out; } inline std::ostream& operator<< ( std::ostream& out, const file& item ) { out << (std::string)item; return out; } // ---------------------------------------------------------------------------------------- template < typename queue_of_dir > typename disable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots ( queue_of_dir& roots ) { roots.clear(); const DWORD mask = GetLogicalDrives(); DWORD bit = 1; char buf[] = "A:\\"; do { if (mask & bit) { directory dir("",buf,directory::private_constructor()); roots.enqueue(dir); } bit <<= 1; ++buf[0]; } while (buf[0] != 'Z'); } template < typename queue_of_dir > typename enable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots ( queue_of_dir& roots ) { roots.clear(); const DWORD mask = GetLogicalDrives(); DWORD bit = 1; char buf[] = "A:\\"; do { if (mask & bit) { directory dir("",buf,directory::private_constructor()); roots.push_back(dir); } bit <<= 1; ++buf[0]; } while (buf[0] != 'Z'); } // ---------------------------------------------------------------------------------------- inline void swap ( file& a, file& b ) { a.swap(b); } // ---------------------------------------------------------------------------------------- inline void swap ( directory& a, directory& b ) { a.swap(b); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // templated member function definitions // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename queue_of_files > typename disable_if<is_std_vector<queue_of_files>,void>::type directory_helper_get_files ( const directory::data& state, queue_of_files& files ) { using namespace std; typedef directory::listing_error listing_error; typedef file::private_constructor private_constructor; files.clear(); if (state.full_name.size() == 0) throw listing_error("This directory object currently doesn't represent any directory."); HANDLE ffind = INVALID_HANDLE_VALUE; try { WIN32_FIND_DATAA data; string path = state.full_name; // ensure that the path ends with a separator if (path[path.size()-1] != directory::get_separator()) path += directory::get_separator(); ffind = FindFirstFileA((path+"*").c_str(), &data); if (ffind == INVALID_HANDLE_VALUE) { throw listing_error("Unable to list the contents of " + state.full_name); } bool no_more_files = false; do { if ((data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) == 0) { uint64 file_size = data.nFileSizeHigh; file_size <<= 32; file_size |= data.nFileSizeLow; ULARGE_INTEGER ull; ull.LowPart = data.ftLastWriteTime.dwLowDateTime; ull.HighPart = data.ftLastWriteTime.dwHighDateTime; std::chrono::nanoseconds epoch(100 * (ull.QuadPart - 116444736000000000)); auto last_modified = std::chrono::time_point<std::chrono::system_clock>(std::chrono::duration_cast<std::chrono::system_clock::duration>(epoch)); // this is a file so add it to the queue file temp(data.cFileName,path+data.cFileName,file_size, last_modified, private_constructor()); files.enqueue(temp); } if (FindNextFileA(ffind,&data) == 0) { // an error occurred if ( GetLastError() == ERROR_NO_MORE_FILES) { // there are no more files no_more_files = true; } else { // there was an error throw listing_error("Unable to list the contents of " + state.full_name); } } } while (no_more_files == false); FindClose(ffind); ffind = INVALID_HANDLE_VALUE; } catch (...) { if (ffind != INVALID_HANDLE_VALUE) FindClose(ffind); files.clear(); throw; } } // ---------------------------------------------------------------------------------------- template < typename queue_of_files > typename enable_if<is_std_vector<queue_of_files>,void>::type directory_helper_get_files ( const directory::data& state, queue_of_files& files ) { queue<file>::kernel_2a temp_files; directory_helper_get_files(state,temp_files); files.clear(); // copy the queue of files into the vector temp_files.reset(); while (temp_files.move_next()) { files.push_back(temp_files.element()); } } // ---------------------------------------------------------------------------------------- template < typename queue_of_files > void directory:: get_files ( queue_of_files& files ) const { // the reason for this indirection here is because it avoids a bug in // the mingw version of gcc directory_helper_get_files(state,files); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > typename disable_if<is_std_vector<queue_of_dirs>,void>::type directory_helper_get_dirs ( const directory::data& state, queue_of_dirs& dirs ) { using namespace std; typedef directory::listing_error listing_error; typedef directory::private_constructor private_constructor; dirs.clear(); if (state.full_name.size() == 0) throw listing_error("This directory object currently doesn't represent any directory."); HANDLE dfind = INVALID_HANDLE_VALUE; try { WIN32_FIND_DATAA data; string path = state.full_name; // ensure that the path ends with a separator if (path[path.size()-1] != directory::get_separator()) path += directory::get_separator(); dfind = FindFirstFileA((path+"*").c_str(), &data); if (dfind == INVALID_HANDLE_VALUE) { throw listing_error("Unable to list the contents of " + state.full_name); } bool no_more_files = false; do { string tname(data.cFileName); if ((data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) != 0 && tname != "." && tname != "..") { // this is a directory so add it to the queue directory temp(tname,path+tname,private_constructor()); dirs.enqueue(temp); } if (FindNextFileA(dfind,&data) == 0) { // an error occurred if ( GetLastError() == ERROR_NO_MORE_FILES) { // there are no more files no_more_files = true; } else { // there was an error throw listing_error("Unable to list the contents of " + state.full_name); } } } while (no_more_files == false); FindClose(dfind); dfind = INVALID_HANDLE_VALUE; } catch (...) { if (dfind != INVALID_HANDLE_VALUE) FindClose(dfind); dirs.clear(); throw; } } // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > typename enable_if<is_std_vector<queue_of_dirs>,void>::type directory_helper_get_dirs ( const directory::data& state, queue_of_dirs& dirs ) { queue<directory>::kernel_2a temp_dirs; directory_helper_get_dirs(state,temp_dirs); dirs.clear(); // copy the queue of dirs into the vector temp_dirs.reset(); while (temp_dirs.move_next()) { dirs.push_back(temp_dirs.element()); } } // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > void directory:: get_dirs ( queue_of_dirs& dirs ) const { // the reason for this indirection here is because it avoids a bug in // the mingw version of gcc directory_helper_get_dirs(state,dirs); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- } #ifdef NO_MAKEFILE #include "dir_nav_kernel_1.cpp" #endif #endif // DLIB_DIR_NAV_KERNEl_1_