I wrote a file manager API in C++ to make the read-write operations easier in read/write cases, needing big structured data to/from multiple files. I want to get a review of the library if it's readable and easy to understand, and if it serves its purpose.
File.hpp
#ifndef FILESAPI_FILE_H
#define FILESAPI_FILE_H
#include <iostream>
#include <mutex>
#include <fstream>
#include <vector>
#include <boost/assert.hpp>
#include "../utilities/DesignText.hpp"
#include "../utilities/Exceptions.hpp"
namespace FilesApi {
/// Use in read/write for non-vectors overload operator. e.g: file >> rw_t<T>{val, 1};
template<typename T> struct rw_s {
T* val;
size_t val_size;
rw_s(T &value, size_t arr_size = 1) : val(&value), val_size(arr_size) {
assert(arr_size > 0);
}
rw_s(T *value, size_t arr_size = 1) : val(value), val_size(arr_size) {
assert(arr_size > 0);
assert(value != nullptr);
}
};
/// Wrapper function for creation of rw_t object, without need for specify the type after the function name
/// Instead of call: f << rw_t<int>(a, size);
/// Call: f << rw_soft(a, size);
template<typename T> rw_s<T> rw_soft(T &value, size_t arr_size = 1) {
return rw_s<T>(value, arr_size);
}
template<typename T> rw_s<T> rw_soft(T *value, size_t arr_size = 1) {
return rw_soft(*value, arr_size);
}
/**
* >> if file_mode is OPEN_IN_ACTION:
* SINGLE_AND_DONE - read/write single time, and then close the file.
* SINGLE_AND_MORE - read/write single time, but don't close the file yet. After single read/write the mode will automatic update to SINGLE_AND_DONE mode.
* MULTIPLE - close the file only in programmer order, until then the file will be remain open.
*/
enum class ReadWriteMode {
SINGLE_AND_DONE,
SINGLE_AND_MORE,
MULTIPLE,
DONE
};
enum class FileAction {
READ,
WRITE,
NONE
};
enum class FileMode {
ALWAYS_OPEN,
OPEN_IN_ACTION
};
class File {
private:
bool is_ready;
std::string name;
std::string path;
FileMode file_mode;
ReadWriteMode read_write_mode;
int multiple_times_left;
FileAction file_action;
std::mutex read_write_mutex;
std::fstream file_ptr;
bool is_open;
std::ios_base::openmode read_flags;
std::ios_base::openmode write_flags;
bool use_exceptions;
/**
* Open file in specific format
* \param mode_flags - fstream.open() flags.
* \param new_file_action - Open purpose.
*/
void open(std::ios_base::openmode mode_flags, const FileAction &new_file_action);
/**
* Auto update for the file's mode (READ / WRITE / NONE).
*/
void update_rwm();
/**
* Close file
* \param automatic - Close request from API(true) or from User(false)
*/
void close(bool automatic);
/**
* Is file ready for read/write actions. Exception if file not ready.
* \return Is file name not empty.
*/
bool is_file_ready(int);
public:
/**
* Ctor
* \param file_name - if @param file_path == "" => path/to/file/filename.bin else filename.bin
* \param exceptions - Throw exceptions on errors Or use bold cout messages.
* \param file_path - file's path.
*/
explicit File(const std::string &file_name, bool exceptions = false, const std::string &file_path = "");
/**
* Close the file.
*/
~File();
/**
* Force close the file.
*/
void close();
/**
* Set file's name.
* \param new_name - New file's name.
*/
void set_name(const std::string &new_name);
/**
* Set file's name.
* \param new_name - New file's name.
*/
File &operator=(const std::string &new_name);
/**
* Get file's name
* \return File's name.
*/
std::string get_name();
/**
* Is file ready for read/write actions. Without exception if file not ready.
* \return Is file name not empty.
*/
bool is_file_ready();
/**
* Init current file's mode
* \param mode - How much reads/writes until the file will close.
* \param multiple_times - if mode is multiple note how much times (-1 for unknown - won't close the file without programmer order/interrupt).
*/
void init_read_write_mode(const ReadWriteMode &mode, int multiple_times = -1);
/**
* Init read fstream flags.
* \param read_flags - When open function in read mode will occur, those flags will be in use.
*/
void init_read_flags(std::ios_base::openmode read_flags = std::ios_base::in);
/**
* Init write fstream flags.
* \param write_flags - When open function in write mode will occur, those flags will be in use.
*/
void init_write_flags(std::ios_base::openmode write_flags = std::ios::out | std::ios::binary | std::ios::in);
/**
* Read to non-vector variable
* \param T - variable type
* \param val - variable address
* \param data_size - in case of array- array's size.
* \return this File object.
*/
template<class T>
File &read(T *val, size_t data_size = 1);
/**
* Read to vector variable
* \tparam T - vector type
* \param val - vector to read into (Have to be initialize with the size of inputs' count).
* \param data_size - vector to read into (Have to be initialize with the size of inputs' count).
* \return this File object.
*/
template<class T>
File &read(std::vector<T> &val);
/**
* Write non-vector variable
* \tparam T - variable type
* \param val - variable address
* \param data_size - in case of array- array's size.
* \return this File object.
*/
template<class T>
File &write(const T *val, size_t data_size = 1);
/**
* Write vector variable
* \tparam T - vector type
* \param val - vector to write.
* \return this File object.
*/
template<class T>
File &write(const std::vector<T> &val);
/**
* Read to vector
* \tparam T - vector type
* \param data - vector to read into
* \return this File object.
*/
template<class T>
File &operator>>(std::vector<T> &data);
/**
* Read to non-vector
* \tparam T - variable type
* \param info - {
* val - variable non-vector to read into
* val_size - in case of array- array's size (else leave as default 1)
* }
* \return this File object
*/
template<class T>
File &operator>>(const rw_s<T> &info);
/**
* Write vector to file
* \tparam T - vector type
* \param data - vector to write
* \return this File object
*/
template<class T>
File &operator<<(const std::vector<T> &data);
/**
* Write non-vector to file
* \tparam T - variable type
* \param info - {
* val - variable non-vector to write
* val_size - in case of array- array's size (else leave as default 1)
* }
* \return this File object
*/
template<class T>
File &operator<<(const rw_s<T> &info);
};
template<class T>
File &File::read(T *val, const size_t data_size) {
if (!is_file_ready(0)) {
return *this;
}
open(read_flags, FileAction::READ);
std::lock_guard<std::mutex> guard(read_write_mutex);
file_ptr.read((char *) (val), sizeof(T) * data_size);
update_rwm();
return *this;
}
template<class T>
File &File::write(const T *val, const size_t data_size) {
if (!is_file_ready(0)) {
return *this;
}
open(write_flags, FileAction::WRITE);
std::lock_guard<std::mutex> guard(read_write_mutex);
file_ptr.write(reinterpret_cast<const char *>(val), sizeof(T) * data_size);
update_rwm();
return *this;
}
template<class T>
File &File::read(std::vector<T> &val) {
if (!is_file_ready(0)) {
return *this;
}
open(read_flags, FileAction::READ);
std::lock_guard<std::mutex> guard(read_write_mutex);
file_ptr.read(reinterpret_cast<char *>(val.data()), sizeof(T) * val.size());
update_rwm();
return *this;
}
template<typename T>
File &File::write(const std::vector<T> &val) {
if (!is_file_ready(0)) {
return *this;
}
open(write_flags, FileAction::WRITE);
std::lock_guard<std::mutex> guard(read_write_mutex);
file_ptr.write(reinterpret_cast<const char *>(val.data()), sizeof(T) * val.size());
update_rwm();
return *this;
}
template<class T>
File &File::operator>>(std::vector<T> &data) {
return read(data);
}
template<class T>
File &File::operator>>(const rw_s<T> &info) {
return read(info.val, info.val_size);
}
template<class T>
File &File::operator<<(const std::vector<T> &data) {
return write(data);
}
template<class T>
File &File::operator<<(const rw_s<T> &info) {
return write(info.val, info.val_size);
}
}
#endif //FILESAPI_FILE_H
FilesManager.hpp
#ifndef FILESAPI_FILESMANAGER_H
#define FILESAPI_FILESMANAGER_H
#include <iostream>
#include <vector>
#include <map>
#include <memory>
#include "File.hpp"
namespace FilesApi {
using add_data = std::tuple<const std::string, const std::string>;
class FilesManager {
private:
std::map<std::string, std::shared_ptr<File>> files;
size_t max_files; // zero for unlimited
std::string files_path; // Leave "" if there is no single path for all of the files
bool use_exceptions;
void remove_unusable_files();
public:
/**
* Ctor
* \param exceptions - Throw exceptions on errors Or use bold cout messages.
* \param max_files - Maximum files number to manage in this FilesManager object (0 for unlimited).
* \param files_path - if @param files_path == "" => in new file associate you will have to supply full
* file path, e.g: "path/to/file/filename.bin"
* else supply only file name, e.g: "filename.bin", if @param files_path == "path/to/file/"
*/
explicit FilesManager(bool exceptions = false, size_t max_files = 0, const std::string &files_path = "");
/**
* Add new file
* \param id - file id (will be use to get this File object).
* \param file - file's name or path (if @files_path == "").
*/
void add(const std::string &id, const std::string &file);
/**
* Remove file
* \param id - file's id
*/
void remove(const std::string &id);
/**
* Get file
* \param id - file's id
*/
File &get(const std::string &id);
/**
* Add new file
* \param data - tuple(0) => file id. tuple(1_ => file name or path (if files path is "").
*/
void operator+=(const add_data &data);
/**
* Get file
* \param id - file's id
*/
File &operator[](const std::string &id);
/**
* Remove file
* \param id - file's id
*/
void operator-=(const std::string &id);
};
}
#endif //FILESAPI_FILESMANAGER_H
File.cpp
#include "../headers/File.hpp"
namespace FilesApi {
File::File(const std::string &file_name, bool exceptions, const std::string &file_path) {
name = file_name;
path = file_path;
is_ready = !name.empty();
use_exceptions = exceptions;
is_open = false;
file_mode = FileMode::OPEN_IN_ACTION;
read_write_mode = ReadWriteMode::DONE;
file_action = FileAction::NONE;
write_flags = std::ios::out | std::ios::binary | std::ios::in;
read_flags = std::ios_base::in;
}
File::~File() {
std::lock_guard<std::mutex> guard(read_write_mutex);
close();
}
void File::open(std::ios_base::openmode mode_flags, const FileAction &new_file_action) {
std::lock_guard<std::mutex> guard(read_write_mutex);
if (!is_file_ready(0)) {
if (is_open) {
file_ptr.close();
is_open = false;
}
return;
}
if (file_action != new_file_action) {
file_ptr.close();
is_open = false;
if (file_action != FileAction::NONE) {
std::cout
<< DesignText::make_colored("Pay attention: file mission replaced by another one. (file closed)",
DesignText::Color::RED, false) << std::endl;
}
}
file_action = new_file_action;
if (!is_open) {
file_ptr.open(path + name, mode_flags);
if (file_ptr.fail()) {
is_open = false;
if (!use_exceptions) {
std::cout << DesignText::make_colored("Error Opening file: " + path + name,
DesignText::Color::RED, true) << std::endl;
} else {
throw FileOpenException(path + name);
}
}
is_open = true;
std::cout << DesignText::make_colored("File has safely opened.", DesignText::Color::GREEN, false)
<< std::endl;
}
}
void File::close(bool automatic) {
if ((!automatic) || (file_mode == FileMode::OPEN_IN_ACTION)) {
if (is_open) {
file_ptr.close();
is_open = false;
read_write_mode = ReadWriteMode::DONE;
file_action = FileAction::NONE;
std::cout << DesignText::make_colored("File has safely closed.", DesignText::Color::GREEN, false)
<< std::endl;
}
}
}
void File::close() {
close(false);
}
void File::update_rwm() {
if (!is_file_ready(0)) {
return;
}
switch (read_write_mode) {
case ReadWriteMode::SINGLE_AND_DONE:
case ReadWriteMode::DONE:
close(true);
break;
case ReadWriteMode::SINGLE_AND_MORE:
read_write_mode = ReadWriteMode::SINGLE_AND_DONE;
break;
case ReadWriteMode::MULTIPLE:
if (multiple_times_left > -1 && !--multiple_times_left) {
multiple_times_left = -1;
close(true);
}
break;
}
}
void File::init_read_write_mode(const ReadWriteMode &new_mode, const int multiple_times) {
read_write_mode = new_mode;
multiple_times_left = multiple_times;
}
void File::init_read_flags(const std::ios_base::openmode new_read_flags) {
read_flags = new_read_flags;
}
void File::init_write_flags(const std::ios_base::openmode new_write_flags) {
write_flags = new_write_flags;
}
void File::set_name(const std::string &new_name) {
if (!new_name.empty()) {
name = new_name;
is_ready = true;
return;
}
if (name.empty()) {
is_ready = false;
}
}
std::string File::get_name() {
return name;
}
bool File::is_file_ready(int) {
if (!is_ready) {
if (!use_exceptions) {
std::cout << DesignText::make_colored("Pay attention: file name is empty. can't open this file.",
DesignText::Color::RED, true) << std::endl;
} else {
throw FileNotReadyException();
}
return false;
}
return true;
}
bool File::is_file_ready() {
return is_ready;
}
File &File::operator=(const std::string &new_name) {
set_name(new_name);
return *this;
}
}
FilesManager.cpp
#include "../headers/FilesManager.hpp"
namespace FilesApi {
FilesManager::FilesManager(bool exceptions, size_t max_files, const std::string &files_path)
: max_files(max_files), files_path(files_path), use_exceptions(exceptions) {
}
void FilesManager::add(const std::string &id, const std::string &file) {
remove_unusable_files();
if (max_files == 0 || files.size() + 1 < max_files) {
files.insert(std::pair<std::string,
std::shared_ptr<File>>(id, std::make_shared<File>(file, use_exceptions, files_path)));
}
}
void FilesManager::remove(const std::string &id) {
remove_unusable_files();
files.erase(id);
}
File &FilesManager::get(const std::string &id) {
remove_unusable_files();
File *ret_file = files[id].get();
if (ret_file == nullptr) {
files[id] = std::make_shared<File>("", use_exceptions, files_path);
ret_file = files[id].get();
}
return *ret_file;
}
void FilesManager::operator+=(const add_data &data) {
add(std::get<0>(data), std::get<1>(data));
}
File &FilesManager::operator[](const std::string &id) {
return get(id);
}
void FilesManager::operator-=(const std::string &id) {
remove(id);
}
void FilesManager::remove_unusable_files() {
for (auto &file : files) {
if (file.second && !file.second->is_file_ready()) {
files.erase(file.first);
}
}
}
}
Use example:
#include <iostream>
#include <vector>
#include <complex>
#include "../src/headers/FilesManager.hpp"
using namespace std;
using namespace FilesApi;
int mainFilesManagerOperatorsTest() {
FilesManager fm(false, 0, "../TestFiles/");
string files[] = {"test_file.bin", "test_file2.bin"};
fm["1"] = files[0];
vector<complex<float>> wdata = {{1, 9}, {3, 75}, {213.34, 21.4}, {153.1, 15.85}};
vector<complex<float>> rdata(wdata.size());
fm["1"].init_read_write_mode(ReadWriteMode::SINGLE_AND_DONE);
//fm.get("1").write(wdata.data(), wdata.size()); // Use it as complex<float> array. Possible.
fm["1"].write(wdata);
fm["1"].init_read_write_mode(ReadWriteMode::SINGLE_AND_DONE);
fm["1"].read(rdata);
fm += add_data("5", files[1]); // Add file to collection
int a = 12;
int b;
fm["5"] << rw_soft(a); // Work
fm["5"].write(&a); // Work
fm["5"] >> rw_soft(b); // Work
cout << b << endl; // Prints 12
fm -= "5"; // Remove the file from collection
fm["5"] << rw_soft(a); // Error
fm["5"].write(&a); // Error
fm["5"] >> rw_soft(b); // Error
//fm["2"] = files[1];
fm += add_data("2", files[1]);
for (size_t i = 0; i < rdata.size(); i++) {
cout << rdata[i] << endl;
}
fm["2"].init_read_write_mode(ReadWriteMode::MULTIPLE);
for (size_t i = 0; i < 100; i++) {
fm["2"].write(&i);
}
//f.init_read_write_mode(ReadWriteMode::MULTIPLE);
size_t j;
for (size_t i = 0; i < 100; i++) {
fm["2"].read(&j);
cout << j << " ";
}
cout << endl;
return 0;
}
You can find more examples/code utilities on GitHub.
f["1"]
when a simple variable would serve the same purpose? \$\endgroup\$