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