FileObject.h
#ifndef __FILE_OBJECT_H__
#define __FILE_OBJECT_H__
#include <string>
#include <list>
#include <vector>
#include <fstream>
#if UNIX
#define PATH_DELIMITER '/'
#define ROOT_DIRECTORY "/"
#elif WINDOWS
#define PATH_DELIMITER '\\'
#define ROOT_DIRECTORY "C:"
#endif
struct FileObject {
public:
enum Type {
eFile,
eDirectory,
eInvalid,
};
enum OpenMode {
eClosed,
eRead,
eWrite,
};
private:
Type m_iType;
OpenMode m_iMode;
std::string m_strPath;
std::fstream m_stream;
public: // конструкторы & деструкторы
// создание нового пустого объекта
FileObject::FileObject(void)
: m_iType(eInvalid), m_iMode(eClosed)
{}
FileObject::FileObject(nullptr_t)
: m_iType(eInvalid), m_iMode(eClosed)
{}
// создание нового файлового объекта из строки пути.
// если путь указывает на файл или директорию, то будет корректно инициализирован тип файлового объекта.
// если путь не абсолютный, то предполагается, что это путь относительно текущей рабочей директории.
FileObject::FileObject(const std::string & strPath)
: m_iType(eInvalid), m_iMode(eClosed) {
init(strPath);
}
FileObject::FileObject(const char * szPath)
: m_iType(eInvalid), m_iMode(eClosed) {
if ( szPath && strlen(szPath) )
init(std::string(szPath));
}
// удаление файлового объекта и всех связанных с ним ресурсов.
FileObject::~FileObject(void) {
close();
}
public:
// семантика копирования запрещена (т.к. файловый объект содержит в себе std::fstream).
FileObject(const FileObject &) = delete;
FileObject & operator =(const FileObject &) = delete;
// семантика перемещения реализована явно.
FileObject(FileObject && rhs)
: m_strPath(std::move(rhs.m_strPath)),
m_iType(std::move(rhs.m_iType)),
m_iMode(std::move(rhs.m_iMode)),
m_stream(std::move(rhs.m_stream))
{}
FileObject & operator =(FileObject && rhs) {
m_strPath = std::move(rhs.m_strPath);
m_iType = std::move(rhs.m_iType);
m_iMode = std::move(rhs.m_iMode);
m_stream = std::move(rhs.m_stream);
return *this;
}
public:
// методы-аксессоры только для чтения
Type type(void) const {
return m_iType;
}
OpenMode mode(void) const {
return m_iMode;
}
bool is_open(void) const {
return m_stream.is_open();
}
bool empty(void) const {
return m_strPath.empty();
}
const std::string & path(void) const {
return m_strPath;
}
public:
// создание нового файлового объекта по строке относительного пути. если файловый объект,
// у которого вызывается метод, не указывает на существующую директорию, то новый объект
// возвращается пустой. если новый файловый объект указывает на реально существующий файл
// или директорию, то у нового файлового объекта будет корректно инициализирован тип.
FileObject makeChild(const std::string & strSubpath) const;
FileObject FileObject::makeChild(nullptr_t) const {
return FileObject();
}
// проверка, указывает ли файловый объект на реально существующий файл или директорию
bool exists(void) const;
// создание файла или директории по пути, на который указывает файловый объект. если файл
// или директория уже реально существуют, то метод возвращает false.
bool create(Type iType);
// копирование файлового объекта в новое место. метод принимает файловый объект указывающий
// на директорию куда необходимо себя скопировать, и при успехе возвращает новый файловый
// объект указывающий на созданную копию. если файловый объект, у которого вызывают
// метод, является директорией, то эта директория будет скопирована рекурсивно со всем
// своим содержимым. если целевая директория не существует, то метод возвращает пустой
// файловый объект.
FileObject copyTo(const FileObject & destDir);
// удаление файла или директории с которой связан файловый объект. если файловый объект
// указывает на директорию, то директория удаляется рекурсивно вместе со всем содержимым.
// Примечание: в Windows все файлы внутри директории должны быть закрыты.
bool remove(void);
// перечисление (нерекурсивное) содержимого директории. специальные ссылки "." и "..", а так-же
// "скрытые" файлы начинающиеся с символа '.' в список не включаются. в случае пустой
// директории или ошибки возвращается пустой список.
std::list < FileObject > listDirectory(void);
// вычисление размера файла в байтах. в случае, если файл не существует, либо файловый объект
// указывает не на файл, возвращается 0.
size_t getSize(void);
// чтение содержимого файла в строку. если файл был открыт для записи, то его содержимое
// сбрасывается на диск, файл закрывается и открывается заново в режиме чтения.
// в случае ошибки возвращается пустая строка.
template < typename CharT = char >
std::basic_string <CharT> readString(void) {
typedef std::basic_string <CharT> StringT;
if ( !reopenRead() )
return StringT();
const size_t nFileSize = getSize();
if ( !nFileSize )
return StringT();
const size_t nCount = nFileSize / sizeof(CharT);
StringT strResult(nCount + 1, CharT(0)); // allocate & fill
const std::streamoff nBytes =
m_stream.read(&strResult[0], nFileSize * sizeof(CharT)).gcount();
return strResult;
}
// чтение содержимого файла в виде списка строк. в каждой строке удаляются конечные символы
// '\r' и '\n'. если файл был открыт для записи, то его содержимое сбрасывается на диск,
// файл закрывается и открывается заново в режиме чтения. в случае ошибки возвращается
// пустой список.
template < typename CharT = char >
std::list < std::basic_string <CharT> > readLines(void) {
typedef std::basic_string <CharT> StringT;
typedef std::list <StringT> ListT;
if ( !reopenRead() )
return ListT();
ListT listResult;
while ( true ) {
StringT line;
if ( !std::getline(m_stream, line) )
return listResult;
StringT::size_type pos;
pos = line.find_last_of(CharT('\n'));
if ( pos != StringT::npos )
line.resize(pos);
// Also trim these characters, in case the file has Windows-style newlines
pos = line.find_last_of(CharT('\r'));
if ( pos != StringT::npos )
line.resize(pos);
listResult.push_back(line);
}
}
// чтение содержимого файла в виде двоичного массива (вектора).
template < typename T >
std::vector < T > readVector(size_t nCount) {
typedef std::vector < T > VectorT;
if ( nCount == 0 || !reopenRead() )
return VectorT();
VectorT vecResult(nCount, T(0));
const std::streamsize nItems = m_stream.read(vecResult.data(), nCount * sizeof(T)).gcount();
return vecResult;
}
template < typename T >
bool readBuffer(T * pData, size_t nCount) {
if ( nCount == 0 || !reopenRead() )
return false;
const std::streamsize nItems = m_stream.read(pData, nCount * sizeof(T)).gcount();
return true;
}
template < typename CharT = char >
bool writeString(const std::basic_string <CharT> & str) {
return writeBuffer(str.c_str(), str.length() * sizeof(CharT));
}
template < typename CharT = char >
bool writeLines(const std::list < std::basic_string <CharT> > & listStr) {
typedef std::basic_string <CharT> StringT;
typedef std::list <StringT> ListT;
if ( !reopenWrite() )
return false;
ListT::const_iterator it = listStr.begin();
CharT nl[2] = { CharT('\r'), CharT('\n') };
while ( it != listStr.end() ) {
const StringT & str = *it;
if ( !writeBuffer(str.c_str(), str.length() * sizeof(CharT)) )
return false;
if ( !writeBuffer(nl, sizeof(nl)) )
return false;
}
}
template < typename T >
bool writeVector(const std::vector< T > & vec) {
return writeBuffer(vec.data(), vec.size());
}
template < typename T >
bool writeBuffer(const T * pData, size_t nCount) {
if ( nCount == 0 || !reopenWrite() )
return false;
const std::streamoff nPos1 = m_stream.tellp();
const std::streamoff nPos2 = m_stream.write((char*)pData, nCount * sizeof(T)).tellp();
return (nPos2 - nPos1) == nCount * sizeof(T);
}
// вычисление базового имени объекта. например для файлового объекта "/foo/bar" базовое имя -
// "bar" вне зависимости от того, что такое "bar" - файл или директория.
std::string getBaseName(void);
// вычисление родительской директории объекта.
FileObject getParent(void);
// вычисление расширения объекта.
std::string getExt(void);
// сброс содержимого файла на диск и закрытие файла.
void close(void);
public:
// вычисление абсолютного пути до текущего исполняемого файла
static std::string GetExecPath(void);
// вычисление рабочей директории
static std::string GetCurrDir(void);
private:
void init(const std::string & strPath);
FileObject copyFileToDirectory(const FileObject & destDir);
FileObject copyDirectoryToDirectory(const FileObject & destDir);
bool reopenRead(void);
bool reopenWrite(void);
};
#endif // !__FILE_OBJECT_H__
FileObject.cpp
// Must be declared before stdlib, shouldn't have any effect on Windows builds
#define _XOPEN_SOURCE 700
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <string>
#include <vector>
#include <sys/stat.h>
#include "FileObject.h"
#if WINDOWS
#include <Windows.h>
#include <Shellapi.h>
#elif UNIX
#if LINUX
#include <errno.h>
#elif MACOSX
#include <mach-o/dyld.h>
#endif
#include <dirent.h>
#include <ftw.h>
#include <unistd.h>
#endif
static const int kFileNameStringLength = 1024;
static const int kFileMaxRecursionDepth = 32;
static const char * kFileNameInvalidCharacters = "*:?<>|";
static bool _isAbsolutePath(const std::string & strPath) {
#if WINDOWS
if ( strPath.length() > 3 ) {
// Check for strings which start with a drive letter, ie C:\file
if ( strPath[1] == ':' && strPath[2] == PATH_DELIMITER )
return true;
// Check for network paths (like "\\SERVER\file") or absolute paths (like "\Users")
if ( strPath[0] == PATH_DELIMITER )
return true;
}
#else
if ( strPath.length() > 1 && strPath[0] == PATH_DELIMITER )
return true;
#endif
return false;
}
static std::string _convertRelativePathToAbsolute(const std::string & strPath) {
return FileObject::GetCurrDir() + PATH_DELIMITER + strPath;
}
static std::string _buildAbsolutePath(const std::string & strParentDir, const std::string & strFileName) {
if ( strParentDir.empty() )
return std::string();
if ( strFileName.empty() )
return std::string();
if ( _isAbsolutePath(strFileName) )
return strFileName;
const std::string strParentDirAbsPath = _isAbsolutePath(strParentDir) ?
strParentDir : _convertRelativePathToAbsolute(strParentDir);
return strParentDirAbsPath + PATH_DELIMITER + strFileName;
}
static bool _isDirectory(const std::string & strPath) {
#if UNIX
stat buffer;
return (stat(strPath.c_str(), &buffer) == 0) && (S_ISDIR(buffer.st_mode) != 0);
#elif WINDOWS
const DWORD nFileAttr = ::GetFileAttributesA(strPath.c_str());
return (nFileAttr != INVALID_FILE_ATTRIBUTES) && ((nFileAttr & FILE_ATTRIBUTE_DIRECTORY) != 0);
#endif
}
static bool _pathContainsInvalidChars(const std::string & strPath) {
size_t i = 0;
#if WINDOWS
// The colon is not allowed in pathnames (even on Windows), however it is
// present in absolute pathnames for the drive letter. Thus we must skip
// the first 3 characters of the path when dealing with absolute paths on
// this platform.
if ( _isAbsolutePath(strPath) )
i = 2;
#endif
if ( strPath.empty() )
return false;
return (strPath.find_first_of(kFileNameInvalidCharacters, i) != std::string::npos);
}
void FileObject::init(const std::string & strPath) {
if ( strPath.empty() )
return;
if ( _pathContainsInvalidChars(strPath) )
return;
if ( _isAbsolutePath(strPath) )
m_strPath = strPath;
else {
const std::string strCurrentDirectory = GetCurrDir();
if ( strCurrentDirectory.empty() )
return;
m_strPath = _buildAbsolutePath(strCurrentDirectory, strPath);
}
m_strPath.reserve(sizeof(std::string::value_type) * (m_strPath.length() + 2));
m_strPath.resize(m_strPath.length() + 2);
m_strPath.resize(m_strPath.length() - 2);
if ( exists() )
m_iType = _isDirectory(m_strPath) ? eDirectory : eFile;
}
FileObject FileObject::makeChild(const std::string & strSubpath) const {
if ( !exists() )
return FileObject();
if ( !_isDirectory(m_strPath) )
return FileObject();
if ( strSubpath.empty() )
return FileObject();
if ( _pathContainsInvalidChars(strSubpath) )
return FileObject();
if ( _isAbsolutePath(strSubpath) )
return FileObject();
FileObject child;
child.m_strPath = _buildAbsolutePath(m_strPath, strSubpath);
child.m_strPath.reserve(sizeof(std::string::value_type) * (child.m_strPath.length() + 2));
child.m_strPath.resize(child.m_strPath.length() + 2);
child.m_strPath.resize(child.m_strPath.length() - 2);
if ( child.exists() )
child.m_iType = _isDirectory(child.m_strPath) ? eDirectory : eFile;
return child;
}
bool FileObject::exists(void) const {
if ( m_strPath.empty() )
return false;
#if UNIX
stat fileStat;
return stat(m_strPath.c_str(), &fileStat) == 0;
#elif WINDOWS
return ::GetFileAttributesA(m_strPath.c_str()) != INVALID_FILE_ATTRIBUTES;
#else
return false;
#endif
}
bool FileObject::create(Type iType) {
if ( exists() )
return false;
else if ( m_strPath.empty() )
return false;
switch ( iType ) {
case eFile:
m_stream.open(m_strPath, std::ios::out | std::ios::binary);
if ( m_stream.is_open() ) {
m_iType = eFile;
m_iMode = eWrite;
return true;
}
break;
case eDirectory:
#if UNIX
if ( ::mkdir(m_strPath.c_str(), 0755) == 0 )
#elif WINDOWS
if ( ::CreateDirectoryA(m_strPath.c_str(), NULL) )
#endif
{
m_iType = eDirectory;
return true;
}
break;
default:
break;
}
return false;
}
FileObject FileObject::copyFileToDirectory(const FileObject & destDir) {
// Close and re-open file to make sure that we start reading at the beginning
close();
m_stream.open(m_strPath, std::ios::in | std::ios::binary);
if ( !m_stream.is_open() )
return FileObject();
const std::string strBaseName = getBaseName();
if ( strBaseName.empty() )
return FileObject();
FileObject copy = destDir.makeChild(strBaseName);
if ( !copy.create(eFile) )
return FileObject();
const size_t nSelfSize = getSize();
if ( !nSelfSize )
// If the source file is empty, then creating the result file is good
// enough and we can return here.
return copy;
return copy;
}
FileObject FileObject::copyDirectoryToDirectory(const FileObject & destDir) {
// Get the basename first, because if it fails then there's no point in doing
// the actual copy.
const std::string strBaseName = getBaseName();
if ( strBaseName.empty() )
return FileObject();
#if UNIX
// Using nftw() is a real pain here, because the recursive callback will start
// at the top of the tree and work back up, meaning that for each file we'd
// need to recursively mkdir the basename, etc.
// So this is a bit lazy but actually more foolproof
const std::string strCopyCommand =
"/bin/cp -r \"" + m_strPath + "\" \"" + destDir->m_strPath + "\"";
const int nSystemResult = system(strCopyCommand.c_str());
return ( WEXITSTATUS(nSystemResult) 1 = 0 ) ? FileObject() : destDir.makeChild(strBaseName);
#elif WINDOWS
SHFILEOPSTRUCTA fileOperation;
memset(&fileOperation, 0, sizeof(fileOperation));
fileOperation.wFunc = FO_COPY;
fileOperation.pFrom = m_strPath.c_str(); // 2*0
fileOperation.pTo = destDir.m_strPath.c_str(); // 2*0
fileOperation.fFlags = FOF_NO_UI;
return (::SHFileOperationA(&fileOperation) != 0) ? FileObject() : destDir.makeChild(strBaseName);
#endif
}
FileObject FileObject::copyTo(const FileObject & destDir) {
if ( !destDir.exists() )
return FileObject();
if ( !_isDirectory(destDir.m_strPath) )
return FileObject();
if ( !exists() )
return FileObject();
switch ( m_iType ) {
case eFile: return copyFileToDirectory(destDir);
case eDirectory: return copyDirectoryToDirectory(destDir);
default: return FileObject();
}
}
#if UNIX
static int _removeCallback(const char * szPath, const stat *, int, FTW *) {
return ::remove(szPath);
}
#endif
bool FileObject::remove(void) {
bool bResult = false;
#if WINDOWS
SHFILEOPSTRUCTA fileOperation = { 0 };
#endif
if ( exists() ) {
switch ( m_iType ) {
case eFile:
// Yes, this seems a bit silly, but otherwise we threaten to leak resources
close();
bResult = (::remove(m_strPath.c_str()) == 0);
break;
case eDirectory:
#if UNIX
bResult = (::nftw(m_strPath.c_str(), _removeCallback, kFileMaxRecursionDepth, FTW_DEPTH | FTW_PHYS) == 0);
#elif WINDOWS
memset(&fileOperation, 0, sizeof(fileOperation));
fileOperation.wFunc = FO_DELETE;
fileOperation.pFrom = m_strPath.c_str(); // 0 * 2
fileOperation.pTo = nullptr;
fileOperation.fFlags = FOF_NO_UI | FOF_NOCONFIRMATION | FOF_SILENT;
bResult = (::SHFileOperationA(&fileOperation) == 0);
#endif
break;
default:
break;
}
}
if ( bResult )
m_iType = eInvalid;
return bResult;
}
std::list < FileObject > FileObject::listDirectory(void)
{
std::list < FileObject > items;
#if UNIX
DIR * pDirectory = ::opendir(m_strPath.c_str());
if ( !pDirectory )
return std::list < FileObject >();
dirent * pEntry;
while ( (pEntry = ::readdir(pDirectory)) != nullptr ) {
if ( pEntry->d_name[0] != '.' )
items.push_back(makeChild(std::string(pEntry->d_name)));
}
::closedir(pDirectory);
#elif WINDOWS
WIN32_FIND_DATAA findData;
const std::string strSearch = m_strPath + "\\*";
const HANDLE hFind = ::FindFirstFileA(strSearch.c_str(), &findData);
if ( hFind == INVALID_HANDLE_VALUE )
return std::list < FileObject >();
do {
if ( findData.cFileName[0] != '.' )
items.push_back(makeChild(std::string(findData.cFileName)));
}
while ( ::FindNextFileA(hFind, &findData) != 0 );
::FindClose(hFind);
#else
#endif
return items;
}
size_t FileObject::getSize(void)
{
#if UNIX
struct stat fileStat;
if ( m_strPath.empty() )
return 0;
if ( stat(m_strPath.c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode) )
// Yes, this will result in a loss of precision, but both fread and fwrite
// take a size_t argument, which is what this function is mostly used for.
return fileStat.st_size;
#elif WINDOWS
WIN32_FIND_DATAA findData;
HANDLE hFind = ::FindFirstFileA(m_strPath.c_str(), &findData);
if ( hFind == INVALID_HANDLE_VALUE )
return 0;
FindClose(hFind);
return size_t(findData.nFileSizeLow | uint64_t(findData.nFileSizeHigh) << 32);
#else
return 0;
#endif
}
bool FileObject::reopenRead(void) {
if ( m_iType != eFile )
return false;
if ( m_iMode != eRead && m_stream.is_open() )
close();
if ( m_stream.is_open() )
return true;
m_stream.open(m_strPath, std::ios::in | std::ios::binary);
if ( !m_stream.is_open() )
return false;
m_iMode = eRead;
return true;
}
bool FileObject::reopenWrite(void) {
if ( m_iType != eFile )
return false;
if ( !exists() )
return false;
if ( m_iMode != eWrite && m_stream.is_open() )
close();
if ( m_stream.is_open() )
return true;
m_stream.open(m_strPath, std::ios::out | std::ios::binary);
if ( !m_stream.is_open() )
return false;
m_iMode = eWrite;
return true;
}
std::string FileObject::getBaseName(void) {
if ( m_strPath.empty() )
return std::string();
const std::string::size_type pos = m_strPath.find_last_of(PATH_DELIMITER);
return (pos == std::string::npos) ? m_strPath : m_strPath.substr(pos + 1);
}
FileObject FileObject::getParent(void) {
if ( m_strPath.empty() )
return FileObject();
const std::string::size_type pos = m_strPath.find_last_of(PATH_DELIMITER);
return FileObject(
(pos == std::string::npos) ? m_strPath : m_strPath.substr(0, pos)
);
}
std::string FileObject::getExt(void) {
const std::string strBaseName = getBaseName();
if ( strBaseName.empty() )
return std::string();
const std::string::size_type pos = strBaseName.find_last_of('.');
return (pos != std::string::npos) ? strBaseName.substr(pos + 1) : std::string();
}
std::string FileObject::GetExecPath(void) {
char strExecutablePath[kFileNameStringLength] = { 0 };
#if LINUX
const ssize_t nResult = ::readlink("/proc/self/exe", strExecutablePath, kFileNameStringLength);
if ( nResult < 0 )
return std::string();
#elif MACOSX
// _NSGetExecutablePath(strExecutablePath, (uint32_t *)&strExecutablePath->capacity);
#elif WINDOWS
::GetModuleFileNameA(nullptr, strExecutablePath, kFileNameStringLength);
#endif
return std::string(strExecutablePath);
}
std::string FileObject::GetCurrDir(void) {
char strCurrentDirectory[kFileNameStringLength] = { 0 };
#if UNIX
if ( !::getcwd(strCurrentDirectory, kFileNameStringLength) )
return std::string();
#elif WINDOWS
::GetCurrentDirectoryA(kFileNameStringLength, strCurrentDirectory);
#endif
return std::string(strCurrentDirectory);
}
void FileObject::close(void) {
if ( m_stream.is_open() && m_iType == eFile ) {
m_stream.flush();
m_stream.close();
m_iMode = eClosed;
}
}