Program Listing for File graph_manager.cc#
↰ Return to documentation for file (src/graph_manager.cc)
#include <unistd.h>
#include <fstream>
#include <iterator>
#include "nodes-graph/graph_manager.h"
#include "nodes-graph/serialization.h"
#include "nodes-graph/deserialization.h"
namespace ng {
namespace sr = serialization;
namespace ds = deserialization;
namespace {
// Walks the folders of a head: one level of folders per chunk of qualifier, the last chunk being a ".graph" file.
void discover_folder(
const std::filesystem::path& path,
std::size_t level,
qualifier_t& buffer,
GraphManager::library_t& discovered,
GraphManager::head_id_t head_id
) {
std::error_code ec;
for (const auto& entry : std::filesystem::directory_iterator(path, ec)) {
if (level == qualifier_t::N_CHUNKS - 1) {
if (!entry.is_regular_file()) { continue; }
if (entry.path().extension() != ".graph") { continue; }
buffer.chunks[level] = as_qchunk(entry.path().stem().c_str());
discovered.emplace(buffer, head_id);
} else if (entry.is_directory()) {
buffer.chunks[level] = as_qchunk(entry.path().filename().c_str());
discover_folder(entry.path(), level + 1, buffer, discovered, head_id);
}
}
}
} // namespace
bool GraphManager::can_write(const std::filesystem::path& path) {
return access(path.c_str(), W_OK) == 0;
}
bool GraphManager::can_read(const std::filesystem::path& path) {
return access(path.c_str(), R_OK) == 0;
}
GraphManager& GraphManager::instance() {
static GraphManager manager;
return manager;
}
void GraphManager::reset() {
heads_.clear();
library_.clear();
graphs_.clear();
loaded_graphs_.clear();
next_head_ = 1;
next_graph_ = 1;
}
// ---
graph_id_t GraphManager::new_graph(const std::string& name) {
graph_id_t id = next_graph_++;
graphs_.emplace(id, Graph(name));
return id;
}
bool GraphManager::close_graph(graph_id_t id) {
for (auto it = loaded_graphs_.begin(); it != loaded_graphs_.end(); ) {
it = (it->second == id) ? loaded_graphs_.erase(it) : std::next(it);
}
return graphs_.erase(id) > 0;
}
// ---
void GraphManager::discover_head(head_id_t head_id) {
const HeadProperties& head = heads_.at(head_id);
if (!head.readable || !std::filesystem::is_directory(head.path)) { return; }
qualifier_t buffer;
library_t discovered;
discover_folder(head.path, 0, buffer, discovered, head_id);
library_.insert(discovered.begin(), discovered.end());
}
GraphManager::head_id_t GraphManager::add_head(const std::filesystem::path& path, Owner owner) {
head_id_t head_id = next_head_++;
heads_.emplace(
head_id,
HeadProperties(owner, path, can_read(path), can_write(path))
);
discover_head(head_id);
return head_id;
}
void GraphManager::refresh() {
library_.clear();
for (const auto& [head_id, _] : heads_) {
discover_head(head_id);
}
}
// ---
std::filesystem::path GraphManager::find_graph(const qualifier_t& q) const {
std::filesystem::path piece = as_filepath(q);
for (const auto& [_, head] : heads_) {
if (!head.readable) { continue; }
std::filesystem::path full_path = head.path / piece;
if (std::filesystem::is_regular_file(full_path)) {
return full_path;
}
}
return {};
}
bool GraphManager::exists(const qualifier_t& q) const {
return !find_graph(q).empty();
}
Status GraphManager::save_graph(graph_id_t id) {
Owner most_personal = Owner::Unknown;
head_id_t chosen = 0;
for (const auto& [head_id, head] : heads_) {
if (!head.writable) { continue; }
if (chosen == 0 || head.owner < most_personal) {
most_personal = head.owner;
chosen = head_id;
}
}
if (chosen == 0) { return Status::NoWritableHead; }
return save_graph(id, chosen);
}
Status GraphManager::save_graph(graph_id_t id, head_id_t head_id) {
auto head_it = heads_.find(head_id);
if (head_it == heads_.end()) { return Status::UnknownHead; }
if (!head_it->second.writable) { return Status::NoWritableHead; }
const Graph& g = get_graph(id);
if (!g.get_qualifier().has_value()) { return Status::NoQualifier; }
const qualifier_t& q = g.get_qualifier().value();
if (!q.is_complete()) { return Status::IncompleteQualifier; }
std::filesystem::path full_path = head_it->second.path / as_filepath(q);
std::error_code ec;
std::filesystem::create_directories(full_path.parent_path(), ec);
if (ec) { return Status::IOError; }
sr::Serializer s(g);
s.serialize_graph();
const sr::buffer_t& buffer = s.get_buffer();
std::ofstream ofs(full_path, std::ios::binary);
if (!ofs) { return Status::IOError; }
ofs.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
if (!ofs) { return Status::IOError; }
ofs.close();
library_[q] = head_id;
loaded_graphs_[q] = id;
return Status::Ok;
}
Graph GraphManager::raw_read(const qualifier_t& q) const {
std::filesystem::path path = find_graph(q);
if (path.empty())
throw std::runtime_error("Graph not found: " + as_string(q));
std::ifstream ifs(path, std::ios::binary);
if (!ifs)
throw std::runtime_error("Failed to open graph file: " + path.string());
sr::buffer_t buffer{
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>()
};
ifs.close();
ds::Deserializer d(buffer);
d.deserialize_graph();
if (d.get_graph().get_qualifier().value() != q)
throw std::runtime_error("Qualifier mismatch in file: " + path.string());
return d.get_graph();
}
graph_id_t GraphManager::load_graph(const qualifier_t& q, bool force) {
auto it = loaded_graphs_.find(q);
if (!force && it != loaded_graphs_.end()) { return it->second; }
Graph g = raw_read(q);
graph_id_t id = next_graph_++;
graphs_.emplace(id, std::move(g));
loaded_graphs_[q] = id;
return id;
}
} // namespace ng