#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_STORAGE_FILESYSTEM_GENERIC_GENERICWINDOWSCONTAINER_H #define NUCLEX_STORAGE_FILESYSTEM_GENERIC_GENERICWINDOWSCONTAINER_H #include "Nuclex/Storage/Config.h" #if defined(NUCLEX_STORAGE_WIN32) || defined(NUCLEX_STORAGE_WINRT) #include "Nuclex/Storage/FileSystem/Container.h" #include "Nuclex/Storage/FileSystem/ContainerFileCodec.h" #include "GenericWindowsFile.h" #include "../../Helpers/StringHelper.h" #include "../../Helpers/WindowsApi.h" #include namespace Nuclex { namespace Storage { namespace FileSystem { // ------------------------------------------------------------------------------------------- // /// Directory on a Windows system using either Win32 or WinRT /// /// A class wrapping the API-specific file system methods /// /// /// A class containing helper methods for working with paths on the platform /// /// /// The differences between Win32 and WinRT are minor as far as file handling /// is concerned, so both share this class to perform their file acccesses, /// using the FileApi and PathHelper to handle the API differences. /// template class GenericWindowsContainer : public Container { #pragma region class FindHandleScope /// Automatically closes a find handle when the scope is left private: class FindHandleScope { /// Initializes a new find handle scope /// Find handle that will be closed when the scope is left public: FindHandleScope(HANDLE findHandle) : findHandle(findHandle) {} /// Closes the find handle public: ~FindHandleScope() { TFileApi::CloseSearch(this->findHandle); } /// Find handle the scope will close private: HANDLE findHandle; }; #pragma endregion // class FindHandleScope /// Smart pointer to a constant container private: typedef std::shared_ptr ConstContainerPointer; /// Smart pointer to a container private: typedef std::shared_ptr ContainerPointer; /// Smart pointer to a constant file private: typedef std::shared_ptr ConstFilePointer; /// Smart pointer to a file private: typedef std::shared_ptr FilePointer; /// A vector of strings private: typedef std::vector StringVector; /// Type of files this container is dealing with private: typedef GenericWindowsFile TFile; /// Initializes a new windows container /// Codecs used to open files as containers /// Absolute path of the directory public: GenericWindowsContainer( const std::shared_ptr &codecs, const std::string &absolutePath ) : Container(codecs), path(absolutePath), name(TPathHelper::GetLastPathElement(absolutePath)) {} /// Destroys the container interface public: virtual ~GenericWindowsContainer() {} /// Returns the name of the file /// The file's name public: const std::string &GetName() const { return this->name; } /// Returns the path the file is stored at in the native format /// The file's absolute path in the native OS format public: const std::string &GetNativePath() const { return this->path; } /// Retrieves a list of the containers located in this container /// Wildcard of the containers to list /// A list of all containers matching the wildcard public: std::vector GetContainers( const std::string &wildcard = std::string() ) const { std::pair contents = enumerate(this->path, wildcard); std::vector containers(contents.second.size()); for(std::size_t index = 0; index < contents.second.size(); ++index) { containers[index] = std::shared_ptr( new GenericWindowsContainer(this->Codecs, contents.second[index]) ); } for(std::size_t index = 0; index < contents.first.size(); ++index) { std::shared_ptr file(new TFile(contents.first[index])); std::shared_ptr container; bool wasOpened = this->Codecs->TryOpenAsContainer( this->Codecs, file, container ); if(wasOpened) { containers.push_back(container); } } return containers; } /// Retrieves a list of the containers located in this container /// Wildcard of the containers to list /// A list of all containers matching the wildcard public: virtual std::vector GetContainers( const std::string &wildcard = std::string() ) { std::pair contents = enumerate(this->path, wildcard); std::vector containers(contents.second.size()); for(std::size_t index = 0; index < contents.second.size(); ++index) { containers[index] = std::shared_ptr( new GenericWindowsContainer(this->Codecs, contents.second[index]) ); } for(std::size_t index = 0; index < contents.first.size(); ++index) { std::shared_ptr file(new TFile(contents.first[index])); std::shared_ptr container; bool wasOpened = this->Codecs->TryOpenAsContainer( this->Codecs, file, container ); if(wasOpened) { containers.push_back(container); } } return containers; } /// Retrieves the container with the specified name /// Name of the container that will be retrieved /// The container with the specified name public: virtual ConstContainerPointer GetContainer(const std::string &name) const { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes == INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("Directory does not exist"); } if(isFile(attributes)) { std::shared_ptr file(new TFile(containerPath)); std::shared_ptr container; bool wasOpened = this->Codecs->TryOpenAsContainer(this->Codecs, file, container); if(wasOpened) { return container; } else { throw std::runtime_error("Could not open this file as a container"); } } return ConstContainerPointer(new GenericWindowsContainer(this->Codecs, containerPath)); } /// Retrieves the container with the specified name /// Name of the container that will be retrieved /// The container with the specified name public: virtual ContainerPointer GetContainer(const std::string &name) { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes == INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("Directory does not exist"); } if(isFile(attributes)) { std::shared_ptr file(new TFile(containerPath)); std::shared_ptr container; bool wasOpened = this->Codecs->TryOpenAsContainer(this->Codecs, file, container); if(wasOpened) { return container; } else { throw std::runtime_error("Could not open this file as a container"); } } return ContainerPointer(new GenericWindowsContainer(this->Codecs, containerPath)); } /// Checks if the container contains the specified child container /// Child container the container will be checked for /// True if the container has a child container with the specified name public: virtual bool HasContainer(const std::string &name) const { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes == INVALID_FILE_ATTRIBUTES) { return false; } else if((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { std::shared_ptr file(new TFile(containerPath)); return this->Codecs->CanOpenAsContainer(file); } else { return true; } } /// Creates a new container in this container /// Name of the created container /// The created container public: virtual ContainerPointer CreateContainer(const std::string &name) { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes != INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("Directory or file with same name already exists"); } TFileApi::CreateDirectory(containerPath); // TODO: Allow container file codecs to create containers return ContainerPointer(new GenericWindowsContainer(this->Codecs, containerPath)); } /// Deletes a container in this container /// Name of the deleted container /// True if the container was deleted, false if it didn't exist public: virtual bool DeleteContainer(const std::string &name) { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes == INVALID_FILE_ATTRIBUTES) { return false; } if((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { std::shared_ptr file(new TFile(containerPath)); if(this->Codecs->CanOpenAsContainer(file)) { file.reset(); TFileApi::DeleteFile(containerPath); } else { throw std::runtime_error("Cannot delete a file as a directory"); } } else { TFileApi::DeleteDirectory(containerPath); } return true; } /// Retrieves a list of the files located in this container /// Wildcard of the files to list /// A list of all files matching the wildcard public: virtual std::vector GetFiles( const std::string &wildcard = std::string() ) const { std::pair contents = enumerate(this->path, wildcard); StringVector &filePaths = contents.first; std::vector files(filePaths.size()); for(std::size_t index = 0; index < filePaths.size(); ++index) { files[index] = std::shared_ptr(new TFile(filePaths[index])); } return files; } /// Retrieves a list of the files located in this container /// Wildcard of the files to list /// A list of all files matching the wildcard public: virtual std::vector GetFiles( const std::string &wildcard = std::string() ) { std::pair contents = enumerate(this->path, wildcard); StringVector &filePaths = contents.first; std::vector files(filePaths.size()); for(std::size_t index = 0; index < filePaths.size(); ++index) { files[index] = std::shared_ptr(new TFile(filePaths[index])); } return files; } /// Retrieves the file with the specified name /// Name of the file that will be retrieved /// The file with the specified name public: virtual ConstFilePointer GetFile(const std::string &name) const { std::string filePath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(filePath); if(attributes == INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("File does not exist"); } if((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { throw std::runtime_error("Could not open directory as a file"); } return ConstFilePointer(new TFile(filePath)); } /// Retrieves the file with the specified name /// Name of the file that will be retrieved /// The file with the specified name public: virtual FilePointer GetFile(const std::string &name) { std::string filePath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(filePath); if(attributes == INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("File does not exist"); } if((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { throw std::runtime_error("Could not open directory as a file"); } return FilePointer(new TFile(filePath)); } /// Checks if the container contains the specified file /// File the container will be checked for /// True if the container contains a file with the specified name public: virtual bool HasFile(const std::string &name) const { std::string containerPath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(containerPath); if(attributes == INVALID_FILE_ATTRIBUTES) { return false; // Doesn't exist } else if((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { return true; // Is a file } else { return false; // Is a directory } } /// Retrieves the file with the specified name /// Name of the file that will be retrieved /// The file with the specified name public: virtual FilePointer CreateFile(const std::string &name) { std::string filePath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(filePath); if(attributes != INVALID_FILE_ATTRIBUTES) { throw std::runtime_error("File or directory with the same name already exists"); } // Make sure the file is created right away FilePointer newFile(new TFile(filePath)); newFile->WriteAt(0, nullptr, 0); return newFile; } /// Deletes the file with the specified name /// Name of the file that will be deleted /// True if the file existed and was deleted, false if it didn't exist /// /// This method can silently ignore when a file does not exist but will /// public: virtual bool DeleteFile(const std::string &name) { std::string filePath = TPathHelper::MakeAbsolute(this->path, name); DWORD attributes = TFileApi::GetFileAttributes(filePath); if(attributes == INVALID_FILE_ATTRIBUTES) { return false; } if((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { throw std::runtime_error("Could not delete directory as a file"); } TFileApi::DeleteFile(filePath); return true; } /// Builds a search mask from a directory and file mask /// Directory the search will take place in /// /// Mask of files that will be searched for. If empty, the search will include all files. /// /// The search mask for Windows' FindFile() functions private: static std::string getSearchMask( const std::string &directory, const std::string &wildcard ) { std::string searchMask = directory; // If a directory was specified, prepend it to the search mask if(!searchMask.empty()) { TPathHelper::AddTrailingPathSeparator(searchMask); } // If no wildcard is specified, use "*.*". Otherwise, append the wildcard. if(wildcard.empty()) { static const char searchMaskChars[] = "*.*\0"; searchMask.append(searchMaskChars, 3); } else { searchMask.append(wildcard); } return searchMask; } /// Enumerates all directories matching a wildcard /// Directory whose sub directories will be enumerated /// Wildcard by which the search will be restricted /// Whether directories should also be enumerated /// /// A pair containing the directories in .first and the files in .second /// public: static std::pair enumerate( const std::string &directory, const std::string &wildcard, bool includeDirectories = true ) { using namespace Nuclex::Storage; using namespace Nuclex::Storage::FileSystem; WIN32_FIND_DATAW findData = {0}; // Build a search mask that the FindFirstFile() function understands std::string searchMask = getSearchMask(directory, wildcard); HANDLE searchHandle = TFileApi::FindFirstFile(searchMask, findData); if(searchHandle == INVALID_HANDLE_VALUE) { return std::make_pair(StringVector(), StringVector()); } // Process everything the file API enumerates for us StringVector directories, files; FindHandleScope findHandleScope(searchHandle); { for(;;) { if(isFile(findData.dwFileAttributes)) { std::string filename = Helpers::StringHelper::Utf8FromWideChar(findData.cFileName); files.push_back(TPathHelper::MakeAbsolute(directory, filename)); } else if(includeDirectories) { if((findData.cFileName[0] != L'\0') && (findData.cFileName[0] != L'.')) { std::string filename = Helpers::StringHelper::Utf8FromWideChar(findData.cFileName); directories.push_back(TPathHelper::MakeAbsolute(directory, filename)); } } // Advance to the next file or directory if(!TFileApi::FindNextFile(searchHandle, findData)) { break; } } } // findhandleScope return std::make_pair(files, directories); } /// Determines if the specified attributes indicate a file /// Attributes that will be checked /// True if the attributes indicated a file, otherwise false private: static bool isFile(DWORD fileAttributes) { return ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && ((fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0); } /// Absolute path of the directory private: std::string path; /// The directory's name private: std::string name; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Storage::FileSystem #endif // defined(NUCLEX_STORAGE_WIN32) || defined(NUCLEX_STORAGE_WINRT) #endif // NUCLEX_STORAGE_FILESYSTEM_GENERIC_GENERICWINDOWSCONTAINER_H