Start stubbing an S3 blob cache

This commit is contained in:
Alexander Olofsson 2024-09-07 20:59:50 +02:00
parent 899f8c6fbc
commit b5f2c2df1a
Signed by: ace
GPG key ID: D439C9470CB04C73
4 changed files with 98 additions and 10 deletions

View file

@ -2,9 +2,9 @@
begin begin
require 'cbor' require 'cbor'
rescue LoadError rescue LoadError # Load CBOR if possible
require 'json'
end end
require 'json'
module FicTracker::Util module FicTracker::Util
class Cache class Cache
@ -26,6 +26,9 @@ module FicTracker::Util
when :redis, 'redis' when :redis, 'redis'
require_relative 'cache/redis' require_relative 'cache/redis'
cache = CacheImpl::Redis.new(**options) cache = CacheImpl::Redis.new(**options)
when :s3, 'S3'
require_relative 'cache/s3'
cache = CacheImpl::Redis.new(**options)
when :none, 'none', nil when :none, 'none', nil
require_relative 'cache/dummy' require_relative 'cache/dummy'
cache = CacheImpl::Dummy.new cache = CacheImpl::Dummy.new
@ -127,8 +130,8 @@ module FicTracker::Util
def best_encoder?(data) def best_encoder?(data)
pod_encoder = :none if data.is_a?(String) pod_encoder = :none if data.is_a?(String)
pod_encoder ||= :cbor if Object.const_defined?(:CBOR) pod_encoder ||= :cbor if Object.const_defined?(:CBOR) && data.respond_to?(:to_cbor)
pod_encoder ||= :json if Object.const_defined?(:JSON) pod_encoder ||= :json if Object.const_defined?(:JSON) && data.respond_to?(:to_json)
pod_encoder ||= :marshal pod_encoder ||= :marshal
return pod_encoder if is_pod?(data) return pod_encoder if is_pod?(data)
@ -139,11 +142,9 @@ module FicTracker::Util
encode ||= @encoder encode ||= @encoder
encode ||= best_encoder?(data) encode ||= best_encoder?(data)
flags = 0 flags = ENCODING_NONE
case encode case encode
when :none when :none
data = data
flags |= ENCODING_NONE
when :marshal when :marshal
data = Marshal.dump(data) data = Marshal.dump(data)
flags |= ENCODING_MARSHAL flags |= ENCODING_MARSHAL

View file

@ -12,8 +12,8 @@ module FicTracker::Util::CacheImpl
end end
def expire def expire
@dataset.where { Sequel::CURRENT_DATE >= expire_at }.update(expired: true)
dataset_expired.delete dataset_expired.delete
@dataset.where { Sequel::CURRENT_DATE >= expire_at }.update(expired: true)
end end
def has?(key) def has?(key)

View file

@ -7,7 +7,7 @@ module FicTracker::Util::CacheImpl
# A filesystem-backed in-memory cache # A filesystem-backed in-memory cache
class File < Base class File < Base
def initialize(dir: nil) def initialize(dir: nil)
@dir = dir || Dir.mktmpdir('ficagg') @dir = dir || Dir.mktmpdir('fictrack')
@internal = Memory.new @internal = Memory.new
end end
@ -88,7 +88,7 @@ module FicTracker::Util::CacheImpl
def meta_valid?(meta) def meta_valid?(meta)
return true unless meta[:ttl] return true unless meta[:ttl]
Time.now < Time.at(meta[:ttl]) Time.now < Time.at(meta[:ttl])
end end

87
lib/fic_tracker/util/cache/s3.rb vendored Normal file
View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 's3-light'
class S3Light::Object
def read
response = client.with_connection do |connection|
connection.make_request(:get, "/#{bucket.name}/#{key}")
end
response.body.to_s
end
end
module FicTracker::Util::CacheImpl
# A cache using an S3 bucket
class S3 < Base
def initialize(access_key:, secret_key:, bucket:, **s3)
@connection = nil # ...
@bucket = nil # ...
@objects = @bucket.objects
load_bucket!
end
def expire
to_destroy = @objects.all.reject do |obj|
obj.key.end_with?('.meta') || check_meta(obj.key)
end
return if to_destroy.empty?
@objects.destroy_batch keys: to_destroy.map(&:key)
end
def has?(key)
return check_meta key
end
def get(key)
return nil unless check_meta key
@objects.find_by(key:).read
end
def set(key, value, expiry = nil)
expiry = expiry >= 0 ? Time.now + expiry : nil if expiry.is_a?(Numeric)
expiry = nil if expiry.is_a?(Numeric)
meta = {
key: key,
ttl: expiry&.to_i
}.compact.to_json
@objects.new(key:, input: value).save!
@objects.new(key: "#{key}.meta", input: meta.to_json).save!
value
end
def delete(key)
@objects.destroy_batch keys: [
key,
"#{key}.meta"
]
nil
end
def clear
to_destroy = @objects.all.map(&:key)
return if to_destroy.empty?
@objects.destroy_batch keys: to_destroy
nil
end
private
def check_meta(key)
meta_key = "#{key}.meta"
return true unless @objects.exists? meta_key
meta = JSON.parse(@objects.find_by(key: meta_key))
return true unless meta[:ttl]
Time.now < Time.at(meta[:ttl])
end
end
end