diff --git a/lib/fic_tracker/util/cache.rb b/lib/fic_tracker/util/cache.rb index 705a59f..11dc5bb 100644 --- a/lib/fic_tracker/util/cache.rb +++ b/lib/fic_tracker/util/cache.rb @@ -2,9 +2,9 @@ begin require 'cbor' -rescue LoadError - require 'json' +rescue LoadError # Load CBOR if possible end +require 'json' module FicTracker::Util class Cache @@ -26,6 +26,9 @@ module FicTracker::Util when :redis, 'redis' require_relative 'cache/redis' cache = CacheImpl::Redis.new(**options) + when :s3, 'S3' + require_relative 'cache/s3' + cache = CacheImpl::Redis.new(**options) when :none, 'none', nil require_relative 'cache/dummy' cache = CacheImpl::Dummy.new @@ -127,8 +130,8 @@ module FicTracker::Util def best_encoder?(data) pod_encoder = :none if data.is_a?(String) - pod_encoder ||= :cbor if Object.const_defined?(:CBOR) - pod_encoder ||= :json if Object.const_defined?(:JSON) + pod_encoder ||= :cbor if Object.const_defined?(:CBOR) && data.respond_to?(:to_cbor) + pod_encoder ||= :json if Object.const_defined?(:JSON) && data.respond_to?(:to_json) pod_encoder ||= :marshal return pod_encoder if is_pod?(data) @@ -139,11 +142,9 @@ module FicTracker::Util encode ||= @encoder encode ||= best_encoder?(data) - flags = 0 + flags = ENCODING_NONE case encode when :none - data = data - flags |= ENCODING_NONE when :marshal data = Marshal.dump(data) flags |= ENCODING_MARSHAL diff --git a/lib/fic_tracker/util/cache/database.rb b/lib/fic_tracker/util/cache/database.rb index d3466b0..e328fc6 100644 --- a/lib/fic_tracker/util/cache/database.rb +++ b/lib/fic_tracker/util/cache/database.rb @@ -12,8 +12,8 @@ module FicTracker::Util::CacheImpl end def expire - @dataset.where { Sequel::CURRENT_DATE >= expire_at }.update(expired: true) dataset_expired.delete + @dataset.where { Sequel::CURRENT_DATE >= expire_at }.update(expired: true) end def has?(key) diff --git a/lib/fic_tracker/util/cache/file.rb b/lib/fic_tracker/util/cache/file.rb index ea13ed8..f6d92c8 100644 --- a/lib/fic_tracker/util/cache/file.rb +++ b/lib/fic_tracker/util/cache/file.rb @@ -7,7 +7,7 @@ module FicTracker::Util::CacheImpl # A filesystem-backed in-memory cache class File < Base def initialize(dir: nil) - @dir = dir || Dir.mktmpdir('ficagg') + @dir = dir || Dir.mktmpdir('fictrack') @internal = Memory.new end @@ -88,7 +88,7 @@ module FicTracker::Util::CacheImpl def meta_valid?(meta) return true unless meta[:ttl] - + Time.now < Time.at(meta[:ttl]) end diff --git a/lib/fic_tracker/util/cache/s3.rb b/lib/fic_tracker/util/cache/s3.rb new file mode 100644 index 0000000..21677a8 --- /dev/null +++ b/lib/fic_tracker/util/cache/s3.rb @@ -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