diff options
Diffstat (limited to 'internal/liturgy/fetch.go')
| -rw-r--r-- | internal/liturgy/fetch.go | 84 |
1 files changed, 52 insertions, 32 deletions
diff --git a/internal/liturgy/fetch.go b/internal/liturgy/fetch.go index 8c887f4..e66b77f 100644 --- a/internal/liturgy/fetch.go +++ b/internal/liturgy/fetch.go @@ -76,25 +76,29 @@ func CacheDir() string { return filepath.Join(base, "lectio") } -// Load returns the day's reading sections, from the modern (niedziela.pl) -// source, preferring cache over network: +// Load returns the day's reading sections and DayInfo, from the modern +// (niedziela.pl) source, preferring cache over network: // -// 0. If Offline, skip straight to LoadOffline (the harvested sigla TSV). -// 1. Unless Refresh, the parsed JSON cache ({date}.json). -// 2. Unless Refresh, the raw HTML cache ({date}.html) -- parsed, and the -// result written to the JSON cache for next time. +// 0. If Offline, skip straight to LoadOffline (the harvested sigla TSV) -- +// that store carries citations only, so DayInfo comes back zero. +// 1. Unless Refresh, the parsed JSON cache ({date}.json), which round-trips +// DayInfo alongside the sections (see cachedDay/loadCache). +// 2. Unless Refresh, the raw HTML cache ({date}.html) -- parsed (both +// sections and DayInfo re-derived from the same HTML via Parse/ +// ParseDayInfo), and the result written to the JSON cache for next time. // 3. Otherwise, fetch the page over the network, parse it, and -- only if // the page is fully published -- write both cache layers. A fetch error // here (e.g. no network) falls back to LoadOffline for this date if it // has been harvested, and only surfaces the original fetch error if // that fallback also fails. -func Load(opts Options) ([]Section, error) { +func Load(opts Options) ([]Section, DayInfo, error) { if !dateRe.MatchString(opts.Date) { - return nil, fmt.Errorf("invalid date %q: want YYYY-MM-DD", opts.Date) + return nil, DayInfo{}, fmt.Errorf("invalid date %q: want YYYY-MM-DD", opts.Date) } if opts.Offline { - return LoadOffline(opts.Date) + secs, err := LoadOffline(opts.Date) + return secs, DayInfo{}, err } dir := CacheDir() @@ -102,27 +106,30 @@ func Load(opts Options) ([]Section, error) { htmlPath := filepath.Join(dir, opts.Date+".html") if !opts.Refresh { - if secs, err := loadJSONCache(jsonPath); err == nil { - return secs, nil + if secs, info, err := loadCache(jsonPath); err == nil { + return secs, info, nil } if page, err := os.ReadFile(htmlPath); err == nil { - secs, err := Parse(string(page)) + pageStr := string(page) + secs, err := Parse(pageStr) if err != nil { - return nil, err + return nil, DayInfo{}, err } - writeJSONCache(jsonPath, secs) - return secs, nil + info := ParseDayInfo(pageStr) + writeCache(jsonPath, secs, info) + return secs, info, nil } } page, err := fetch(opts.Date) if err != nil { // Network is unreachable: fall back to a prior harvest of this date - // if there is one, rather than failing outright. + // if there is one, rather than failing outright. The sigla store + // carries no DayInfo, so this path always reports it zero. if secs, offErr := LoadOffline(opts.Date); offErr == nil { - return secs, nil + return secs, DayInfo{}, nil } - return nil, err + return nil, DayInfo{}, err } // Parse itself reports an unpublished date as "no reading published for @@ -130,19 +137,20 @@ func Load(opts Options) ([]Section, error) { // page is neither cached nor returned as sections here. secs, err := Parse(page) if err != nil { - return nil, err + return nil, DayInfo{}, err } + info := ParseDayInfo(page) // Cache only fully-published pages, so an as-yet-unpublished future date // keeps being retried instead of caching a "no reading" placeholder. if publishedRe.MatchString(page) { if err := os.MkdirAll(dir, 0o755); err == nil { _ = os.WriteFile(htmlPath, []byte(page), 0o644) - writeJSONCache(jsonPath, secs) + writeCache(jsonPath, secs, info) } } - return secs, nil + return secs, info, nil } // CleanCache removes cached readings whose date is before `before` from @@ -189,23 +197,35 @@ func CleanCache(before time.Time) (removed int, freed int64, err error) { return removed, freed, nil } -// loadJSONCache reads and unmarshals the parsed-sections cache file. -func loadJSONCache(path string) ([]Section, error) { +// cachedDay is the on-disk shape of the parsed-sections JSON cache +// ({date}.json): the sections plus the day's liturgical identity, so a +// cache hit round-trips DayInfo without re-parsing the HTML cache. (An +// older cache file written before DayInfo existed was a bare JSON array, +// not an object -- json.Unmarshal into cachedDay then fails, which +// loadCache treats as an ordinary cache miss, falling through to the HTML +// cache or the network like any other stale/missing cache entry.) +type cachedDay struct { + Sections []Section + DayInfo DayInfo +} + +// loadCache reads and unmarshals the parsed-sections + DayInfo cache file. +func loadCache(path string) ([]Section, DayInfo, error) { data, err := os.ReadFile(path) if err != nil { - return nil, err + return nil, DayInfo{}, err } - var secs []Section - if err := json.Unmarshal(data, &secs); err != nil { - return nil, err + var cd cachedDay + if err := json.Unmarshal(data, &cd); err != nil { + return nil, DayInfo{}, err } - return secs, nil + return cd.Sections, cd.DayInfo, nil } -// writeJSONCache best-effort writes the parsed sections cache; a failure to -// cache should never fail the load itself. -func writeJSONCache(path string, secs []Section) { - data, err := json.Marshal(secs) +// writeCache best-effort writes the parsed sections + DayInfo cache; a +// failure to cache should never fail the load itself. +func writeCache(path string, secs []Section, info DayInfo) { + data, err := json.Marshal(cachedDay{Sections: secs, DayInfo: info}) if err != nil { return } |
