list the sources with a stored session
known_sources() reads the session files the layout store already owns, keeping only those that name a signal: a development machine accumulates one file per probe run on a throwaway port, and all 140 of them here hold empty slots.
This commit is contained in:
1 parent
c604d62917
commit
eabdde34f9
2 files changed
+78
No files matched your search
@@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
#include "layout_store.hpp"
|
#include "layout_store.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <dirent.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -117,6 +120,59 @@ std::string layout_path(const std::string& host, std::uint16_t port) {
|
|||||||
return dir + "/" + sanitize(host) + "_" + std::to_string(port) + ".toml";
|
return dir + "/" + sanitize(host) + "_" + std::to_string(port) + ".toml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<KnownSource> known_sources() {
|
||||||
|
std::vector<KnownSource> out;
|
||||||
|
const std::string dir = config_dir();
|
||||||
|
DIR* d = ::opendir(dir.c_str());
|
||||||
|
// No directory yet is an empty list, not an error: a first run has never saved anything.
|
||||||
|
if (d == nullptr) return out;
|
||||||
|
while (const dirent* e = ::readdir(d)) {
|
||||||
|
const std::string name = e->d_name;
|
||||||
|
if (name.size() <= 5 || name.compare(name.size() - 5, 5, ".toml") != 0) continue;
|
||||||
|
const std::string stem = name.substr(0, name.size() - 5);
|
||||||
|
// The *last* '_': `sanitize()` replaces anything unsafe in a host with one, so the host part
|
||||||
|
// may hold them too and a first-match split would return a host cut in half.
|
||||||
|
const std::size_t cut = stem.rfind('_');
|
||||||
|
if (cut == std::string::npos || cut + 1 == stem.size()) continue;
|
||||||
|
const long port = std::strtol(stem.c_str() + cut + 1, nullptr, 10);
|
||||||
|
if (port <= 0 || port > 65535) continue;
|
||||||
|
|
||||||
|
LayoutState st;
|
||||||
|
const std::string path = dir + "/" + name;
|
||||||
|
if (!load_layout(path, st)) continue;
|
||||||
|
|
||||||
|
KnownSource s;
|
||||||
|
s.host = stem.substr(0, cut);
|
||||||
|
s.port = static_cast<std::uint16_t>(port);
|
||||||
|
s.multicast = st.multicast;
|
||||||
|
s.interface_addr = st.interface_addr;
|
||||||
|
s.rows = static_cast<int>(st.rows.size());
|
||||||
|
for (const LayoutState::Row& row : st.rows) {
|
||||||
|
for (const std::string& sig : row.signals) {
|
||||||
|
if (!sig.empty()) ++s.signals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.signals == 0) continue; // see the header: a session nobody configured
|
||||||
|
|
||||||
|
struct stat sb {};
|
||||||
|
s.mtime = (::stat(path.c_str(), &sb) == 0) ? static_cast<std::int64_t>(sb.st_mtime) : 0;
|
||||||
|
out.push_back(std::move(s));
|
||||||
|
}
|
||||||
|
::closedir(d);
|
||||||
|
// Stable, so two files sharing an mtime keep the order the directory gave them rather than
|
||||||
|
// swapping between runs.
|
||||||
|
std::stable_sort(out.begin(), out.end(),
|
||||||
|
[](const KnownSource& a, const KnownSource& b) { return a.mtime > b.mtime; });
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool forget_source(const KnownSource& src) {
|
||||||
|
// Through `layout_path()` rather than by rebuilding the name here: one expression of the
|
||||||
|
// scheme. `src.host` came back out of a filename, so it is already sanitized, and `sanitize()`
|
||||||
|
// keeps alphanumerics, '.', '-' and '_' -- it is idempotent, so the round trip is exact.
|
||||||
|
return std::remove(layout_path(src.host, src.port).c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::string math_node_text(const MathNode& node) {
|
std::string math_node_text(const MathNode& node) {
|
||||||
// The op is written as its index through `math_op_at()`, not as the raw enum value: that accessor
|
// The op is written as its index through `math_op_at()`, not as the raw enum value: that accessor
|
||||||
// is the contract the dialog, the engine and this file share, so the format is tied to it rather
|
// is the contract the dialog, the engine and this file share, so the format is tied to it rather
|
||||||
|
|||||||
@@ -53,6 +53,28 @@ struct LayoutState {
|
|||||||
std::vector<Measure> measures;
|
std::vector<Measure> measures;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A data source udpscope has a stored session for: one row of the startup chooser.
|
||||||
|
struct KnownSource {
|
||||||
|
std::string host; ///< the CONFIG server, even for a multicast session
|
||||||
|
std::uint16_t port = 0;
|
||||||
|
std::string multicast; ///< empty = unicast
|
||||||
|
std::string interface_addr; ///< empty = the default route
|
||||||
|
int rows = 0; ///< plots the session had
|
||||||
|
int signals = 0; ///< named slots across those plots
|
||||||
|
std::int64_t mtime = 0; ///< orders the list; never shown
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Every session file that names at least one signal, newest first.
|
||||||
|
///
|
||||||
|
/// The filter is what makes the list usable: a development machine accumulates one session file
|
||||||
|
/// per probe run on a throwaway port -- 140 of them when this was written -- and every one holds
|
||||||
|
/// `signals = ["", "", "", ""]`. A listed source is one somebody actually configured.
|
||||||
|
std::vector<KnownSource> known_sources();
|
||||||
|
|
||||||
|
/// Deletes one source's session file; the chooser's `x`. False means the file was already gone or
|
||||||
|
/// could not be removed, which costs the caller nothing -- there is no state to roll back.
|
||||||
|
bool forget_source(const KnownSource& src);
|
||||||
|
|
||||||
/// One `MathNode` as `kind|source|in_a|in_b|gain|offset|fc|q|lo|hi|x|y`, and back. The kind is the
|
/// One `MathNode` as `kind|source|in_a|in_b|gain|offset|fc|q|lo|hi|x|y`, and back. The kind is the
|
||||||
/// op's *index* into `math_op_at()`, which is why the enum's order is a stored format and not just
|
/// op's *index* into `math_op_at()`, which is why the enum's order is a stored format and not just
|
||||||
/// an implementation detail: reordering it renames every saved graph's operations.
|
/// an implementation detail: reordering it renames every saved graph's operations.
|
||||||
|
|||||||
Reference in new issue
Block a user