fbe 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/fbe/middleware/sqlite_store.rb +66 -4
- data/lib/fbe/octo.rb +8 -3
- data/lib/fbe.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae9a9de76ff2dba753f10923fb0f984acc0c0e2e4f2a626a15c0ae6622d64392
|
4
|
+
data.tar.gz: ceeef4187f94f32b39569cf7f1e98c1a25559b1730c742f8c01073520456afce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2cec9308283d6d2ced937184b6e62b09bee329a10b7d65d69d369002dfb797b7e2b513611d64911d09cbaa50b7c00aec6480040f5955510297a20128528b443
|
7
|
+
data.tar.gz: 4d901bf3b1a062683dd481398e4961447676af8ae97ef8d938b321c6b0774fdadde71ba69e4875859e138e1853be2f1a734db1bf0fbe72f5ea62695c67fb67e3
|
@@ -6,26 +6,66 @@
|
|
6
6
|
require 'time'
|
7
7
|
require 'json'
|
8
8
|
require 'sqlite3'
|
9
|
+
require 'loog'
|
9
10
|
require_relative '../../fbe'
|
10
11
|
require_relative '../../fbe/middleware'
|
11
12
|
|
12
13
|
# Persisted SQLite store for Faraday::HttpCache
|
13
14
|
#
|
15
|
+
# This class provides a persistent cache store backed by SQLite for use with
|
16
|
+
# Faraday::HttpCache middleware. It's designed to cache HTTP responses from
|
17
|
+
# GitHub API calls to reduce API rate limit consumption and improve performance.
|
18
|
+
#
|
19
|
+
# Key features:
|
20
|
+
# - Automatic version management to invalidate cache on version changes
|
21
|
+
# - Size-based cache eviction (configurable, defaults to 10MB)
|
22
|
+
# - Thread-safe SQLite transactions
|
23
|
+
# - JSON serialization for cached values
|
24
|
+
# - Filtering of non-cacheable requests (non-GET, URLs with query parameters)
|
25
|
+
#
|
26
|
+
# Usage example:
|
27
|
+
# store = Fbe::Middleware::SqliteStore.new(
|
28
|
+
# '/path/to/cache.db',
|
29
|
+
# '1.0.0',
|
30
|
+
# loog: logger,
|
31
|
+
# maxsize: 50 * 1024 * 1024 # 50MB max size
|
32
|
+
# )
|
33
|
+
#
|
34
|
+
# # Use with Faraday
|
35
|
+
# Faraday.new do |builder|
|
36
|
+
# builder.use Faraday::HttpCache, store: store
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# The store automatically manages the SQLite database schema and handles
|
40
|
+
# cleanup operations when the database grows too large. Old entries are
|
41
|
+
# deleted based on their last access time to maintain the configured size limit.
|
42
|
+
#
|
14
43
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
15
44
|
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
16
45
|
# License:: MIT
|
17
46
|
class Fbe::Middleware::SqliteStore
|
18
47
|
attr_reader :path
|
19
48
|
|
20
|
-
|
49
|
+
# Initialize the SQLite store.
|
50
|
+
# @param path [String] Path to the SQLite database file
|
51
|
+
# @param version [String] Version identifier for cache compatibility
|
52
|
+
# @param loog [Loog] Logger instance (optional, defaults to Loog::NULL)
|
53
|
+
# @param maxsize [Integer] Maximum database size in bytes (optional, defaults to 10MB)
|
54
|
+
# @raise [ArgumentError] If path is nil/empty, directory doesn't exist, or version is nil/empty
|
55
|
+
def initialize(path, version, loog: Loog::NULL, maxsize: 10 * 1024 * 1024)
|
21
56
|
raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
|
22
57
|
dir = File.dirname(path)
|
23
58
|
raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
|
24
59
|
raise ArgumentError, 'Version cannot be nil or empty' if version.nil? || version.empty?
|
25
60
|
@path = File.absolute_path(path)
|
26
61
|
@version = version
|
62
|
+
@loog = loog
|
63
|
+
@maxsize = maxsize
|
27
64
|
end
|
28
65
|
|
66
|
+
# Read a value from the cache.
|
67
|
+
# @param key [String] The cache key to read
|
68
|
+
# @return [Object, nil] The cached value parsed from JSON, or nil if not found
|
29
69
|
def read(key)
|
30
70
|
value = perform do |t|
|
31
71
|
t.execute('UPDATE cache SET touched_at = ?2 WHERE key = ?1;', [key, Time.now.utc.iso8601])
|
@@ -34,11 +74,20 @@ class Fbe::Middleware::SqliteStore
|
|
34
74
|
JSON.parse(value) if value
|
35
75
|
end
|
36
76
|
|
77
|
+
# Delete a key from the cache.
|
78
|
+
# @param key [String] The cache key to delete
|
79
|
+
# @return [nil]
|
37
80
|
def delete(key)
|
38
81
|
perform { _1.execute('DELETE FROM cache WHERE key = ?', [key]) }
|
39
82
|
nil
|
40
83
|
end
|
41
84
|
|
85
|
+
# Write a value to the cache.
|
86
|
+
# @param key [String] The cache key to write
|
87
|
+
# @param value [Object] The value to cache (will be JSON encoded)
|
88
|
+
# @return [nil]
|
89
|
+
# @note Values larger than 10KB are not cached
|
90
|
+
# @note Non-GET requests and URLs with query parameters are not cached
|
42
91
|
def write(key, value)
|
43
92
|
return if value.is_a?(Array) && value.any? do |vv|
|
44
93
|
req = JSON.parse(vv[0])
|
@@ -55,6 +104,8 @@ class Fbe::Middleware::SqliteStore
|
|
55
104
|
nil
|
56
105
|
end
|
57
106
|
|
107
|
+
# Clear all entries from the cache.
|
108
|
+
# @return [void]
|
58
109
|
def clear
|
59
110
|
perform do |t|
|
60
111
|
t.execute 'DELETE FROM cache;'
|
@@ -63,6 +114,8 @@ class Fbe::Middleware::SqliteStore
|
|
63
114
|
@db.execute 'VACUUM;'
|
64
115
|
end
|
65
116
|
|
117
|
+
# Get all entries from the cache.
|
118
|
+
# @return [Array<Array>] Array of [key, value] pairs
|
66
119
|
def all
|
67
120
|
perform { _1.execute('SELECT key, value FROM cache') }
|
68
121
|
end
|
@@ -84,15 +137,22 @@ class Fbe::Middleware::SqliteStore
|
|
84
137
|
t.execute 'CREATE INDEX IF NOT EXISTS meta_key_idx ON meta(key);'
|
85
138
|
t.execute "INSERT INTO meta(key, value) VALUES('version', ?) ON CONFLICT(key) DO NOTHING;", [@version]
|
86
139
|
end
|
87
|
-
|
140
|
+
found = d.execute("SELECT value FROM meta WHERE key = 'version' LIMIT 1;").dig(0, 0)
|
141
|
+
if found != @version
|
142
|
+
@loog.info("Version mismatch in SQLite cache: stored '#{found}' != current '#{@version}', cleaning up")
|
88
143
|
d.transaction do |t|
|
89
144
|
t.execute 'DELETE FROM cache;'
|
90
145
|
t.execute "UPDATE meta SET value = ? WHERE key = 'version';", [@version]
|
91
146
|
end
|
92
147
|
d.execute 'VACUUM;'
|
93
148
|
end
|
94
|
-
if File.size(@path) >
|
95
|
-
|
149
|
+
if File.size(@path) > @maxsize
|
150
|
+
@loog.info(
|
151
|
+
"SQLite cache file size (#{File.size(@path)} bytes) exceeds " \
|
152
|
+
"#{@maxsize / 1024 / 1024}MB, cleaning up old entries"
|
153
|
+
)
|
154
|
+
deleted = 0
|
155
|
+
while d.execute(<<~SQL).dig(0, 0) > @maxsize
|
96
156
|
SELECT (page_count - freelist_count) * page_size AS size
|
97
157
|
FROM pragma_page_count(), pragma_freelist_count(), pragma_page_size();
|
98
158
|
SQL
|
@@ -101,9 +161,11 @@ class Fbe::Middleware::SqliteStore
|
|
101
161
|
DELETE FROM cache
|
102
162
|
WHERE key IN (SELECT key FROM cache ORDER BY touched_at LIMIT 50)
|
103
163
|
SQL
|
164
|
+
deleted += t.changes
|
104
165
|
end
|
105
166
|
end
|
106
167
|
d.execute 'VACUUM;'
|
168
|
+
@loog.info("Deleted #{deleted} old cache entries, new file size: #{File.size(@path)} bytes")
|
107
169
|
end
|
108
170
|
at_exit { @db&.close }
|
109
171
|
end
|
data/lib/fbe/octo.rb
CHANGED
@@ -25,6 +25,10 @@ require_relative 'middleware/sqlite_store'
|
|
25
25
|
# logging, and caching.
|
26
26
|
#
|
27
27
|
# @param [Judges::Options] options The options available globally
|
28
|
+
# @option options [String] :github_token GitHub API token for authentication
|
29
|
+
# @option options [Boolean] :testing When true, uses FakeOctokit for testing
|
30
|
+
# @option options [String] :sqlite_cache Path to SQLite cache file for HTTP responses
|
31
|
+
# @option options [Integer] :sqlite_cache_maxsize Maximum size of SQLite cache in bytes (default: 10MB)
|
28
32
|
# @param [Hash] global Hash of global options
|
29
33
|
# @param [Loog] loog Logging facility
|
30
34
|
# @return [Hash] Usually returns a JSON, as it comes from the GitHub API
|
@@ -77,11 +81,12 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
77
81
|
backoff_factor: 2
|
78
82
|
)
|
79
83
|
if options.sqlite_cache
|
80
|
-
|
84
|
+
maxsize = options.sqlite_cache_maxsize || (10 * 1024 * 1024)
|
85
|
+
store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache, Fbe::VERSION, loog:, maxsize:)
|
81
86
|
loog.info(
|
82
87
|
"Using HTTP cache in SQLite file: #{store.path} (" \
|
83
|
-
"#{File.exist?(store.path) ? "#{File.size(store.path)} bytes" : 'file is absent'}" \
|
84
|
-
|
88
|
+
"#{File.exist?(store.path) ? "#{File.size(store.path)} bytes" : 'file is absent'}, " \
|
89
|
+
"max size: #{maxsize / 1024 / 1024}MB)"
|
85
90
|
)
|
86
91
|
builder.use(
|
87
92
|
Faraday::HttpCache,
|
data/lib/fbe.rb
CHANGED