summaryrefslogtreecommitdiff
path: root/internal/liturgy/store.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/liturgy/store.go')
-rw-r--r--internal/liturgy/store.go215
1 files changed, 0 insertions, 215 deletions
diff --git a/internal/liturgy/store.go b/internal/liturgy/store.go
deleted file mode 100644
index 5bf7ef0..0000000
--- a/internal/liturgy/store.go
+++ /dev/null
@@ -1,215 +0,0 @@
-package liturgy
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "time"
-)
-
-// harvestRetries is how many times Harvest attempts a single date's fetch
-// before treating it as a genuine network failure rather than transient
-// hiccup; harvestRetryDelay is the backoff slept between attempts. Both are
-// package vars so tests can shrink the delay instead of waiting on it.
-var (
- harvestRetries = 3
- harvestRetryDelay = 2 * time.Second
-)
-
-// siglaRow is one line of the sigla TSV: a date's section label and the
-// scripture citation extracted from its heading.
-type siglaRow struct {
- label, citation string
-}
-
-// siglaPath is the persistent sigla store written by Harvest and read by
-// LoadOffline: ${XDG_DATA_HOME:-~/.local/share}/lectio/sigla.tsv
-func siglaPath() string {
- base := os.Getenv("XDG_DATA_HOME")
- if base == "" {
- home, err := os.UserHomeDir()
- if err != nil {
- home = "."
- }
- base = filepath.Join(home, ".local", "share")
- }
- return filepath.Join(base, "lectio", "sigla.tsv")
-}
-
-// sectionLabel derives the short label Harvest stores alongside a citation
-// from a section's full heading, e.g. "Ewangelia (J 20, 1. 11-18)" ->
-// "Ewangelia". It strips the same trailing "(...)" citation ExtractCitation
-// reads, so the two stay in sync.
-func sectionLabel(heading string) string {
- loc := citationRe.FindStringIndex(heading)
- if loc == nil {
- return strings.TrimSpace(heading)
- }
- return strings.TrimSpace(heading[:loc[0]])
-}
-
-// readSigla loads the sigla TSV into date -> rows. A missing file is not an
-// error -- it just means nothing has been harvested yet.
-func readSigla(path string) (map[string][]siglaRow, error) {
- data, err := os.ReadFile(path)
- if err != nil {
- if os.IsNotExist(err) {
- return map[string][]siglaRow{}, nil
- }
- return nil, err
- }
- rows := map[string][]siglaRow{}
- for _, line := range strings.Split(string(data), "\n") {
- if line == "" {
- continue
- }
- fields := strings.SplitN(line, "\t", 3)
- if len(fields) != 3 {
- continue
- }
- date := fields[0]
- rows[date] = append(rows[date], siglaRow{label: fields[1], citation: fields[2]})
- }
- return rows, nil
-}
-
-// writeSigla writes date -> rows back out as the sigla TSV, sorted by date
-// for a deterministic, diffable file.
-func writeSigla(path string, byDate map[string][]siglaRow) error {
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
- return err
- }
- dates := make([]string, 0, len(byDate))
- for d := range byDate {
- dates = append(dates, d)
- }
- sort.Strings(dates)
-
- var b strings.Builder
- for _, date := range dates {
- for _, r := range byDate[date] {
- fmt.Fprintf(&b, "%s\t%s\t%s\n", date, r.label, r.citation)
- }
- }
- return os.WriteFile(path, []byte(b.String()), 0o644)
-}
-
-// Harvest walks dates forward from fromDate, fetching and parsing each day's
-// page and recording every section's citation to the sigla TSV, for up to
-// maxDays days (0 = walk until the unpublished horizon). It stops cleanly
-// (nil error) the first time a date fails to PARSE -- niedziela.pl's
-// "Przykro nam" placeholder (or any other parse failure) marks the horizon
-// the site hasn't published past yet, not an error to report. A date that
-// fails to FETCH, by contrast, is a transient network problem, not the
-// horizon: Harvest retries it a few times (harvestRetries, backing off
-// harvestRetryDelay between attempts) and, if it still fails, stops and
-// returns an error -- but only after saving whatever was harvested up to
-// that point, so the caller never loses progress to a blip.
-//
-// Re-harvesting a date replaces its rows in the TSV rather than duplicating
-// them, so running Harvest again over an already-harvested range is safe.
-// It also warms the HTML/JSON cache for every date it successfully harvests.
-//
-// It returns how many days were harvested and the furthest (most recent)
-// date reached, alongside any fetch error (nil on a clean parse-horizon
-// stop or on reaching maxDays).
-func Harvest(fromDate string, maxDays int) (added int, furthest string, err error) {
- start, err := time.Parse("2006-01-02", fromDate)
- if err != nil {
- return 0, "", fmt.Errorf("invalid date %q: %w", fromDate, err)
- }
-
- path := siglaPath()
- byDate, err := readSigla(path)
- if err != nil {
- return 0, "", err
- }
-
- dir := CacheDir()
- day := start
- var harvestErr error
- for i := 0; maxDays == 0 || i < maxDays; i++ {
- dateStr := day.Format("2006-01-02")
-
- var page string
- var ferr error
- for attempt := 1; attempt <= harvestRetries; attempt++ {
- page, ferr = fetch(dateStr)
- if ferr == nil {
- break
- }
- if attempt < harvestRetries {
- time.Sleep(harvestRetryDelay)
- }
- }
- if ferr != nil {
- // A genuine network/transport error, not the horizon: don't
- // silently stop as if the site simply hadn't published this
- // date yet. Record it and stop walking, but writeSigla below
- // still runs so progress made so far isn't lost.
- harvestErr = fmt.Errorf("harvest interrupted at %s: %w", dateStr, ferr)
- break
- }
- secs, perr := Parse(page)
- if perr != nil {
- break // unpublished horizon (or unparsable page): stop walking, cleanly
- }
-
- var rows []siglaRow
- for _, s := range secs {
- citation, cerr := ExtractCitation(s.Heading)
- if cerr != nil {
- continue
- }
- rows = append(rows, siglaRow{label: sectionLabel(s.Heading), citation: citation})
- }
- byDate[dateStr] = rows
-
- if publishedRe.MatchString(page) {
- if mkErr := os.MkdirAll(dir, 0o755); mkErr == nil {
- _ = os.WriteFile(filepath.Join(dir, dateStr+".html"), []byte(page), 0o644)
- writeCache(filepath.Join(dir, dateStr+".json"), secs, ParseDayInfo(page))
- }
- }
-
- added++
- furthest = dateStr
- day = day.AddDate(0, 0, 1)
- }
-
- werr := writeSigla(path, byDate)
- if harvestErr != nil {
- return added, furthest, harvestErr
- }
- if werr != nil {
- return added, furthest, werr
- }
- return added, furthest, nil
-}
-
-// LoadOffline builds a day's sections purely from the harvested sigla TSV:
-// Heading is the stored section label, Citation the stored citation, and
-// Paragraphs empty (no reading text is harvested, only the scripture
-// reference). It errors clearly if the date has not been harvested.
-func LoadOffline(date string) ([]Section, error) {
- byDate, err := readSigla(siglaPath())
- if err != nil {
- return nil, err
- }
- rows, ok := byDate[date]
- if !ok || len(rows) == 0 {
- return nil, fmt.Errorf("%s not harvested; run 'lectio update' while online first", date)
- }
- secs := make([]Section, 0, len(rows))
- czytanieCount := 0
- for _, r := range rows {
- secs = append(secs, Section{
- Heading: r.label,
- Citation: r.citation,
- PartID: partID(r.label, &czytanieCount),
- })
- }
- return secs, nil
-}