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 — per-download stalled bookkeeping (groundwork for the stalled-download handling in #9): when a download was first seen stalled.

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_state(backend : String, download_id : String) : Nil

Clears any stored stalled state for (backend, download_id).

Source
close

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

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_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).

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