Module: Pikuri::Paths

Defined in:
lib/pikuri/paths.rb

Overview

Standardized on-disk locations for pikuri's local state, centralizing XDG resolution so every disk-caching component roots under one place instead of re-deriving it. The load-bearing distinction is Paths.cache (shared across all pikuri processes, persistent) vs. Paths.new_temp (private to this process, ephemeral) — reach for the wrong one and two concurrent processes clobber each other's scratch.

Constant Summary collapse

TEMP_STALE_SECONDS =

temp-* umbrellas older than this are reaped by sweep_stale_temps! at core load — generous enough not to disturb a long-lived process in another shell, tight enough that one killed last week doesn't leak forever.

Returns:

  • (Integer)

    seconds

7 * 24 * 60 * 60

Class Method Summary collapse

Class Method Details

.cachePathname

Pikuri's cache root: $XDG_CACHE_HOME/pikuri or ~/.cache/pikuri.

Shared across ALL pikuri processes and PERSISTENT (survives exit) — the home for state meant to be reused: the URL cache, mem0 / Qdrant / Chroma server data. For scratch private to this process that should vanish when it exits, use new_temp; reaching for cache + a fixed subdir is how two concurrent processes end up overwriting each other's files.

A method, not a constant: a constant would snapshot XDG_CACHE_HOME at require time, breaking env-stubbing in tests and a runtime change. Not created — callers mkdir_p the subdir they need.

Returns:

  • (Pathname)

    the <cache home>/pikuri directory



35
36
37
38
39
# File 'lib/pikuri/paths.rb', line 35

def self.cache
  home = ENV['XDG_CACHE_HOME']
  home = File.expand_path('~/.cache') if home.nil? || home.empty?
  Pathname.new(home).join('pikuri')
end

.configPathname

Pikuri's config root: $XDG_CONFIG_HOME/pikuri or ~/.config/pikuri. Home for user-owned, hand-edited config (the OS helper's MACHINE.md).

A method for the same reason as cache. Not created — config is user-owned, so callers mkdir_p only when writing.

Returns:

  • (Pathname)

    the <config home>/pikuri directory



101
102
103
104
105
# File 'lib/pikuri/paths.rb', line 101

def self.config
  home = ENV['XDG_CONFIG_HOME']
  home = File.expand_path('~/.config') if home.nil? || home.empty?
  Pathname.new(home).join('pikuri')
end

.new_tempPathname

A fresh scratch dir private to this process, reaped at exit via Finalizers (and stale-swept by sweep_stale_temps! for strays a crash leaves behind). Mint one per concurrent-safe scratch need and hold the result:

dir  = Paths.new_temp                       # ~/.cache/pikuri/temp-XXXX/
snap = Snapshot.new(source: db, dir: dir)   # can't collide with another process

Minted IN HOME under cache — never /tmp — for two concrete reasons: a cp --reflink=auto copy shares extents only on the source's filesystem (the copied DB lives under $HOME), and the bubblewrap sandbox binds its scratch at /tmp inside, so an umbrella already under /tmp would bind on top of itself.

A factory — a new dir each call (hence new_, not a memoized accessor like cache); a caller memoizes if it wants one stable dir, but per consumer, not per process. Memoizing one at process scope and handing it to several agents puts two writers on one path — the collision this exists to prevent, moved inside the process.

Returns:

  • (Pathname)

    absolute, symlink-resolved path of the freshly-made dir



62
63
64
65
66
67
# File 'lib/pikuri/paths.rb', line 62

def self.new_temp
  FileUtils.mkdir_p(cache)
  path = Pathname.new(Dir.mktmpdir('temp-', cache)).realpath
  Pikuri::Finalizers.register { FileUtils.remove_entry(path.to_s) if path.exist? }
  path
end

.statePathname

Pikuri's state root: $XDG_STATE_HOME/pikuri or ~/.local/state/pikuri. Created automatically. For state that persists across restarts but isn't portable enough for XDG_DATA_HOME (logs, history, layout).

Returns:

  • (Pathname)

    the <state home>/pikuri directory



112
113
114
115
116
117
118
# File 'lib/pikuri/paths.rb', line 112

def self.state
  state = ENV['XDG_STATE_HOME']
  state = File.expand_path('~/.local/state') if state.nil? || state.empty?
  state = Pathname.new(state).join('pikuri')
  FileUtils.mkdir_p(state.to_path)
  state
end

.sweep_stale_temps!void

This method returns an undefined value.

Reap temp-* umbrellas under cache older than TEMP_STALE_SECONDS. Called once at core load (so every binary sweeps, regardless of which gems it wires) — the net for a process killed before its Finalizers reap could fire. Failures are swallowed (best-effort; the Finalizers removal is the load-bearing path), and recent dirs are left for concurrent processes.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/pikuri/paths.rb', line 77

def self.sweep_stale_temps!
  return unless File.directory?(cache)

  cutoff = Time.now - TEMP_STALE_SECONDS
  Dir.children(cache).each do |entry|
    next unless entry.start_with?('temp-')

    path = File.join(cache, entry)
    next unless File.directory?(path)
    next if File.mtime(path) > cutoff

    FileUtils.remove_entry(path)
  rescue StandardError
    # best-effort sweep; never block the host on dead state
  end
end