class

ArrJanitor::Store

Inherits Reference < Object

SQLite-backed persistence for ArrJanitor.

Tracks two things:

  • processed_downloads — an audit log of every download ArrJanitor has acted on (for retention/reporting), swept on a TTL.
  • download_states — independent first-seen clocks per download: when it was first seen stuck fetching metadata (first_seen_metadata) and when it was first seen stalled (first_seen_stalled). A row may have only one of the two timestamps set.

A single Store (and its underlying DB::Database connection pool) is meant to be shared across all backend fibers. Under -Dpreview_mt several fibers may write concurrently; the database is opened in WAL mode with a busy_timeout so concurrent writers block-and-retry instead of failing with SQLITE_BUSY. Do not open a Store per call — reuse the shared one.

Constants

BUSY_TIMEOUT = 5.seconds

How long a writer waits for a competing writer's lock before giving up.

Constructors

new(db : DB::Database)

Wraps an already-open DB::Database. Prefer Store.open.

Source
open(path : String) : Store

Opens (creating if necessary) the SQLite database at path, enables WAL mode + a busy timeout, runs the schema migrations, and returns a ready Store. The path is a plain filesystem path (not a sqlite3:// URI).

The pragmas are passed as URI query parameters so crystal-sqlite3 applies them to every connection the pool opens (the driver runs the URI pragmas in each new connection's initialize). Setting busy_timeout via a post-open @db.exec would only configure the single pooled connection that happened to run it — other connections (created lazily under -Dpreview_mt) would default to busy_timeout=0 and raise SQLITE_BUSY immediately instead of blocking-and-retrying. journal_mode is a persistent file-level setting, but is set via the URI too for good measure.

NOTE: path is interpolated into the URI unescaped. Paths are local filenames so this is fine in practice; a path containing URI-significant characters (e.g. ? or #) would need escaping.

Source

Instance methods

clear_metadata(backend : String, download_id : String) : Nil

Nulls out first_seen_metadata only; leaves first_seen_stalled (and the row) intact.

Source
clear_stalled(backend : String, download_id : String) : Nil

Nulls out first_seen_stalled only; leaves first_seen_metadata (and the row) intact.

Source
clear_state(backend : String, download_id : String) : Nil

Clears any stored state for (backend, download_id) — both clocks.

Source
close

Closes the underlying database (flushing the WAL). Idempotent-safe to call once at shutdown.

Source
first_seen_metadata(backend : String, download_id : String) : Time | Nil

The time (backend, download_id) was first seen stuck fetching metadata, or nil when it has no recorded metadata state.

Source
first_seen_stalled(backend : String, download_id : String) : Time | Nil

The time (backend, download_id) was first seen stalled, or nil when it has no recorded stalled state.

Source
mark_metadata(backend : String, download_id : String, now = Time.utc) : Nil

Marks (backend, download_id) as stuck fetching metadata, recording now as the first-seen-metadata time the first time it is called. Subsequent calls leave the original first-seen time untouched (only updated_at moves). Does not clobber first_seen_stalled. If the row already exists from a stalled mark (metadata timestamp NULL), this sets metadata without moving an existing metadata time.

Source
mark_stalled(backend : String, download_id : String, now = Time.utc) : Nil

Marks (backend, download_id) as stalled, recording now as the first-seen-stalled time the first time it is called. Subsequent calls leave the original first-seen time untouched (only updated_at moves). Does not clobber first_seen_metadata. If the row already exists from a metadata mark (stalled timestamp NULL), this sets stalled without moving an existing stalled time.

Source
migrate

Creates the schema if absent. Idempotent — safe to call on every open.

Source
processed?(backend : String, download_id : String) : Bool

Whether a download identified by (backend, download_id) has already been recorded in processed_downloads.

Source
record_processed(backend : String, download_id : String, title : String | Nil, action : String, matched_extensions : Enumerable(String), created_at : Time = Time.utc) : Nil

Records that ArrJanitor took action on the download identified by (backend, download_id). matched_extensions is the set of bad extensions/paths that triggered the action; it is stored comma-joined. created_at defaults to now; it is a parameter so callers (and tests) can backdate rows through the public API.

Source
sweep(older_than : Time::Span) : Int64

Deletes processed_downloads rows older than older_than (relative to now) and returns the number of rows removed.

Source

Nested types