// Copyright 2022, FOSS-VG Developers and Contributers // // Author(s): // Bodgemaster, Shwoomple // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, version 3. // // This program 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 GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // version 3 along with this program. // If not, see https://www.gnu.org/licenses/agpl-3.0.en.html #include #include #include #include #include #include "tinyutf8/tinyutf8.h" #include "file.hpp" File::File(std::string path, char mode, uint64_t cursorPosition): mode(mode), path(path), cursorPosition(cursorPosition){ std::filesystem::path filePath = path; if(this->mode == 'w' || (this->mode == 'a' && !std::filesystem::exists(filePath))){ this->size = ErrorOr(0); }else{ this->size = ErrorOr(std::filesystem::file_size(filePath)); } this->open(); } File::~File() { if (this->isOpen) { this->fileStream.close(); } } void File::open(){ switch(this->mode){ case 'w': this->fileStream.open(this->path, std::fstream::out | std::fstream::binary); break; case 'm': this->fileStream.open(this->path, std::fstream::out | std::fstream::in | std::fstream::binary); break; case 'r': this->fileStream.open(this->path, std::fstream::in | std::fstream::binary); break; case 'a': this->fileStream.open(this->path, std::fstream::app | std::fstream::binary); break; default: break; } std::filesystem::path filePath = this->path; this->size = ErrorOr(std::filesystem::file_size(filePath)); this->isOpen = this->fileStream.is_open(); } void File::close(){ this->fileStream.close(); this->isOpen = false; } bool File::eof() { return !this->size.isError && this->cursorPosition >= this->size.value; } ErrorOr File::readByte(){ if (!this->isOpen) { return ErrorOr(true, ErrorCodes::FILE_NOT_OPEN); } if (!this->size.isError && this->cursorPosition >= this->size.value) { return ErrorOr(true, ErrorCodes::OVERRUN); } uint8_t* nextPointer = new uint8_t; uint8_t nextByte; bool failure = false; try { this->fileStream.seekg(this->cursorPosition); this->fileStream.read(reinterpret_cast(nextPointer), 1); nextByte = *nextPointer; this->cursorPosition++; } catch (std::exception& e) { failure = true; } delete nextPointer; return failure? ErrorOr(true, ErrorCodes::UNKNOWN) : ErrorOr(nextByte); } ErrorOr> File::read(uint64_t bytes){ if (!this->isOpen) { return ErrorOr>(true, ErrorCodes::FILE_NOT_OPEN); } if (!this->size.isError && this->cursorPosition >= this->size.value+bytes) { return ErrorOr>(true, ErrorCodes::OVERRUN); } uint8_t* buffer = new uint8_t[bytes]; std::vector data; bool failure = false; try { this->fileStream.seekg(this->cursorPosition); this->fileStream.read(reinterpret_cast(buffer), bytes); data = std::vector(buffer, buffer+bytes); this->cursorPosition += bytes; } catch (std::exception& e) { failure = true; } delete[] buffer; return failure? ErrorOr>(true, ErrorCodes::UNKNOWN) : ErrorOr>(data); } ErrorOr File::readString(uint64_t bytes){ ErrorOr> data = this->read(bytes); if(data.isError){ return ErrorOr(true, data.errorCode); } std::string s; for(auto byte: data.value){ s.push_back(byte); } return ErrorOr(tiny_utf8::string(s)); } ErrorOrVoid File::writeByte(uint8_t byte){ bool failure = false; try{ this->fileStream << byte; }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } ErrorOrVoid File::write(std::vector data){ bool failure = false; try{ this->fileStream << std::string(data.begin(), data.end()); }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } ErrorOrVoid File::writeString(tiny_utf8::string data){ bool failure = false; try{ this->fileStream << data; }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } // Naive implementation of insertByte ErrorOrVoid File::insertByte(uint8_t byte){ bool failure = false; try{ uint8_t* buffer = new uint8_t[this->size.value]; std::vector readData; this->fileStream.read(reinterpret_cast(buffer), this->size.value); readData = std::vector(buffer, buffer+this->size.value); readData.insert(readData.begin()+this->cursorPosition, byte); this->fileStream.seekg(0); this->write(readData); this->cursorPosition++; delete buffer; }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } ErrorOrVoid File::insert(std::vector data){ bool failure = false; try{ uint8_t* buffer = new uint8_t[this->size.value]; std::vector readData; this->fileStream.read(reinterpret_cast(buffer), this->size.value); readData = std::vector(buffer, buffer+this->size.value); readData.insert(readData.begin()+this->cursorPosition, data.begin(), data.end()); this->fileStream.seekg(0); this->write(readData); this->cursorPosition += data.size(); delete buffer; }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } ErrorOrVoid File::insertString(tiny_utf8::string string){ bool failure = false; try{ uint8_t* buffer = new uint8_t[this->size.value]; tiny_utf8::string readData; this->fileStream.read(reinterpret_cast(buffer), this->size.value); readData = tiny_utf8::string((char *) buffer); readData.insert(readData.begin()+this->cursorPosition, string); this->fileStream.seekg(0); //TODO: fix hack. tinyutf8 appends "_utf-8" when readData is assigned: tiny_utf8::string((char *) buffer); this->writeString(readData.substr(0, readData.find("_utf-8"))); this->cursorPosition += string.size(); delete buffer; }catch(std::exception& e){ failure = true; } return failure ? ErrorOrVoid(true, ErrorCodes::UNKNOWN) : ErrorOrVoid(); } ErrorOr File::cutByte(){ bool failure = false; uint8_t byte; try{ uint8_t* buffer = new uint8_t[this->size.value]; std::vector readData; this->fileStream.read(reinterpret_cast(buffer), this->size.value); readData = std::vector(buffer, buffer+this->size.value); byte = readData[this->cursorPosition]; readData.erase(readData.begin() + this->cursorPosition); std::filesystem::resize_file(this->path, readData.size()); this->fileStream.seekg(0); this->write(readData); this->cursorPosition++; delete buffer; }catch(std::exception& e){ failure = true; } return failure ? ErrorOr(true, ErrorCodes::UNKNOWN) : ErrorOr(byte); } ErrorOr File::open(std::string path, char mode, uint64_t startPosition){ if (!std::filesystem::exists(path) && (mode == 'r' || mode == 'm')) { return ErrorOr(true, ErrorCodes::FILE_NOT_FOUND, nullptr); } //TODO: check access perms return ErrorOr(new File(path, mode, startPosition)); }