The GraphManager#

ng::GraphManager (graph_manager.h) is a singleton (GraphManager::instance()) with two jobs: owning the graphs open in memory, and reading/writing graphs in folders of the disk called heads.

Graphs in memory#

  • new_graph(name) creates an empty graph and returns its graph_id_t.

  • get_graph(id) returns a reference to it (throws std::out_of_range if unknown). Graphs are modified in place through this reference.

  • has_graph(id), close_graph(id).

  • reset() forgets everything (graphs, heads, library). Used by the tests to isolate themselves.

Heads#

A head is a folder where graphs are saved, with an owner:

enum class Owner { User, Group, Global, Unknown };   // from the most to the least personal

add_head(path, owner) records the folder, checks if it is readable and writable (access()), and discovers the graphs it contains. A missing folder is accepted: it is just neither readable nor writable.

A graph with the qualifier a.b.c is the file <head>/a/b/c.graph. Discovery walks exactly N_CHUNKS - 1 levels of folders and keeps the .graph files at the last level; other files, or files at the wrong depth, are ignored.

get_library() maps each discovered qualifier to the head it was found in. refresh() clears it and discovers all the readable heads again (to see files added or removed by another program).

heads (by head ID)                 library
1: /home/me/graphs     User   ──►  a.b.c -> 1
2: /shared/lab/graphs  Group  ──►  a.b.d -> 2,  x.y.z -> 2

Saving#

save_graph(id) picks the most personal writable head (smallest Owner, then smallest head ID), and calls save_graph(id, head), which:

Check

Status

The head exists

UnknownHead

The head is writable (or, for save_graph(id), a writable head exists)

NoWritableHead

The graph has a qualifier

NoQualifier

The qualifier is complete

IncompleteQualifier

The folders can be created and the file written

IOError

On success, the file is (over)written, the qualifier is added to the library, and the graph is recorded as “loaded” under its qualifier (a later load_graph returns this graph instead of reading the file).

Reading#

raw_read(qualifier)

Finds the file in the first readable head, by head ID, that contains it, deserializes it, checks that the qualifier stored in the file matches the one requested (a moved or copied file is refused), and returns a copy of the graph. Nothing is recorded in the manager. This is what proxies and the runtime use. Throws std::runtime_error on any problem.

load_graph(qualifier, force = false)

If a graph with this qualifier was already loaded or saved, returns its ID. Otherwise (or with force), calls raw_read and stores the result as a new graph in memory.

exists(qualifier) tells whether a file exists in a readable head.

Pitfalls#

  • Reading takes the first head containing the file, saving takes the most personal writable head. If a read-only User head contains an old version of a graph, saving writes into a Group head, but reading still returns the old version from the User head.

  • can_read / can_write use the POSIX access() function (unistd.h): not portable to Windows.

  • The manager is a global singleton: tests and examples that use it must reset it (ScopedLibrary in tests/contexts does it).