// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #ifndef FILE_IO_H_89578342758342572345 #define FILE_IO_H_89578342758342572345 #include "file_error.h" #include "file_access.h" #include "serialize.h" #include "crc.h" #include "guid.h" namespace zen { const char LINE_BREAK[] = "\n"; //since OS X Apple uses newline, too /* OS-buffered file IO optimized for - sequential read/write accesses - better error reporting - long path support - follows symlinks */ class FileBase { public: using FileHandle = int; static const int invalidFileHandle = -1; FileHandle getHandle() { return hFile_; } //Windows: use 64kB ?? https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938632%28v=technet.10%29 //Linux: use st_blksize? //macOS: use f_iosize? static size_t getBlockSize() { return 128 * 1024; }; const Zstring& getFilePath() const { return filePath_; } protected: FileBase(FileHandle handle, const Zstring& filePath) : hFile_(handle), filePath_(filePath) {} ~FileBase(); void close(); //throw FileError -> optional, but good place to catch errors when closing stream! private: FileBase (const FileBase&) = delete; FileBase& operator=(const FileBase&) = delete; FileHandle hFile_ = invalidFileHandle; const Zstring filePath_; }; //----------------------------------------------------------------------------------------------- class FileInput : public FileBase { public: FileInput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorFileLocked FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! size_t read(void* buffer, size_t bytesToRead); //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! private: size_t tryRead(void* buffer, size_t bytesToRead); //throw FileError, ErrorFileLocked; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0! const IOCallback notifyUnbufferedIO_; //throw X std::vector memBuf_ = std::vector(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_= 0; }; class FileOutput : public FileBase { public: FileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! ~FileOutput(); void reserveSpace(uint64_t expectedSize); //throw FileError void write(const void* buffer, size_t bytesToWrite); //throw FileError, X void flushBuffers(); //throw FileError, X //caveat: does NOT flush OS or hard disk buffers like e.g. FlushFileBuffers()! void finalize(); /*= flushBuffers() + close()*/ //throw FileError, X private: size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0 IOCallback notifyUnbufferedIO_; //throw X std::vector memBuf_ = std::vector(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_ = 0; }; //----------------------------------------------------------------------------------------------- //native stream I/O convenience functions: class TempFileOutput { public: TempFileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : //throw FileError filePath_(filePath), tmpFile_(tmpFilePath_, notifyUnbufferedIO) {} //throw FileError, (ErrorTargetExisting) void reserveSpace(uint64_t expectedSize) { tmpFile_.reserveSpace(expectedSize); } //throw FileError void write(const void* buffer, size_t bytesToWrite) { tmpFile_.write(buffer, bytesToWrite); } //throw FileError, X FileOutput& refTempFile() { return tmpFile_; } void commit() //throw FileError, X { tmpFile_.finalize(); //throw FileError, X //take ownership: ZEN_ON_SCOPE_FAIL( try { removeFilePlain(tmpFilePath_); /*throw FileError*/ } catch (FileError&) {}); //operation finished: move temp file transactionally moveAndRenameItem(tmpFilePath_, filePath_, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting) } private: //generate (hopefully) unique file name to avoid clashing with unrelated tmp file const Zstring filePath_; const Zstring shortGuid_ = printNumber(Zstr("%04x"), static_cast(getCrc16(generateGUID()))); const Zstring tmpFilePath_ = filePath_ + Zstr('.') + shortGuid_ + Zstr(".tmp"); FileOutput tmpFile_; }; [[nodiscard]] std::string getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X //overwrites if existing + transactional! :) void setFileContent(const Zstring& filePath, const std::string& bytes, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X } #endif //FILE_IO_H_89578342758342572345