101 lines
4.8 KiB
C++
101 lines
4.8 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#pragma once
|
|
|
|
#include "export.hpp"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace udpscope {
|
|
|
|
/// What a cached event is, without loading its samples: the sidecar's fields, and what the pane
|
|
/// shows in one row.
|
|
struct EventRecord {
|
|
std::string stem; ///< the file's name without the extension
|
|
std::string file; ///< `<stem>.npy`, the event's table in the export format
|
|
std::string thumbnail; ///< `<stem>.bmp`, the preview drawn from the samples; else empty
|
|
double wall_time = 0.0; ///< CLOCK_REALTIME at the latch, for the row's label
|
|
double duration_s = 0.0; ///< pre + post
|
|
double pre_s = 0.0;
|
|
double post_s = 0.0;
|
|
double edge = 0.0; ///< the stream time of the event's x = 0
|
|
double rate_hz = 0.0; ///< the stored sample rate at the latch
|
|
std::size_t signals = 0; ///< columns in the file
|
|
std::size_t samples = 0; ///< rows
|
|
std::uint64_t seq = 0; ///< the run's own event counter, so two events in one second differ
|
|
};
|
|
|
|
/// The cache of captured events: `<cache_dir>/events/<source>/`, one `.npy` per event written in
|
|
/// the **export format** -- so `--open` and the chooser's Waveforms page read it back with the
|
|
/// loader that already exists -- beside a `.txt` sidecar holding what the list shows without
|
|
/// loading the samples, and whatever thumbnail the caller rendered.
|
|
///
|
|
/// No GTK: the caller renders the thumbnail and hands over a path, exactly as it hands over the
|
|
/// table. The store owns naming, the sidecar, pruning and listing, which is what makes it testable
|
|
/// without a window.
|
|
/// The directory every source's event folder is created under when no preference says otherwise:
|
|
/// `<cache>/udpscope/events`. Exposed because the preferences dialog shows it -- "the standard
|
|
/// location" is not an answer a user can act on, and a second copy of the path computed in the dialog
|
|
/// is a second thing to get wrong.
|
|
std::string default_events_dir();
|
|
|
|
class EventStore {
|
|
public:
|
|
/// `source` is the `<host>-<port>` pair, the same key the packet capture uses.
|
|
/// `base_dir` is the directory every source's own folder is created under; empty means
|
|
/// `<cache>/udpscope/events`. It comes from the preferences (item 9), because a user with a small
|
|
/// home partition and a second disk should not have to choose between events and free space.
|
|
explicit EventStore(std::string source = {}, std::string base_dir = {});
|
|
|
|
/// How many events to keep, newest first. The oldest beyond it are deleted on every save; item 9's
|
|
/// preferences are where this becomes the user's number.
|
|
static constexpr std::size_t kDefaultKeep = 32;
|
|
|
|
const std::string& dir() const noexcept { return dir_; }
|
|
bool empty_dir() const noexcept { return dir_.empty(); }
|
|
|
|
/// Writes `table` as one event, with `meta` in the sidecar and a preview drawn beside it. Returns
|
|
/// the samples' path, or an empty string on failure -- a cache that cannot be written must not stop
|
|
/// the scope.
|
|
///
|
|
/// The preview is drawn from the *data* -- min/max per pixel column, one strip per signal -- and not
|
|
/// captured from the window: the plot is a `GtkGLArea`, and a GL area's ink is not in the widget's
|
|
/// paint tree, so a snapshot of it is empty (`gtk_snapshot_free_to_node()` returns NULL). Drawing it
|
|
/// here also keeps this class GTK-free, which is what makes it testable without a window.
|
|
std::string save(const ExportTable& table, const EventRecord& meta,
|
|
std::size_t keep = kDefaultKeep);
|
|
|
|
/// The cached events, newest first: the sidecars, never the sample files, so a listing is cheap
|
|
/// however large the captures are.
|
|
std::vector<EventRecord> list() const;
|
|
|
|
/// Draws `record`'s preview again from its samples and returns the thumbnail's path, or an empty
|
|
/// string when the samples cannot be read.
|
|
///
|
|
/// This is the repair for a preview that is missing or will not decode -- a write that failed, a
|
|
/// truncated file, a cache written by an older build. The `.npy` is the event, the `.bmp` is only a
|
|
/// picture of it, so a lost picture is recoverable and must not be shown as a blank row. It reads
|
|
/// the samples, so unlike `list()` it is **not** cheap: call it for the one event that needs it.
|
|
std::string redraw_preview(const EventRecord& record) const;
|
|
|
|
/// Deletes one event's samples, sidecar and thumbnail.
|
|
bool forget(const std::string& stem);
|
|
|
|
/// Deletes the oldest events beyond `keep`.
|
|
void prune(std::size_t keep) const;
|
|
|
|
private:
|
|
std::string dir_;
|
|
};
|
|
|
|
/// The sidecar's text, and back. One `key = value` per line, the project's own TOML subset, so a
|
|
/// human can read the cache with `cat` -- which is the point of a forensic directory.
|
|
std::string event_meta_text(const EventRecord& record);
|
|
bool event_meta_parse(const std::string& text, EventRecord* out);
|
|
|
|
} // namespace udpscope
|