Class: Pikuri::UrlCache

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/url_cache.rb

Overview

On-disk cache for string-keyed text payloads. The bundled tools use it to avoid re-fetching within a TTL: Tool::WebScrape.visit caches a URL's rendered Markdown, Tool::Search::Engines#search caches a query's result list (keys are SHA-256 hashed, so any opaque string works).

Each tool wires its own instance against a dedicated subdir under ROOT_DIR, so a web_search query and a web_scrape URL can't collide on one file. No global singleton — pass a fresh instance, or NULL to disable caching.

One file per entry (+.txt+), freshness by mtime, no sidecar metadata; a stale entry is overwritten on the next #fetch with that key. rm -rf the directory to clear it.

Sharing: P_shared_benign — an instance holds only its TTL and directory, and every tool that owns one holds it in a class-level constant, so all agents (and other pikuri processes) hit the same files. If two callers race on the same cold key, both compute and both write the same bytes — the accepted tradeoff for keeping this a few dozen lines (worst case: a duplicate fetch). The cross-agent flow is deliberate: a public URL's content is nobody's private context.

Constant Summary collapse

ROOT_DIR =

Root dir for per-tool cache subdirs, per XDG: $XDG_CACHE_HOME/pikuri/url_cache or ~/.cache/pikuri/url_cache. Each tool picks its own subdir (+"#ROOT_DIR/web_scrape"+) so keys can't collide. Created lazily on first write.

Returns:

  • (String)
Paths.cache.join('url_cache').to_path
DEFAULT_TTL =

Default freshness window: 2 hours. Long enough to cover an interactive session (revisiting a page or re-running a search within the working window hits the cache), short enough that resuming the next day doesn't serve stale results. (opencode caches nothing; pi-web-fetch uses 15 min, pi-web-search 5.)

Returns:

  • (Integer)
2 * 60 * 60
NULL =

Null cache: always misses, never persists — a drop-in for tests (or anywhere caching should be off) keeping the #fetch contract.

Object.new

Instance Method Summary collapse

Constructor Details

#initialize(ttl:, dir:) ⇒ UrlCache

Returns a new instance of UrlCache.

Parameters:

  • ttl (Integer)

    freshness window in seconds; entries with an mtime older than this are treated as misses

  • dir (String)

    directory under which cache files live; created lazily on first write



47
48
49
50
# File 'lib/pikuri/url_cache.rb', line 47

def initialize(ttl:, dir:)
  @ttl = ttl
  @dir = dir
end

Instance Method Details

#fetch(url) ⇒ String

Return the cached payload for url if fresh, else yield to compute it, persist, and return. The block runs only on a miss; if it raises, no file is written (errors aren't cached).

Parameters:

  • url (String)

    cache key; a URL or any opaque string

Yield Returns:

  • (String)

    payload to store and return on a miss

Returns:

  • (String)

    cached or freshly-computed payload



59
60
61
62
63
64
65
66
67
# File 'lib/pikuri/url_cache.rb', line 59

def fetch(url)
  path = path_for(url)
  return File.read(path) if fresh?(path)

  content = yield
  FileUtils.mkdir_p(@dir)
  File.write(path, content)
  content
end

#fresh?(path) ⇒ Boolean

Returns true when path exists and was written within the TTL window.

Parameters:

  • path (String)

Returns:

  • (Boolean)

    true when path exists and was written within the TTL window



72
73
74
# File 'lib/pikuri/url_cache.rb', line 72

def fresh?(path)
  File.exist?(path) && Time.now - File.mtime(path) < @ttl
end

#path_for(url) ⇒ String

Returns absolute path of the cache file for url.

Parameters:

  • url (String)

Returns:

  • (String)

    absolute path of the cache file for url



78
79
80
# File 'lib/pikuri/url_cache.rb', line 78

def path_for(url)
  File.join(@dir, "#{Digest::SHA256.hexdigest(url)}.txt")
end