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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c33c559724c991d4aabc2ad9c097170e53de0002e362dcfadfe956d77eed7ec
4
- data.tar.gz: 787dbc8ce12ebacbd0b96cd8174ced19aa11665f29d3d410fb93c397dbb80d87
3
+ metadata.gz: ae9a9de76ff2dba753f10923fb0f984acc0c0e2e4f2a626a15c0ae6622d64392
4
+ data.tar.gz: ceeef4187f94f32b39569cf7f1e98c1a25559b1730c742f8c01073520456afce
5
5
  SHA512:
6
- metadata.gz: ca43bc96f5c8f426a72fb3ab722684d36b67613526b9d2e1be175abae03581c4fc0318314e92628c9064d2c7ba8637814ed950e349ba86b64caedc768619be8f
7
- data.tar.gz: 81d737f13af364095e871df44000f6293d1c062929d7f859cd3836d23c3cf602a1362a9653fd739f1726c4d1004e0bad1806e3cb3fde23dc4aef081e39dfbd6a
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
- def initialize(path, version)
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
- if d.execute("SELECT value FROM meta WHERE key = 'version' LIMIT 1;").dig(0, 0) != @version
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) > 10 * 1024 * 1024
95
- while d.execute(<<~SQL).dig(0, 0) > 10 * 1024 * 1024
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
- store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache, Fbe::VERSION)
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
@@ -10,5 +10,5 @@
10
10
  # License:: MIT
11
11
  module Fbe
12
12
  # Current version of the gem (changed by +.rultor.yml+ on every release)
13
- VERSION = '0.20.0' unless const_defined?(:VERSION)
13
+ VERSION = '0.21.0' unless const_defined?(:VERSION)
14
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fbe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko