factorix 0.5.1 → 0.7.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +1 -1
  4. data/exe/factorix +17 -0
  5. data/lib/factorix/api/mod_download_api.rb +11 -6
  6. data/lib/factorix/api/mod_info.rb +2 -2
  7. data/lib/factorix/api/mod_management_api.rb +1 -1
  8. data/lib/factorix/api/mod_portal_api.rb +6 -49
  9. data/lib/factorix/api_credential.rb +1 -1
  10. data/lib/factorix/cache/base.rb +116 -0
  11. data/lib/factorix/cache/entry.rb +25 -0
  12. data/lib/factorix/cache/file_system.rb +137 -57
  13. data/lib/factorix/cache/redis.rb +287 -0
  14. data/lib/factorix/cache/s3.rb +388 -0
  15. data/lib/factorix/cli/commands/backup_support.rb +1 -1
  16. data/lib/factorix/cli/commands/base.rb +3 -3
  17. data/lib/factorix/cli/commands/cache/evict.rb +19 -24
  18. data/lib/factorix/cli/commands/cache/stat.rb +66 -67
  19. data/lib/factorix/cli/commands/command_wrapper.rb +5 -5
  20. data/lib/factorix/cli/commands/completion.rb +1 -2
  21. data/lib/factorix/cli/commands/confirmable.rb +1 -1
  22. data/lib/factorix/cli/commands/download_support.rb +2 -7
  23. data/lib/factorix/cli/commands/mod/check.rb +1 -1
  24. data/lib/factorix/cli/commands/mod/disable.rb +1 -1
  25. data/lib/factorix/cli/commands/mod/download.rb +7 -7
  26. data/lib/factorix/cli/commands/mod/edit.rb +10 -13
  27. data/lib/factorix/cli/commands/mod/enable.rb +1 -1
  28. data/lib/factorix/cli/commands/mod/image/add.rb +3 -6
  29. data/lib/factorix/cli/commands/mod/image/edit.rb +2 -5
  30. data/lib/factorix/cli/commands/mod/image/list.rb +5 -8
  31. data/lib/factorix/cli/commands/mod/install.rb +7 -7
  32. data/lib/factorix/cli/commands/mod/list.rb +7 -7
  33. data/lib/factorix/cli/commands/mod/search.rb +13 -12
  34. data/lib/factorix/cli/commands/mod/settings/dump.rb +3 -3
  35. data/lib/factorix/cli/commands/mod/settings/restore.rb +2 -2
  36. data/lib/factorix/cli/commands/mod/show.rb +22 -23
  37. data/lib/factorix/cli/commands/mod/sync.rb +8 -8
  38. data/lib/factorix/cli/commands/mod/uninstall.rb +1 -1
  39. data/lib/factorix/cli/commands/mod/update.rb +11 -43
  40. data/lib/factorix/cli/commands/mod/upload.rb +7 -10
  41. data/lib/factorix/cli/commands/path.rb +2 -2
  42. data/lib/factorix/cli/commands/portal_support.rb +27 -0
  43. data/lib/factorix/cli/commands/version.rb +1 -1
  44. data/lib/factorix/container.rb +155 -0
  45. data/lib/factorix/dependency/parser.rb +1 -1
  46. data/lib/factorix/errors.rb +3 -0
  47. data/lib/factorix/http/cache_decorator.rb +5 -5
  48. data/lib/factorix/http/client.rb +3 -3
  49. data/lib/factorix/info_json.rb +7 -7
  50. data/lib/factorix/mod_list.rb +2 -2
  51. data/lib/factorix/mod_settings.rb +2 -2
  52. data/lib/factorix/portal.rb +3 -2
  53. data/lib/factorix/runtime/user_configurable.rb +9 -9
  54. data/lib/factorix/service_credential.rb +3 -3
  55. data/lib/factorix/transfer/downloader.rb +19 -11
  56. data/lib/factorix/version.rb +1 -1
  57. data/lib/factorix.rb +110 -1
  58. data/sig/factorix/api/mod_download_api.rbs +1 -2
  59. data/sig/factorix/cache/base.rbs +28 -0
  60. data/sig/factorix/cache/entry.rbs +14 -0
  61. data/sig/factorix/cache/file_system.rbs +7 -6
  62. data/sig/factorix/cache/redis.rbs +36 -0
  63. data/sig/factorix/cache/s3.rbs +38 -0
  64. data/sig/factorix/container.rbs +15 -0
  65. data/sig/factorix/errors.rbs +3 -0
  66. data/sig/factorix/portal.rbs +1 -1
  67. data/sig/factorix.rbs +99 -0
  68. metadata +27 -4
  69. data/lib/factorix/application.rb +0 -218
  70. data/sig/factorix/application.rbs +0 -86
@@ -50,9 +50,9 @@ module Factorix
50
50
  # Don't cache streaming requests
51
51
  return client.get(uri, headers:, &block) if block
52
52
 
53
- key = cache.key_for(uri.to_s)
53
+ cache_key = uri.to_s
54
54
 
55
- cached_body = cache.read(key)
55
+ cached_body = cache.read(cache_key)
56
56
  if cached_body
57
57
  logger.debug("Cache hit", uri: uri.to_s)
58
58
  publish("cache.hit", url: uri.to_s)
@@ -63,9 +63,9 @@ module Factorix
63
63
  publish("cache.miss", url: uri.to_s)
64
64
 
65
65
  # Locking prevents concurrent downloads of the same resource
66
- cache.with_lock(key) do
66
+ cache.with_lock(cache_key) do
67
67
  # Double-check: another thread might have filled the cache
68
- cached_body = cache.read(key)
68
+ cached_body = cache.read(cache_key)
69
69
  if cached_body
70
70
  publish("cache.hit", url: uri.to_s)
71
71
  return CachedResponse.new(cached_body)
@@ -77,7 +77,7 @@ module Factorix
77
77
  with_temporary_file do |temp|
78
78
  temp.write(response.body)
79
79
  temp.close
80
- cache.store(key, Pathname(temp.path))
80
+ cache.store(cache_key, Pathname(temp.path))
81
81
  end
82
82
  end
83
83
 
@@ -162,9 +162,9 @@ module Factorix
162
162
  Net::HTTP.new(uri.host, uri.port).tap do |http|
163
163
  http.use_ssl = uri.scheme == "https"
164
164
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
165
- http.open_timeout = Application.config.http.connect_timeout
166
- http.read_timeout = Application.config.http.read_timeout
167
- http.write_timeout = Application.config.http.write_timeout if http.respond_to?(:write_timeout=)
165
+ http.open_timeout = Factorix.config.http.connect_timeout
166
+ http.read_timeout = Factorix.config.http.read_timeout
167
+ http.write_timeout = Factorix.config.http.write_timeout if http.respond_to?(:write_timeout=)
168
168
  end
169
169
  end
170
170
 
@@ -47,21 +47,21 @@ module Factorix
47
47
  # @return [InfoJSON] parsed info.json from zip
48
48
  # @raise [FileFormatError] if zip is invalid or info.json not found
49
49
  def self.from_zip(zip_path)
50
- cache = Application.resolve(:info_json_cache)
51
- logger = Application.resolve(:logger)
52
- cache_key = cache.key_for(zip_path.to_s)
50
+ cache = Container.resolve(:info_json_cache)
51
+ logger = Container.resolve(:logger)
52
+ cache_key = zip_path.to_s
53
53
 
54
- if (cached_json = cache.read(cache_key, encoding: Encoding::UTF_8))
54
+ if (cached_json = cache.read(cache_key))
55
55
  logger.debug("info.json cache hit", path: zip_path.to_s)
56
- return from_json(cached_json)
56
+ return from_json((+cached_json).force_encoding(Encoding::UTF_8))
57
57
  end
58
58
 
59
59
  logger.debug("info.json cache miss", path: zip_path.to_s)
60
60
 
61
61
  cache.with_lock(cache_key) do
62
- if (cached_json = cache.read(cache_key, encoding: Encoding::UTF_8))
62
+ if (cached_json = cache.read(cache_key))
63
63
  logger.debug("info.json cache hit (after lock)", path: zip_path.to_s)
64
- return from_json(cached_json)
64
+ return from_json((+cached_json).force_encoding(Encoding::UTF_8))
65
65
  end
66
66
 
67
67
  json_string = Zip::File.open(zip_path) {|zip_file|
@@ -18,7 +18,7 @@ module Factorix
18
18
  # @param path [Pathname] the path to the file to load the MOD list from (default: runtime.mod_list_path)
19
19
  # @return [Factorix::MODList] the loaded MOD list
20
20
  # @raise [MODSettingsError] if the base MOD is disabled
21
- def self.load(path=Application[:runtime].mod_list_path)
21
+ def self.load(path=Container[:runtime].mod_list_path)
22
22
  raw_data = JSON.parse(path.read, symbolize_names: true)
23
23
  mods_hash = raw_data[:mods].to_h {|entry|
24
24
  mod = MOD[name: entry[:name]]
@@ -50,7 +50,7 @@ module Factorix
50
50
  #
51
51
  # @param path [Pathname] the path to the file to save the MOD list to (default: runtime.mod_list_path)
52
52
  # @return [void]
53
- def save(path=Application[:runtime].mod_list_path)
53
+ def save(path=Container[:runtime].mod_list_path)
54
54
  mods_data = @mods.map {|mod, state|
55
55
  data = {name: mod.name, enabled: state.enabled?}
56
56
  # Only include version in the output if it exists
@@ -108,7 +108,7 @@ module Factorix
108
108
  #
109
109
  # @param path [Pathname] Path to the MOD settings file (default: runtime.mod_settings_path)
110
110
  # @return [MODSettings] New MODSettings instance
111
- def self.load(path=Application[:runtime].mod_settings_path)
111
+ def self.load(path=Container[:runtime].mod_settings_path)
112
112
  path.open("rb") do |io|
113
113
  game_version, sections = load_settings_from_io(io)
114
114
  new(game_version, sections)
@@ -244,7 +244,7 @@ module Factorix
244
244
  #
245
245
  # @param path [Pathname] Path to save the MOD settings file (default: runtime.mod_settings_path)
246
246
  # @return [void]
247
- def save(path=Application[:runtime].mod_settings_path)
247
+ def save(path=Container[:runtime].mod_settings_path)
248
248
  path.open("wb") do |file|
249
249
  serializer = SerDes::Serializer.new(file)
250
250
 
@@ -72,12 +72,13 @@ module Factorix
72
72
  #
73
73
  # @param release [API::Release] release object containing download_url and sha1
74
74
  # @param output [Pathname] output file path
75
+ # @param handler [Object, nil] event handler for download progress (optional)
75
76
  # @return [void]
76
77
  # @raise [DigestMismatchError] if SHA1 verification fails
77
- def download_mod(release, output)
78
+ def download_mod(release, output, handler: nil)
78
79
  # Extract path from URI::HTTPS
79
80
  download_path = release.download_url.path
80
- mod_download_api.download(download_path, output, expected_sha1: release.sha1)
81
+ mod_download_api.download(download_path, output, expected_sha1: release.sha1, handler:)
81
82
  end
82
83
 
83
84
  # Upload a MOD file to the portal
@@ -8,10 +8,10 @@ module Factorix
8
8
  # auto-detected paths via configuration. When a configured path is available,
9
9
  # it is used instead of platform-specific auto-detection.
10
10
  #
11
- # Configuration is done via Application.config:
11
+ # Configuration is done via Factorix.config:
12
12
  #
13
13
  # @example Configure paths in config file
14
- # Factorix::Application.configure do |config|
14
+ # Factorix.configure do |config|
15
15
  # config.runtime.executable_path = "/opt/factorio/bin/x64/factorio"
16
16
  # config.runtime.user_dir = "/home/user/.factorio"
17
17
  # end
@@ -46,20 +46,20 @@ module Factorix
46
46
  def data_dir = configurable_path(:data_dir, example_path: "/path/to/factorio/data") { super }
47
47
 
48
48
  private def configurable_path(name, example_path:)
49
- if (configured = Application.config.runtime.public_send(name))
50
- Application[:logger].debug("Using configured #{name}", path: configured.to_s)
49
+ if (configured = Factorix.config.runtime.public_send(name))
50
+ Container[:logger].debug("Using configured #{name}", path: configured.to_s)
51
51
  configured
52
52
  else
53
- Application[:logger].debug("No configuration for #{name}, using auto-detection")
54
- yield.tap {|path| Application[:logger].debug("Auto-detected #{name}", path: path.to_s) }
53
+ Container[:logger].debug("No configuration for #{name}, using auto-detection")
54
+ yield.tap {|path| Container[:logger].debug("Auto-detected #{name}", path: path.to_s) }
55
55
  end
56
56
  rescue NotImplementedError => e
57
- Application[:logger].error("Auto-detection failed and no configuration provided", error: e.message)
57
+ Container[:logger].error("Auto-detection failed and no configuration provided", error: e.message)
58
58
  raise ConfigurationError, <<~MESSAGE
59
59
  #{name} not configured and auto-detection is not supported for this platform.
60
- Please configure it in #{Application[:runtime].factorix_config_path}:
60
+ Please configure it in #{Container[:runtime].factorix_config_path}:
61
61
 
62
- Factorix::Application.configure do |config|
62
+ Factorix.configure do |config|
63
63
  config.runtime.#{name} = "#{example_path}"
64
64
  end
65
65
  MESSAGE
@@ -39,7 +39,7 @@ module Factorix
39
39
  elsif username_env || token_env
40
40
  raise CredentialError, "Both #{ENV_USERNAME} and #{ENV_TOKEN} must be set (or neither)"
41
41
  else
42
- runtime = Application[:runtime]
42
+ runtime = Container[:runtime]
43
43
  from_player_data(runtime:)
44
44
  end
45
45
  end
@@ -49,7 +49,7 @@ module Factorix
49
49
  # @return [ServiceCredential] new instance with credentials from environment
50
50
  # @raise [CredentialError] if username or token is not set or empty
51
51
  def self.from_env
52
- logger = Application["logger"]
52
+ logger = Container["logger"]
53
53
  logger.debug "Loading service credentials from environment"
54
54
 
55
55
  username = ENV.fetch(ENV_USERNAME, nil)
@@ -83,7 +83,7 @@ module Factorix
83
83
  # @raise [Errno::ENOENT] if player-data.json does not exist
84
84
  # @raise [CredentialError] if username or token is missing in player-data.json
85
85
  def self.from_player_data(runtime:)
86
- logger = Application["logger"]
86
+ logger = Container["logger"]
87
87
  logger.debug "Loading service credentials from player-data.json"
88
88
 
89
89
  player_data_path = runtime.player_data_path
@@ -53,9 +53,9 @@ module Factorix
53
53
  end
54
54
 
55
55
  logger.info("Starting download", output: output.to_s)
56
- key = cache.key_for(url.to_s)
56
+ cache_key = strip_query(url)
57
57
 
58
- case try_cache_hit(key, output, expected_sha1:)
58
+ case try_cache_hit(cache_key, output, expected_sha1:)
59
59
  when :hit
60
60
  return
61
61
  when :miss
@@ -68,14 +68,14 @@ module Factorix
68
68
  raise RuntimeError, "Unexpected cache state"
69
69
  end
70
70
 
71
- cache.with_lock(key) do
72
- return if try_cache_hit(key, output, expected_sha1:) == :hit
71
+ cache.with_lock(cache_key) do
72
+ return if try_cache_hit(cache_key, output, expected_sha1:) == :hit
73
73
 
74
74
  with_temporary_file do |temp_file|
75
75
  download_file_with_progress(url, temp_file)
76
76
  verify_sha1(temp_file, expected_sha1) if expected_sha1
77
- cache.store(key, temp_file)
78
- cache.fetch(key, output)
77
+ cache.store(cache_key, temp_file)
78
+ cache.write_to(cache_key, output)
79
79
  end
80
80
  end
81
81
  end
@@ -112,21 +112,21 @@ module Factorix
112
112
  # If the cached file exists but fails SHA1 verification, the cache entry
113
113
  # is invalidated and :corrupted is returned to trigger re-download.
114
114
  #
115
- # @param key [String] cache key
115
+ # @param cache_key [String] logical cache key (URL string)
116
116
  # @param output [Pathname] path to save the cached file
117
117
  # @param expected_sha1 [String, nil] expected SHA1 digest for verification (optional)
118
118
  # @return [Symbol] :hit if cache hit with valid SHA1, :miss if not cached, :corrupted if SHA1 mismatch
119
- private def try_cache_hit(key, output, expected_sha1:)
120
- return :miss unless cache.fetch(key, output)
119
+ private def try_cache_hit(cache_key, output, expected_sha1:)
120
+ return :miss unless cache.write_to(cache_key, output)
121
121
 
122
122
  logger.info("Cache hit", output: output.to_s)
123
123
  verify_sha1(output, expected_sha1) if expected_sha1
124
- total_size = cache.size(key)
124
+ total_size = cache.size(cache_key)
125
125
  publish("cache.hit", output: output.to_s, total_size:)
126
126
  :hit
127
127
  rescue DigestMismatchError => e
128
128
  logger.warn("Cache corrupted, invalidating", output: output.to_s, error: e.message)
129
- cache.delete(key)
129
+ cache.delete(cache_key)
130
130
  :corrupted
131
131
  end
132
132
 
@@ -144,6 +144,14 @@ module Factorix
144
144
  raise DigestMismatchError, "SHA1 mismatch: expected #{expected_sha1}, got #{actual_sha1}"
145
145
  end
146
146
 
147
+ # Strip query parameters from a URI to create a safe cache key.
148
+ # This prevents sensitive data (e.g., authentication tokens) from being
149
+ # stored in cache metadata.
150
+ #
151
+ # @param uri [URI] the URI to strip
152
+ # @return [String] URI string without query parameters
153
+ private def strip_query(uri) = uri.dup.tap {|u| u.query = nil }.to_s
154
+
147
155
  # Create a temporary file for downloading, ensuring cleanup after use.
148
156
  #
149
157
  # @yield [Pathname] the temporary file path
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Factorix
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  public_constant :VERSION
6
6
  end
data/lib/factorix.rb CHANGED
@@ -1,13 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/auto_inject"
4
+ require "dry/configurable"
4
5
  require "zeitwerk"
5
6
  require_relative "factorix/errors"
6
7
  require_relative "factorix/version"
7
8
 
8
9
  # Factorix provides a CLI for Factorio MOD management, settings synchronization,
9
10
  # and MOD Portal integration.
11
+ #
12
+ # @example Configure Factorix
13
+ # Factorix.configure do |config|
14
+ # config.log_level = :debug
15
+ # config.http.connect_timeout = 10
16
+ # end
10
17
  module Factorix
18
+ extend Dry::Configurable
19
+
20
+ # Log level (:debug, :info, :warn, :error, :fatal)
21
+ setting :log_level, default: :info
22
+
23
+ # Runtime settings (optional overrides for auto-detection)
24
+ setting :runtime do
25
+ setting :executable_path, constructor: ->(v) { v ? Pathname(v) : nil }
26
+ setting :user_dir, constructor: ->(v) { v ? Pathname(v) : nil }
27
+ setting :data_dir, constructor: ->(v) { v ? Pathname(v) : nil }
28
+ end
29
+
30
+ # HTTP timeout settings
31
+ setting :http do
32
+ setting :connect_timeout, default: 5
33
+ setting :read_timeout, default: 30
34
+ setting :write_timeout, default: 30
35
+ end
36
+
37
+ # Cache settings
38
+ # Each cache type can have its own backend with hierarchical configuration.
39
+ # Common settings (backend, ttl) apply to all backends.
40
+ # Backend-specific settings are nested under the backend name.
41
+ setting :cache do
42
+ # Download cache settings (for MOD files)
43
+ setting :download do
44
+ setting :backend, default: :file_system
45
+ setting :ttl, default: nil # nil for unlimited (MOD files are immutable)
46
+ setting :file_system do
47
+ setting :max_file_size, default: nil # nil for unlimited
48
+ setting :compression_threshold, default: nil # nil for no compression (binary files)
49
+ end
50
+ setting :redis do
51
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
52
+ setting :lock_timeout, default: 30
53
+ end
54
+ setting :s3 do
55
+ setting :bucket, default: nil # required when using S3 backend
56
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
57
+ setting :lock_timeout, default: 30
58
+ end
59
+ end
60
+
61
+ # API cache settings (for API responses)
62
+ setting :api do
63
+ setting :backend, default: :file_system
64
+ setting :ttl, default: 3600 # 1 hour (API responses may change)
65
+ setting :file_system do
66
+ setting :max_file_size, default: 10 * 1024 * 1024 # 10MiB (JSON responses)
67
+ setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
68
+ end
69
+ setting :redis do
70
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
71
+ setting :lock_timeout, default: 30
72
+ end
73
+ setting :s3 do
74
+ setting :bucket, default: nil # required when using S3 backend
75
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
76
+ setting :lock_timeout, default: 30
77
+ end
78
+ end
79
+
80
+ # info.json cache settings (for MOD metadata from ZIP files)
81
+ setting :info_json do
82
+ setting :backend, default: :file_system
83
+ setting :ttl, default: nil # nil for unlimited (info.json is immutable within a MOD ZIP)
84
+ setting :file_system do
85
+ setting :max_file_size, default: nil # nil for unlimited (info.json is small)
86
+ setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
87
+ end
88
+ setting :redis do
89
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
90
+ setting :lock_timeout, default: 30
91
+ end
92
+ setting :s3 do
93
+ setting :bucket, default: nil # required when using S3 backend
94
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
95
+ setting :lock_timeout, default: 30
96
+ end
97
+ end
98
+ end
99
+
100
+ # Load configuration from file
101
+ #
102
+ # @param path [Pathname, nil] configuration file path
103
+ # @return [void]
104
+ # @raise [ConfigurationError] if explicitly specified path does not exist
105
+ def self.load_config(path=nil)
106
+ if path
107
+ # Explicitly specified path must exist
108
+ raise ConfigurationError, "Configuration file not found: #{path}" unless path.exist?
109
+
110
+ config_path = path
111
+ else
112
+ # Default path is optional
113
+ config_path = Container.resolve(:runtime).factorix_config_path
114
+ return unless config_path.exist?
115
+ end
116
+
117
+ instance_eval(config_path.read, config_path.to_s)
118
+ end
119
+
11
120
  loader = Zeitwerk::Loader.for_gem
12
121
  loader.ignore("#{__dir__}/factorix/version.rb")
13
122
  loader.ignore("#{__dir__}/factorix/errors.rb")
@@ -33,6 +142,6 @@ module Factorix
33
142
  )
34
143
  loader.setup
35
144
 
36
- Import = Dry::AutoInject(Application)
145
+ Import = Dry::AutoInject(Container)
37
146
  public_constant :Import
38
147
  end
@@ -7,12 +7,11 @@ module Factorix
7
7
  class MODDownloadAPI
8
8
  BASE_URL: String
9
9
 
10
- attr_reader downloader: Transfer::Downloader
11
10
  attr_reader logger: Dry::Logger::Dispatcher
12
11
 
13
12
  def initialize: (**untyped deps) -> void
14
13
 
15
- def download: (String download_url, Pathname output, ?expected_sha1: String?) -> void
14
+ def download: (String download_url, Pathname output, ?expected_sha1: String?, ?handler: untyped?) -> void
16
15
  end
17
16
  end
18
17
  end
@@ -0,0 +1,28 @@
1
+ # RBS type signature file
2
+
3
+ module Factorix
4
+ module Cache
5
+ class Base
6
+ attr_reader ttl: Integer?
7
+
8
+ def initialize: (?ttl: Integer?) -> void
9
+
10
+ def exist?: (String key) -> bool
11
+ def read: (String key) -> String?
12
+ def store: (String key, Pathname src) -> bool
13
+ def delete: (String key) -> bool
14
+ def clear: () -> void
15
+
16
+ def with_lock: (String key) { () -> void } -> void
17
+
18
+ def age: (String key) -> Float?
19
+ def expired?: (String key) -> bool
20
+ def size: (String key) -> Integer?
21
+
22
+ def each: () -> Enumerator[[String, Entry], void]
23
+ | () { ([String, Entry]) -> void } -> void
24
+
25
+ def backend_info: () -> Hash[Symbol, untyped]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # RBS type signature file
2
+
3
+ module Factorix
4
+ module Cache
5
+ class Entry
6
+ attr_reader size: Integer
7
+ attr_reader age: Float
8
+
9
+ def expired?: () -> bool
10
+
11
+ def initialize: (size: Integer, age: Float, expired: bool) -> void
12
+ end
13
+ end
14
+ end
@@ -4,18 +4,16 @@
4
4
 
5
5
  module Factorix
6
6
  module Cache
7
- class FileSystem
7
+ class FileSystem < Base
8
8
  LOCK_FILE_LIFETIME: Integer
9
9
 
10
- def initialize: (Pathname cache_dir, ?ttl: Integer?, ?max_file_size: Integer?, ?compression_threshold: Integer?) -> void
11
-
12
- def key_for: (String url_string) -> String
10
+ def initialize: (cache_type: Symbol, ?ttl: Integer?, ?max_file_size: Integer?, ?compression_threshold: Integer?) -> void
13
11
 
14
12
  def exist?: (String key) -> bool
15
13
 
16
- def fetch: (String key, Pathname output) -> bool
14
+ def write_to: (String key, Pathname output) -> bool
17
15
 
18
- def read: (String key, ?encoding: Encoding | String) -> String?
16
+ def read: (String key) -> String?
19
17
 
20
18
  def store: (String key, Pathname src) -> bool
21
19
 
@@ -30,6 +28,9 @@ module Factorix
30
28
  def size: (String key) -> Integer?
31
29
 
32
30
  def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
33
34
  end
34
35
  end
35
36
  end
@@ -0,0 +1,36 @@
1
+ # RBS type signature file
2
+ # NOTE: Do not include private method definitions in RBS files.
3
+ # Only public interfaces should be documented here.
4
+
5
+ module Factorix
6
+ module Cache
7
+ class Redis < Base
8
+ DEFAULT_LOCK_TIMEOUT: Integer
9
+
10
+ def initialize: (?url: String?, cache_type: String | Symbol, ?lock_timeout: Integer, ?ttl: Integer?) -> void
11
+
12
+ def exist?: (String key) -> bool
13
+
14
+ def read: (String key) -> String?
15
+
16
+ def write_to: (String key, Pathname output) -> bool
17
+
18
+ def store: (String key, Pathname src) -> bool
19
+
20
+ def delete: (String key) -> bool
21
+
22
+ def clear: () -> void
23
+
24
+ def age: (String key) -> Integer?
25
+
26
+ def expired?: (String key) -> bool
27
+
28
+ def size: (String key) -> Integer?
29
+
30
+ def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # RBS type signature file
2
+ # NOTE: Do not include private method definitions in RBS files.
3
+ # Only public interfaces should be documented here.
4
+
5
+ module Factorix
6
+ module Cache
7
+ class S3 < Base
8
+ DEFAULT_LOCK_TIMEOUT: Integer
9
+
10
+ def initialize: (bucket: String, cache_type: String | Symbol, ?region: String?, ?lock_timeout: Integer, ?ttl: Integer?) -> void
11
+
12
+ def exist?: (String key) -> bool
13
+
14
+ def read: (String key) -> String?
15
+
16
+ def write_to: (String key, Pathname output) -> bool
17
+
18
+ def store: (String key, Pathname src) -> bool
19
+
20
+ def delete: (String key) -> bool
21
+
22
+ def clear: () -> void
23
+
24
+ def age: (String key) -> Integer?
25
+
26
+ def expired?: (String key) -> bool
27
+
28
+ def size: (String key) -> Integer?
29
+
30
+ def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
34
+
35
+ def backend_info: () -> Hash[Symbol, untyped]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # RBS type signature file
2
+ # NOTE: Do not include private method definitions in RBS files.
3
+ # Only public interfaces should be documented here.
4
+
5
+ module Factorix
6
+ class Container
7
+ extend Dry::Core::Container::Mixin
8
+
9
+ def self.register: (Symbol, ?memoize: bool) { () -> untyped } -> void
10
+
11
+ def self.resolve: (Symbol) -> untyped
12
+
13
+ def self.[]: (Symbol) -> untyped
14
+ end
15
+ end
@@ -49,6 +49,9 @@ module Factorix
49
49
  class HTTPServerError < HTTPError
50
50
  end
51
51
 
52
+ class LockTimeoutError < InfrastructureError
53
+ end
54
+
52
55
  class DigestMismatchError < InfrastructureError
53
56
  end
54
57
 
@@ -18,7 +18,7 @@ module Factorix
18
18
 
19
19
  def get_mod_full: (String name) -> API::MODInfo
20
20
 
21
- def download_mod: (API::Release release, Pathname output) -> void
21
+ def download_mod: (API::Release release, Pathname output, ?handler: untyped?) -> void
22
22
 
23
23
  def upload_mod: (String mod_name, Pathname file_path, **untyped metadata) -> void
24
24