/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FUD_C_FILE_HPP #define FUD_C_FILE_HPP #include "fud_result.hpp" #include "fud_status.hpp" #include "fud_string.hpp" #include #include namespace fud { /** \brief Access Modes for */ enum class CFileMode : uint8_t { ReadOnly, ReadWrite, WriteTruncate, ReadWriteTruncate, WriteAppend, ReadWriteAppend, }; constexpr const char* CBinaryFileModeFromFlags(CFileMode mode) { switch (mode) { case CFileMode::ReadOnly: return "rb"; case CFileMode::ReadWrite: return "r+b"; case CFileMode::WriteTruncate: return "wb"; case CFileMode::ReadWriteTruncate: return "w+b"; case CFileMode::WriteAppend: return "ab"; case CFileMode::ReadWriteAppend: return "a+b"; default: return ""; } } constexpr const char* CTextFileModeFromFlags(CFileMode mode) { switch (mode) { case CFileMode::ReadOnly: return "r"; case CFileMode::ReadWrite: return "r+"; case CFileMode::WriteTruncate: return "w"; case CFileMode::ReadWriteTruncate: return "w+"; case CFileMode::WriteAppend: return "a"; case CFileMode::ReadWriteAppend: return "a+"; default: return ""; } } struct [[nodiscard]] ReadResult { size_t bytesRead{0}; FudStatus status{FudStatus::Success}; }; struct [[nodiscard]] WriteResult { size_t bytesWritten{0}; FudStatus status{FudStatus::Success}; }; namespace detail { template class CFile { public: [[nodiscard]] bool isOpen() const { const auto& self = static_cast(*this); return self.m_file != nullptr; } FudStatus open() { auto& self = static_cast(*this); if (!self.m_filename.valid()) { return FudStatus::ObjectInvalid; } self.m_file = fopen(self.m_filename.c_str(), self.m_mode.c_str()); return self.m_file != nullptr ? FudStatus::Success : FudStatus::Failure; } void close() { auto& self = static_cast(*this); if (self.m_file != nullptr) { fclose(self.m_file); self.m_file = nullptr; } } const FILE* file() const { const auto& self = static_cast(*this); return self.m_file; } FILE* file() { auto& self = static_cast(*this); return self.m_file; } [[nodiscard]] Result size() const { const auto& self = static_cast(*this); using RetType = Result; if (!self.isOpen()) { return RetType::error(FudStatus::OperationInvalid); } auto seekStatus = fseek(self.m_file, 0, SEEK_END); if (seekStatus != 0) { return RetType::error(FudStatus::Failure); } auto fileSizeResult = ftell(self.m_file); if (fileSizeResult < 0) { return RetType::error(FudStatus::Failure); } auto fileSize = static_cast(fileSizeResult); auto resetStatus = self.reset(); if (resetStatus != FudStatus::Success) { return RetType::error(resetStatus); } return RetType::okay(fileSize); } [[nodiscard]] ReadResult read(void* destination, size_t destinationSize, size_t length) { auto& self = static_cast(*this); return self.read(destination, destinationSize, length, 0); } [[nodiscard]] ReadResult read(void* destination, size_t destinationSize, size_t length, size_t offset) { auto& self = static_cast(*this); ReadResult result{}; if (length == 0) { return result; } if (destination == nullptr) { result.status = FudStatus::NullPointer; return result; } if (offset > LONG_MAX || SIZE_MAX - offset < length || destinationSize < length) { result.status = FudStatus::InvalidInput; return result; } auto fileSizeResult = self.size(); if (fileSizeResult.isError()) { result.status = fileSizeResult.getError(); return result; } auto fileSize = fileSizeResult.getOkay(); if (offset + length > fileSize) { result.status = FudStatus::InvalidInput; return result; } auto seekResult = fseek(self.m_file, static_cast(offset), SEEK_SET); if (seekResult != 0) { result.status = FudStatus::Failure; return result; } auto* destBytes = static_cast(destination); result.bytesRead = fread(destBytes, 1, length, self.m_file); static_cast(self.reset()); if (result.bytesRead != length) { result.status = FudStatus::Partial; } else { result.status = FudStatus::Success; } return result; } template [[nodiscard]] ReadResult read(T& destination, size_t length) { auto& self = static_cast(*this); return self.read(destination, length, 0); } template [[nodiscard]] ReadResult read(T& destination, size_t length, size_t offset) { auto& self = static_cast(*this); return self.read(&destination, sizeof(destination), length, offset); } [[nodiscard]] WriteResult write(const void* source, size_t sourceSize, size_t length) { auto& self = static_cast(*this); auto offsetResult = self.size(); if (offsetResult.isError()) { return WriteResult{0, offsetResult.getError()}; } return self.write(source, sourceSize, length, offsetResult.getOkay()); } [[nodiscard]] WriteResult write(const void* source, size_t sourceSize, size_t length, size_t offset) { auto& self = static_cast(*this); WriteResult result{}; if (length == 0) { return result; } if (source == nullptr) { result.status = FudStatus::NullPointer; return result; } if (offset > LONG_MAX || SIZE_MAX - offset < length || sourceSize < length) { result.status = FudStatus::InvalidInput; return result; } auto fileSizeResult = size(); if (fileSizeResult.isError()) { result.status = fileSizeResult.getError(); return result; } // TODO: find the proper way of handling a write beyond the end of the file auto fileSize = fileSizeResult.getOkay(); int seekResult; if (offset > fileSize) { seekResult = fseek(self.m_file, 0, SEEK_END); } else { seekResult = fseek(self.m_file, static_cast(offset), SEEK_SET); } if (seekResult != 0) { result.status = FudStatus::Failure; return result; } auto* sourceBytes = static_cast(source); result.bytesWritten = fwrite(sourceBytes, 1, length, self.m_file); static_cast(self.reset()); if (result.bytesWritten != length) { result.status = FudStatus::Partial; } else { result.status = FudStatus::Success; } return result; } template [[nodiscard]] WriteResult write(const T& source) { auto& self = static_cast(*this); return self.write(source, sizeof(source), sizeof(source)); } template [[nodiscard]] WriteResult write(const T& source, size_t sourceSize, size_t length) { auto& self = static_cast(*this); auto offsetResult = size(); if (offsetResult.isError()) { return WriteResult{0, offsetResult.getError()}; } return self.write(source, sourceSize, length, offsetResult.getOkay()); } template [[nodiscard]] WriteResult write(const T& source, size_t sourceSize, size_t length, size_t offset) { auto& self = static_cast(*this); return self.write(static_cast(&source), sourceSize, length, offset); } private: FudStatus reset() const { const auto& self = static_cast(*this); if (!isOpen()) { return FudStatus::OperationInvalid; } auto result = fseek(self.m_file, 0, SEEK_SET); return result == 0 ? FudStatus::Success : FudStatus::Failure; } }; } // namespace detail class CBinaryFile : public detail::CFile { friend class CFile; public: CBinaryFile(const String& filename, CFileMode mode); CBinaryFile(const String& filename, CFileMode mode, const String& extraFlags); CBinaryFile(const CBinaryFile& rhs) = delete; CBinaryFile(CBinaryFile&& rhs); ~CBinaryFile(); CBinaryFile& operator=(const CBinaryFile& rhs) = delete; CBinaryFile& operator=(CBinaryFile&& rhs); private: String m_filename; String m_extraFlags{}; String m_mode; CFileMode m_modeFlags; FILE* m_file{nullptr}; }; class CTextFile : public detail::CFile { friend class CFile; public: CTextFile(const String& filename, CFileMode mode); CTextFile(const String& filename, CFileMode mode, const String& extraFlags); CTextFile(const CTextFile& rhs) = delete; CTextFile(CTextFile&& rhs); ~CTextFile(); CTextFile& operator=(const CTextFile& rhs) = delete; CTextFile& operator=(CTextFile&& rhs); private: String m_filename; String m_extraFlags{}; String m_mode; CFileMode m_modeFlags; FILE* m_file{nullptr}; }; } // namespace fud #endif