Class: Tina4::CacheBackends::FileBackend

Inherits:
BaseBackend show all
Defined in:
lib/tina4/cache_backends/file_backend.rb

Overview

File-based cache — stores entries as JSON files in data/cache/ (parity with Python _FileBackend). Always available; used as the graceful-degrade target when a configured network backend is unreachable.

Instance Method Summary collapse

Methods inherited from BaseBackend

#available?

Constructor Details

#initialize(cache_dir: "data/cache", max_entries: 1000) ⇒ FileBackend

Returns a new instance of FileBackend.



14
15
16
17
18
19
20
21
# File 'lib/tina4/cache_backends/file_backend.rb', line 14

def initialize(cache_dir: "data/cache", max_entries: 1000)
  @dir = cache_dir
  @max_entries = max_entries
  @mutex = Mutex.new
  @hits = 0
  @misses = 0
  FileUtils.mkdir_p(@dir)
end

Instance Method Details

#clearObject



78
79
80
81
82
83
84
# File 'lib/tina4/cache_backends/file_backend.rb', line 78

def clear
  @mutex.synchronize do
    @hits = 0
    @misses = 0
    Dir.glob(File.join(@dir, "*.json")).each { |f| File.delete(f) rescue nil }
  end
end

#delete(key) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/tina4/cache_backends/file_backend.rb', line 66

def delete(key)
  path = key_path(key)
  @mutex.synchronize do
    if File.exist?(path)
      File.delete(path) rescue nil
      true
    else
      false
    end
  end
end

#get(key) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/tina4/cache_backends/file_backend.rb', line 23

def get(key)
  path = key_path(key)
  @mutex.synchronize do
    unless File.exist?(path)
      @misses += 1
      return nil
    end
    begin
      data = JSON.parse(File.read(path))
      expires_at = data["expires_at"]
      if expires_at && Time.now.to_f > expires_at
        File.delete(path) rescue nil
        @misses += 1
        return nil
      end
      @hits += 1
      data["value"]
    rescue JSON::ParserError, SystemCallError
      @misses += 1
      nil
    end
  end
end

#nameObject



106
107
108
# File 'lib/tina4/cache_backends/file_backend.rb', line 106

def name
  "file"
end

#set(key, value, ttl) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/tina4/cache_backends/file_backend.rb', line 47

def set(key, value, ttl)
  expires_at = ttl > 0 ? Time.now.to_f + ttl : nil
  entry = { "key" => key, "value" => value, "expires_at" => expires_at }
  @mutex.synchronize do
    FileUtils.mkdir_p(@dir)
    begin
      files = Dir.glob(File.join(@dir, "*.json")).sort_by { |f| File.mtime(f) }
      while files.size >= @max_entries
        File.delete(files.shift) rescue nil
      end
    rescue SystemCallError
    end
    begin
      File.write(key_path(key), JSON.generate(entry))
    rescue SystemCallError
    end
  end
end

#statsObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/tina4/cache_backends/file_backend.rb', line 86

def stats
  @mutex.synchronize do
    now = Time.now.to_f
    count = 0
    Dir.glob(File.join(@dir, "*.json")).each do |f|
      begin
        data = JSON.parse(File.read(f))
        exp = data["expires_at"]
        if exp && now > exp
          File.delete(f) rescue nil
        else
          count += 1
        end
      rescue JSON::ParserError, SystemCallError
      end
    end
    { hits: @hits, misses: @misses, size: count, backend: "file" }
  end
end

#sweepInteger

Actively delete expired entries and return the number removed. (The file backend is the only one that supports an explicit sweep —network/db backends expire lazily via TTL.)

Returns:

  • (Integer)


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/tina4/cache_backends/file_backend.rb', line 115

def sweep
  removed = 0
  now = Time.now.to_f
  @mutex.synchronize do
    Dir.glob(File.join(@dir, "*.json")).each do |f|
      begin
        data = JSON.parse(File.read(f))
        if data["expires_at"] && now > data["expires_at"]
          File.delete(f) rescue nil
          removed += 1
        end
      rescue JSON::ParserError, SystemCallError
      end
    end
  end
  removed
end