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
data/sig/factorix.rbs CHANGED
@@ -6,4 +6,103 @@ module Factorix
6
6
  VERSION: String
7
7
 
8
8
  Import: Dry::AutoInject::_Builder
9
+
10
+ extend Dry::Configurable
11
+
12
+ def self.config: () -> Config
13
+
14
+ def self.configure: () { (Config) -> void } -> void
15
+
16
+ def self.load_config: (?Pathname? path) -> void
17
+
18
+ class Config
19
+ def log_level: () -> Symbol
20
+
21
+ def log_level=: (Symbol) -> void
22
+
23
+ def credential: () -> CredentialConfig
24
+
25
+ def runtime: () -> RuntimeConfig
26
+
27
+ def http: () -> HttpConfig
28
+
29
+ def cache: () -> CacheConfig
30
+ end
31
+
32
+ class CredentialConfig
33
+ def source: () -> Symbol
34
+
35
+ def source=: (Symbol) -> void
36
+ end
37
+
38
+ class RuntimeConfig
39
+ def executable_path: () -> Pathname?
40
+
41
+ def executable_path=: (Pathname | String | nil) -> void
42
+
43
+ def user_dir: () -> Pathname?
44
+
45
+ def user_dir=: (Pathname | String | nil) -> void
46
+
47
+ def data_dir: () -> Pathname?
48
+
49
+ def data_dir=: (Pathname | String | nil) -> void
50
+ end
51
+
52
+ class HttpConfig
53
+ def connect_timeout: () -> Integer
54
+
55
+ def connect_timeout=: (Integer) -> void
56
+
57
+ def read_timeout: () -> Integer
58
+
59
+ def read_timeout=: (Integer) -> void
60
+
61
+ def write_timeout: () -> Integer
62
+
63
+ def write_timeout=: (Integer) -> void
64
+ end
65
+
66
+ class CacheConfig
67
+ def download: () -> CacheInstanceConfig
68
+
69
+ def api: () -> CacheInstanceConfig
70
+
71
+ def info_json: () -> CacheInstanceConfig
72
+
73
+ def values: () -> Hash[Symbol, CacheInstanceConfig]
74
+ end
75
+
76
+ class CacheInstanceConfig
77
+ def dir: () -> Pathname
78
+
79
+ def dir=: (Pathname | String) -> void
80
+
81
+ def ttl: () -> Integer?
82
+
83
+ def ttl=: (Integer?) -> void
84
+
85
+ def max_file_size: () -> Integer?
86
+
87
+ def max_file_size=: (Integer?) -> void
88
+
89
+ def compression_threshold: () -> Integer?
90
+
91
+ def compression_threshold=: (Integer?) -> void
92
+
93
+ def to_h: () -> Hash[Symbol, untyped]
94
+ end
95
+
96
+ # @deprecated Use Container for DI and Factorix for configuration
97
+ class Application
98
+ def self.[]: (Symbol) -> untyped
99
+
100
+ def self.resolve: (Symbol) -> untyped
101
+
102
+ def self.register: (Symbol, ?memoize: bool) { () -> untyped } -> void
103
+
104
+ def self.config: () -> Config
105
+
106
+ def self.configure: () { (Config) -> void } -> void
107
+ end
9
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factorix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OZAWA Sakuro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-13 00:00:00.000000000 Z
11
+ date: 2026-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dry-inflector
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: dry-logger
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -221,8 +235,11 @@ files:
221
235
  - lib/factorix/api/release.rb
222
236
  - lib/factorix/api/tag.rb
223
237
  - lib/factorix/api_credential.rb
224
- - lib/factorix/application.rb
238
+ - lib/factorix/cache/base.rb
239
+ - lib/factorix/cache/entry.rb
225
240
  - lib/factorix/cache/file_system.rb
241
+ - lib/factorix/cache/redis.rb
242
+ - lib/factorix/cache/s3.rb
226
243
  - lib/factorix/cli.rb
227
244
  - lib/factorix/cli/commands/backup_support.rb
228
245
  - lib/factorix/cli/commands/base.rb
@@ -253,8 +270,10 @@ files:
253
270
  - lib/factorix/cli/commands/mod/update.rb
254
271
  - lib/factorix/cli/commands/mod/upload.rb
255
272
  - lib/factorix/cli/commands/path.rb
273
+ - lib/factorix/cli/commands/portal_support.rb
256
274
  - lib/factorix/cli/commands/requires_game_stopped.rb
257
275
  - lib/factorix/cli/commands/version.rb
276
+ - lib/factorix/container.rb
258
277
  - lib/factorix/dependency/edge.rb
259
278
  - lib/factorix/dependency/entry.rb
260
279
  - lib/factorix/dependency/graph.rb
@@ -322,8 +341,11 @@ files:
322
341
  - sig/factorix/api/release.rbs
323
342
  - sig/factorix/api/tag.rbs
324
343
  - sig/factorix/api_credential.rbs
325
- - sig/factorix/application.rbs
344
+ - sig/factorix/cache/base.rbs
345
+ - sig/factorix/cache/entry.rbs
326
346
  - sig/factorix/cache/file_system.rbs
347
+ - sig/factorix/cache/redis.rbs
348
+ - sig/factorix/cache/s3.rbs
327
349
  - sig/factorix/cli.rbs
328
350
  - sig/factorix/cli/commands/base.rbs
329
351
  - sig/factorix/cli/commands/cache/evict.rbs
@@ -353,6 +375,7 @@ files:
353
375
  - sig/factorix/cli/commands/path.rbs
354
376
  - sig/factorix/cli/commands/requires_game_stopped.rbs
355
377
  - sig/factorix/cli/commands/version.rbs
378
+ - sig/factorix/container.rbs
356
379
  - sig/factorix/dependency/edge.rbs
357
380
  - sig/factorix/dependency/entry.rbs
358
381
  - sig/factorix/dependency/graph.rbs
@@ -1,218 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/configurable"
4
- require "dry/core"
5
- require "dry/logger"
6
-
7
- module Factorix
8
- # Application container and configuration
9
- #
10
- # Provides dependency injection container and configuration management
11
- # using dry-core's Container and dry-configurable.
12
- #
13
- # @example Configure the application
14
- # Factorix::Application.configure do |config|
15
- # config.log_level = :debug
16
- # config.http.connect_timeout = 10
17
- # end
18
- #
19
- # @example Resolve dependencies
20
- # runtime = Factorix::Application[:runtime]
21
- class Application
22
- extend Dry::Core::Container::Mixin
23
- extend Dry::Configurable
24
-
25
- # Some items are registered with memoize: false to support independent event handlers
26
- # for each parallel download task (e.g., progress tracking).
27
- # Items registered with memoize: false:
28
- # - :downloader (event handlers for progress tracking)
29
- # - :mod_download_api (contains :downloader)
30
- # - :portal (contains :mod_download_api)
31
-
32
- # Register runtime detector
33
- register(:runtime, memoize: true) do
34
- Runtime.detect
35
- end
36
-
37
- # Register logger
38
- register(:logger, memoize: true) do
39
- runtime = resolve(:runtime)
40
- log_path = runtime.factorix_log_path
41
-
42
- # Ensure log directory exists
43
- log_path.dirname.mkpath unless log_path.dirname.exist?
44
-
45
- # Create logger with file backend
46
- # Dispatcher level set to DEBUG to allow all messages through
47
- # Backend controls filtering based on --log-level option
48
- Dry.Logger(:factorix, level: :debug) do |dispatcher|
49
- dispatcher.add_backend(level: config.log_level, stream: log_path.to_s, template: "[%<time>s] %<severity>s: %<message>s %<payload>s")
50
- end
51
- end
52
-
53
- # Register retry strategy for network operations
54
- register(:retry_strategy, memoize: true) do
55
- HTTP::RetryStrategy.new
56
- end
57
-
58
- # Register download cache
59
- register(:download_cache, memoize: true) do
60
- c = config.cache.download
61
- Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
62
- end
63
-
64
- # Register API cache (with compression for JSON responses)
65
- register(:api_cache, memoize: true) do
66
- c = config.cache.api
67
- Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
68
- end
69
-
70
- # Register info.json cache (for MOD metadata from ZIP files)
71
- register(:info_json_cache, memoize: true) do
72
- c = config.cache.info_json
73
- Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
74
- end
75
-
76
- # Register base HTTP client
77
- register(:http_client, memoize: true) do
78
- HTTP::Client.new(masked_params: %w[username token secure])
79
- end
80
-
81
- # Register decorated HTTP client for downloads (with retry only)
82
- # Note: Caching is handled by Downloader, not at HTTP client level
83
- register(:download_http_client, memoize: true) do
84
- client = resolve(:http_client)
85
- retry_strategy = resolve(:retry_strategy)
86
-
87
- # Decorate: Client -> Retry (no cache, handled by Downloader)
88
- HTTP::RetryDecorator.new(client:, retry_strategy:)
89
- end
90
-
91
- # Register decorated HTTP client for API calls (with retry + cache)
92
- register(:api_http_client, memoize: true) do
93
- client = resolve(:http_client)
94
- api_cache = resolve(:api_cache)
95
- retry_strategy = resolve(:retry_strategy)
96
-
97
- # Decorate: Client -> Cache -> Retry
98
- cached = HTTP::CacheDecorator.new(client:, cache: api_cache)
99
- HTTP::RetryDecorator.new(client: cached, retry_strategy:)
100
- end
101
-
102
- # Register decorated HTTP client for uploads (with retry only, no cache)
103
- register(:upload_http_client, memoize: true) do
104
- client = resolve(:http_client)
105
- retry_strategy = resolve(:retry_strategy)
106
-
107
- # Decorate: Client -> Retry (no cache for uploads)
108
- HTTP::RetryDecorator.new(client:, retry_strategy:)
109
- end
110
-
111
- # Register downloader
112
- register(:downloader, memoize: false) do
113
- Transfer::Downloader.new
114
- end
115
-
116
- # Register uploader
117
- register(:uploader, memoize: true) do
118
- Transfer::Uploader.new
119
- end
120
-
121
- # Register service credential
122
- register(:service_credential, memoize: true) { ServiceCredential.load }
123
-
124
- # Register MOD Portal API client
125
- register(:mod_portal_api, memoize: true) do
126
- API::MODPortalAPI.new
127
- end
128
-
129
- # Register MOD Download API client
130
- register(:mod_download_api, memoize: false) do
131
- API::MODDownloadAPI.new
132
- end
133
-
134
- # Register API credential (for MOD upload/management)
135
- register(:api_credential, memoize: true) { APICredential.load }
136
-
137
- # Register MOD Management API client
138
- register(:mod_management_api, memoize: true) do
139
- api = API::MODManagementAPI.new
140
- # Subscribe mod_portal_api to invalidate cache when MOD is changed on portal
141
- api.subscribe(resolve(:mod_portal_api))
142
- api
143
- end
144
-
145
- # Register portal (high-level API wrapper)
146
- register(:portal, memoize: false) do
147
- Portal.new
148
- end
149
-
150
- # Log level (:debug, :info, :warn, :error, :fatal)
151
- setting :log_level, default: :info
152
-
153
- # Runtime settings (optional overrides for auto-detection)
154
- setting :runtime do
155
- setting :executable_path, constructor: ->(v) { v ? Pathname(v) : nil }
156
- setting :user_dir, constructor: ->(v) { v ? Pathname(v) : nil }
157
- setting :data_dir, constructor: ->(v) { v ? Pathname(v) : nil }
158
- end
159
-
160
- # HTTP timeout settings
161
- setting :http do
162
- setting :connect_timeout, default: 5
163
- setting :read_timeout, default: 30
164
- setting :write_timeout, default: 30
165
- end
166
-
167
- # Cache settings
168
- setting :cache do
169
- # Download cache settings (for MOD files)
170
- setting :download do
171
- setting :dir, constructor: ->(value) { Pathname(value) }
172
- setting :ttl, default: nil # nil for unlimited (MOD files are immutable)
173
- setting :max_file_size, default: nil # nil for unlimited
174
- setting :compression_threshold, default: nil # nil for no compression (binary files)
175
- end
176
-
177
- # API cache settings (for API responses)
178
- setting :api do
179
- setting :dir, constructor: ->(value) { Pathname(value) }
180
- setting :ttl, default: 3600 # 1 hour (API responses may change)
181
- setting :max_file_size, default: 10 * 1024 * 1024 # 10MiB (JSON responses)
182
- setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
183
- end
184
-
185
- # info.json cache settings (for MOD metadata from ZIP files)
186
- setting :info_json do
187
- setting :dir, constructor: ->(value) { Pathname(value) }
188
- setting :ttl, default: nil # nil for unlimited (info.json is immutable within a MOD ZIP)
189
- setting :max_file_size, default: nil # nil for unlimited (info.json is small)
190
- setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
191
- end
192
- end
193
-
194
- # Load configuration from file
195
- #
196
- # @param path [Pathname, String, nil] configuration file path
197
- # @return [void]
198
- # @raise [ConfigurationError] if explicitly specified path does not exist
199
- def self.load_config(path=nil)
200
- if path
201
- # Explicitly specified path must exist
202
- config_path = Pathname(path)
203
- raise ConfigurationError, "Configuration file not found: #{config_path}" unless config_path.exist?
204
- else
205
- # Default path is optional
206
- config_path = resolve(:runtime).factorix_config_path
207
- return unless config_path.exist?
208
- end
209
-
210
- instance_eval(config_path.read, config_path.to_s)
211
- end
212
-
213
- runtime = resolve(:runtime)
214
- config.cache.download.dir = runtime.factorix_cache_dir / "download"
215
- config.cache.api.dir = runtime.factorix_cache_dir / "api"
216
- config.cache.info_json.dir = runtime.factorix_cache_dir / "info_json"
217
- end
218
- end
@@ -1,86 +0,0 @@
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 Application
7
- extend Dry::Core::Container::Mixin
8
- extend Dry::Configurable
9
-
10
- def self.register: (Symbol, ?memoize: bool) { () -> untyped } -> void
11
-
12
- def self.resolve: (Symbol) -> untyped
13
-
14
- def self.[]: (Symbol) -> untyped
15
-
16
- def self.config: () -> Config
17
-
18
- def self.configure: () { (Config) -> void } -> void
19
-
20
- def self.load_config: (?String? path) -> void
21
-
22
- class Config
23
- def log_level: () -> Symbol
24
-
25
- def log_level=: (Symbol) -> void
26
-
27
- def credential: () -> CredentialConfig
28
-
29
- def runtime: () -> RuntimeConfig
30
-
31
- def http: () -> HttpConfig
32
-
33
- def cache: () -> CacheConfig
34
- end
35
-
36
- class CredentialConfig
37
- def source: () -> Symbol
38
-
39
- def source=: (Symbol) -> void
40
- end
41
-
42
- class RuntimeConfig
43
- def executable_path: () -> Pathname?
44
-
45
- def executable_path=: (Pathname | String | nil) -> void
46
-
47
- def user_dir: () -> Pathname?
48
-
49
- def user_dir=: (Pathname | String | nil) -> void
50
- end
51
-
52
- class HttpConfig
53
- def connect_timeout: () -> Integer
54
-
55
- def connect_timeout=: (Integer) -> void
56
-
57
- def read_timeout: () -> Integer
58
-
59
- def read_timeout=: (Integer) -> void
60
-
61
- def write_timeout: () -> Integer
62
-
63
- def write_timeout=: (Integer) -> void
64
- end
65
-
66
- class CacheConfig
67
- def download: () -> CacheInstanceConfig
68
-
69
- def api: () -> CacheInstanceConfig
70
- end
71
-
72
- class CacheInstanceConfig
73
- def dir: () -> Pathname
74
-
75
- def dir=: (Pathname | String) -> void
76
-
77
- def ttl: () -> Integer?
78
-
79
- def ttl=: (Integer?) -> void
80
-
81
- def max_file_size: () -> Integer?
82
-
83
- def max_file_size=: (Integer?) -> void
84
- end
85
- end
86
- end