// Copyright (c) 2023 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "File.hpp" using namespace Dawn; std::string File::normalizeSlashes(std::string str) { size_t i = 0; while(i < str.size()) { auto c = str[i]; if(c == '\\' || c == '/') str[i] = FILE_PATH_SEP; ++i; } return str; } void File::mkdirp(std::string path) { std::string buffer; char c; size_t i = 0; bool_t inFile; bool_t hasMore; inFile = false; hasMore = false; while(c = path[i]) { if((c == '\\' || c == '/') && i > 0) { fileMkdir(buffer.c_str(), 0755); inFile = false; hasMore = false; buffer += FILE_PATH_SEP; i++; continue; } if(c == '.') inFile = true; hasMore = true; buffer += c; i++; } if(!inFile && hasMore) { fileMkdir(buffer.c_str(), 0755); } } // File::File(std::string filename) { this->filename = File::normalizeSlashes(filename); } bool_t File::open(enum FileMode mode) { assertNull(this->file, "File is already open"); this->mode = mode; this->file = fopen( this->filename.c_str(), mode == FILE_MODE_READ ? "rb" : "wb" ); if(this->file == NULL) return false; if(mode == FILE_MODE_READ) { fseek(this->file, 0, SEEK_END); this->length = ftell(this->file); fseek(this->file, 0, SEEK_SET); if(this->length <= 0) { this->close(); return false; } } else { this->length = 0; } return true; } bool_t File::isOpen() { return this->file != nullptr; } bool_t File::exists() { if(this->file != nullptr) return true; FILE *f = fopen(this->filename.c_str(), "rb"); if(f == NULL || f == nullptr) return false; fclose(f); return true; } void File::close() { assertNotNull(this->file, "File::close: File is not open"); fclose(this->file); this->file = nullptr; } bool_t File::mkdirp() { File::mkdirp(this->filename); return true; } bool_t File::readString(std::string *out) { assertNotNull(out, "File::readString: Out cannot be null"); if(!this->isOpen()) { if(!this->open(FILE_MODE_READ)) return false; } assertTrue(this->mode == FILE_MODE_READ, "File::readString: File must be open in read mode"); out->clear(); size_t i = 0; char buffer[FILE_BUFFER_SIZE + 1];// +1 for null term while(i != this->length) { size_t amt = mathMin(FILE_BUFFER_SIZE, (this->length - i)); auto amtRead = fread(buffer, sizeof(char), amt, this->file); if(amtRead != amt) return false; i += amtRead; buffer[amtRead] = '\0'; out->append(buffer); } return true; } size_t File::readAhead(char *buffer, size_t max, char needle) { assertNotNull(buffer, "File::readAhead: Buffer cannot be null"); assertTrue(max > 0, "File::readAhead: Max must be greater than 0"); if(!this->isOpen()) { if(!this->open(FILE_MODE_READ)) return 0; } assertTrue(this->mode == FILE_MODE_READ, "File::readAhead: File must be open in read mode"); // Buffer size_t pos = ftell(this->file); size_t amountLeftToRead = mathMin(max, this->length - pos); char temporary[FILE_BUFFER_SIZE]; size_t n = 0; while(amountLeftToRead > 0) { size_t toRead = mathMin(amountLeftToRead, FILE_BUFFER_SIZE); amountLeftToRead -= toRead; // Read bytes size_t read = fread(temporary, sizeof(char), toRead, this->file); // Read error? if(toRead != read) return 0; // Did we read the needle? size_t i = 0; while(i < read) { char c = temporary[i++]; if(c == needle) { return n; } else { buffer[n++] = c; } } } // Needle was not found. return -1; } size_t File::readToBuffer(char **buffer) { if(!this->isOpen()) { if(!this->open(FILE_MODE_READ)) return 0; } assertTrue(this->mode == FILE_MODE_READ, "File::readToBuffer: File must be open in read mode"); if((*buffer) == nullptr) *buffer = (char*)malloc(this->length); fseek(this->file, 0, SEEK_SET); auto l = fread((*buffer), sizeof(char), this->length, this->file); return l; } size_t File::readRaw(char *buffer, size_t max) { assertNotNull(buffer, "File::readRaw: Buffer cannot be null"); assertTrue(max > 0, "File::readRaw: Max must be greater than 0"); if(!this->isOpen()) { if(!this->open(FILE_MODE_READ)) return 0; } assertTrue(this->mode == FILE_MODE_READ, "File::readRaw: File must be open in read mode"); return fread(buffer, sizeof(char), max, this->file); } bool_t File::writeString(std::string in) { if(!this->isOpen() && !this->open(FILE_MODE_WRITE)) return false; assertTrue(this->mode == FILE_MODE_WRITE, "File::writeString: File must be open in write mode"); return this->writeRaw((char *)in.c_str(), in.size()) && this->length == in.size(); } bool_t File::writeRaw(char *data, size_t len) { if(!this->isOpen() && !this->open(FILE_MODE_WRITE)) return false; assertTrue(this->mode == FILE_MODE_WRITE, "File::writeRaw: File must be open in write mode"); assertTrue(len > 0, "File::writeRaw: Length must be greater than 0"); this->length = fwrite(data, sizeof(char_t), len, this->file); return true; } bool_t File::copyRaw(File *otherFile, size_t length) { assertTrue(length > 0, "File::copyRaw: Length must be greater than 0"); assertTrue(otherFile->isOpen(), "File::copyRaw: Other file must be open"); char buffer[FILE_BUFFER_SIZE]; size_t amountLeftToRead = length; size_t read = 0; while(amountLeftToRead > 0) { auto iRead = otherFile->readRaw(buffer, mathMin(FILE_BUFFER_SIZE, amountLeftToRead)); if(iRead == 0) return false; this->writeRaw(buffer, iRead); amountLeftToRead -= iRead; read += iRead; } assertTrue(read == length, "File::copyRaw: Read length does not match expected length"); return true; } void File::setPosition(size_t n) { fseek(this->file, 0, SEEK_SET); fseek(this->file, n, SEEK_CUR); } std::string File::getFileName(bool_t withExt) { // Remove all but last slash std::string basename; size_t lastSlash = this->filename.find_last_of('/'); if(lastSlash == std::string::npos) { basename = this->filename; } else { basename = this->filename.substr(lastSlash + 1); } // Do we need to remove ext? if(withExt) return basename; size_t lastDot = basename.find_last_of('.'); if(lastDot == std::string::npos) return basename; return basename.substr(0, lastDot); } std::string File::getExtension() { // Remove all but last slash std::string basename; size_t lastSlash = this->filename.find_last_of('/'); if(lastSlash == std::string::npos) { basename = this->filename; } else { basename = this->filename.substr(lastSlash + 1); } size_t lastDot = basename.find_last_of('.'); if(lastDot == std::string::npos) return ""; return basename.substr(lastDot + 1); } File::~File() { if(this->file != nullptr) this->close(); }