factorix 0.5.1 → 0.6.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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +1 -1
- data/lib/factorix/api/mod_download_api.rb +1 -1
- data/lib/factorix/api/mod_info.rb +2 -2
- data/lib/factorix/api/mod_management_api.rb +1 -1
- data/lib/factorix/api_credential.rb +1 -1
- data/lib/factorix/cli/commands/backup_support.rb +1 -1
- data/lib/factorix/cli/commands/base.rb +3 -3
- data/lib/factorix/cli/commands/cache/evict.rb +3 -3
- data/lib/factorix/cli/commands/cache/stat.rb +15 -15
- data/lib/factorix/cli/commands/command_wrapper.rb +5 -5
- data/lib/factorix/cli/commands/completion.rb +1 -2
- data/lib/factorix/cli/commands/confirmable.rb +1 -1
- data/lib/factorix/cli/commands/download_support.rb +2 -2
- data/lib/factorix/cli/commands/mod/check.rb +1 -1
- data/lib/factorix/cli/commands/mod/disable.rb +1 -1
- data/lib/factorix/cli/commands/mod/download.rb +5 -4
- data/lib/factorix/cli/commands/mod/edit.rb +9 -9
- data/lib/factorix/cli/commands/mod/enable.rb +1 -1
- data/lib/factorix/cli/commands/mod/image/add.rb +2 -2
- data/lib/factorix/cli/commands/mod/image/edit.rb +1 -1
- data/lib/factorix/cli/commands/mod/image/list.rb +4 -4
- data/lib/factorix/cli/commands/mod/install.rb +5 -4
- data/lib/factorix/cli/commands/mod/list.rb +7 -7
- data/lib/factorix/cli/commands/mod/search.rb +11 -9
- data/lib/factorix/cli/commands/mod/settings/dump.rb +3 -3
- data/lib/factorix/cli/commands/mod/settings/restore.rb +2 -2
- data/lib/factorix/cli/commands/mod/show.rb +20 -20
- data/lib/factorix/cli/commands/mod/sync.rb +6 -5
- data/lib/factorix/cli/commands/mod/uninstall.rb +1 -1
- data/lib/factorix/cli/commands/mod/update.rb +7 -6
- data/lib/factorix/cli/commands/mod/upload.rb +6 -6
- data/lib/factorix/cli/commands/path.rb +2 -2
- data/lib/factorix/cli/commands/version.rb +1 -1
- data/lib/factorix/{application.rb → container.rb} +8 -85
- data/lib/factorix/dependency/parser.rb +1 -1
- data/lib/factorix/http/client.rb +3 -3
- data/lib/factorix/info_json.rb +2 -2
- data/lib/factorix/mod_list.rb +2 -2
- data/lib/factorix/mod_settings.rb +2 -2
- data/lib/factorix/runtime/user_configurable.rb +9 -9
- data/lib/factorix/service_credential.rb +3 -3
- data/lib/factorix/version.rb +1 -1
- data/lib/factorix.rb +118 -1
- data/sig/factorix/container.rbs +15 -0
- data/sig/factorix.rbs +99 -0
- metadata +4 -4
- data/sig/factorix/application.rbs +0 -86
|
@@ -32,7 +32,7 @@ module Factorix
|
|
|
32
32
|
"some-mod # Show details for some-mod"
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
-
argument :mod_name,
|
|
35
|
+
argument :mod_name, required: true, desc: "MOD name to show"
|
|
36
36
|
|
|
37
37
|
# Execute the show command
|
|
38
38
|
#
|
|
@@ -75,10 +75,10 @@ module Factorix
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
private def display_header(mod_info)
|
|
78
|
-
puts TITLE_STYLE[mod_info.title]
|
|
79
|
-
puts
|
|
80
|
-
puts mod_info.summary unless mod_info.summary.empty?
|
|
81
|
-
puts
|
|
78
|
+
out.puts TITLE_STYLE[mod_info.title]
|
|
79
|
+
out.puts
|
|
80
|
+
out.puts mod_info.summary unless mod_info.summary.empty?
|
|
81
|
+
out.puts
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
private def display_basic_info(mod_info, local_status)
|
|
@@ -103,9 +103,9 @@ module Factorix
|
|
|
103
103
|
|
|
104
104
|
max_label_width = rows.map {|label, _| label.length }.max
|
|
105
105
|
rows.each do |label, value|
|
|
106
|
-
puts "#{label.ljust(max_label_width)} #{value}"
|
|
106
|
+
out.puts "#{label.ljust(max_label_width)} #{value}"
|
|
107
107
|
end
|
|
108
|
-
puts
|
|
108
|
+
out.puts
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
private def format_status(local_status)
|
|
@@ -123,18 +123,18 @@ module Factorix
|
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
private def display_links(mod_info)
|
|
126
|
-
puts HEADER_STYLE["Links"]
|
|
127
|
-
puts " MOD Portal: https://mods.factorio.com/mod/#{mod_info.name}"
|
|
126
|
+
out.puts HEADER_STYLE["Links"]
|
|
127
|
+
out.puts " MOD Portal: https://mods.factorio.com/mod/#{mod_info.name}"
|
|
128
128
|
|
|
129
129
|
if mod_info.detail
|
|
130
130
|
if mod_info.detail.source_url
|
|
131
|
-
puts " Source: #{mod_info.detail.source_url}"
|
|
131
|
+
out.puts " Source: #{mod_info.detail.source_url}"
|
|
132
132
|
end
|
|
133
133
|
if mod_info.detail.homepage
|
|
134
|
-
puts " Homepage: #{mod_info.detail.homepage}"
|
|
134
|
+
out.puts " Homepage: #{mod_info.detail.homepage}"
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
|
-
puts
|
|
137
|
+
out.puts
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
private def display_dependencies(mod_info)
|
|
@@ -149,16 +149,16 @@ module Factorix
|
|
|
149
149
|
optional = parsed.select {|d| d[:type] == :optional }
|
|
150
150
|
|
|
151
151
|
unless required.empty?
|
|
152
|
-
puts HEADER_STYLE["Dependencies"]
|
|
152
|
+
out.puts HEADER_STYLE["Dependencies"]
|
|
153
153
|
required.each {|dep| display_dependency(dep) }
|
|
154
|
-
puts
|
|
154
|
+
out.puts
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
return if optional.empty?
|
|
158
158
|
|
|
159
|
-
puts HEADER_STYLE["Optional Dependencies"]
|
|
159
|
+
out.puts HEADER_STYLE["Optional Dependencies"]
|
|
160
160
|
optional.each {|dep| display_dependency(dep) }
|
|
161
|
-
puts
|
|
161
|
+
out.puts
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
private def parse_dependency(dep_str)
|
|
@@ -178,7 +178,7 @@ module Factorix
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
private def display_dependency(dep)
|
|
181
|
-
puts " #{dep[:spec]}"
|
|
181
|
+
out.puts " #{dep[:spec]}"
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
private def display_incompatibilities(mod_info)
|
|
@@ -191,9 +191,9 @@ module Factorix
|
|
|
191
191
|
|
|
192
192
|
return if incompatible.empty?
|
|
193
193
|
|
|
194
|
-
puts HEADER_STYLE["Incompatibilities"]
|
|
195
|
-
incompatible.each {|dep| puts " #{INCOMPATIBLE_MOD_STYLE[dep[:spec]]}" }
|
|
196
|
-
puts
|
|
194
|
+
out.puts HEADER_STYLE["Incompatibilities"]
|
|
195
|
+
incompatible.each {|dep| out.puts " #{INCOMPATIBLE_MOD_STYLE[dep[:spec]]}" }
|
|
196
|
+
out.puts
|
|
197
197
|
end
|
|
198
198
|
end
|
|
199
199
|
end
|
|
@@ -31,15 +31,16 @@ module Factorix
|
|
|
31
31
|
"-j 8 save.zip # Use 8 parallel downloads"
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
-
argument :save_file,
|
|
35
|
-
option :jobs,
|
|
34
|
+
argument :save_file, required: true, desc: "Path to Factorio save file (.zip)"
|
|
35
|
+
option :jobs, aliases: ["-j"], default: "4", desc: "Number of parallel downloads"
|
|
36
36
|
|
|
37
37
|
# Execute the sync command
|
|
38
38
|
#
|
|
39
39
|
# @param save_file [String] Path to save file
|
|
40
40
|
# @param jobs [Integer] Number of parallel downloads
|
|
41
41
|
# @return [void]
|
|
42
|
-
def call(save_file:, jobs: 4, **)
|
|
42
|
+
def call(save_file:, jobs: "4", **)
|
|
43
|
+
jobs = Integer(jobs)
|
|
43
44
|
# Load save file
|
|
44
45
|
say "Loading save file: #{save_file}", prefix: :info
|
|
45
46
|
save_data = SaveFile.load(Pathname(save_file))
|
|
@@ -47,7 +48,7 @@ module Factorix
|
|
|
47
48
|
|
|
48
49
|
# Load current state
|
|
49
50
|
mod_list = MODList.load
|
|
50
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
51
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
51
52
|
handler = Progress::ScanHandler.new(presenter)
|
|
52
53
|
installed_mods = InstalledMOD.all(handler:)
|
|
53
54
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -109,7 +110,7 @@ module Factorix
|
|
|
109
110
|
# @return [Array<Hash>] Installation targets with MOD info and releases
|
|
110
111
|
private def plan_installation(mods_to_install, graph, jobs)
|
|
111
112
|
# Create progress presenter for info fetching
|
|
112
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output:
|
|
113
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output: err)
|
|
113
114
|
|
|
114
115
|
# Fetch info for MODs to install
|
|
115
116
|
target_infos = fetch_target_mod_info(mods_to_install, jobs, presenter)
|
|
@@ -45,7 +45,7 @@ module Factorix
|
|
|
45
45
|
|
|
46
46
|
# Load current state (without validation to allow fixing issues)
|
|
47
47
|
mod_list = MODList.load
|
|
48
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
48
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
49
49
|
handler = Progress::ScanHandler.new(presenter)
|
|
50
50
|
installed_mods = InstalledMOD.all(handler:)
|
|
51
51
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -32,15 +32,16 @@ module Factorix
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
argument :mod_names, type: :array, required: false, desc: "MOD names to update (all if not specified)"
|
|
35
|
-
option :jobs,
|
|
35
|
+
option :jobs, aliases: ["-j"], default: "4", desc: "Number of parallel downloads"
|
|
36
36
|
|
|
37
37
|
# Execute the update command
|
|
38
38
|
#
|
|
39
39
|
# @param mod_names [Array<String>] MOD names to update
|
|
40
40
|
# @param jobs [Integer] Number of parallel downloads
|
|
41
41
|
# @return [void]
|
|
42
|
-
def call(mod_names: [], jobs: 4, **)
|
|
43
|
-
|
|
42
|
+
def call(mod_names: [], jobs: "4", **)
|
|
43
|
+
jobs = Integer(jobs)
|
|
44
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
44
45
|
handler = Progress::ScanHandler.new(presenter)
|
|
45
46
|
installed_mods = InstalledMOD.all(handler:)
|
|
46
47
|
mod_list = MODList.load
|
|
@@ -98,7 +99,7 @@ module Factorix
|
|
|
98
99
|
# @param jobs [Integer] Number of parallel jobs
|
|
99
100
|
# @return [Array<Hash>] Update targets with current and latest versions
|
|
100
101
|
private def find_update_targets(target_mods, installed_mods, jobs)
|
|
101
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Checking for updates", output:
|
|
102
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Checking for updates", output: err)
|
|
102
103
|
presenter.start(total: target_mods.size)
|
|
103
104
|
|
|
104
105
|
pool = Concurrent::FixedThreadPool.new(jobs)
|
|
@@ -189,13 +190,13 @@ module Factorix
|
|
|
189
190
|
# @param jobs [Integer] Number of parallel jobs
|
|
190
191
|
# @return [void]
|
|
191
192
|
private def download_mods(targets, jobs)
|
|
192
|
-
multi_presenter = Progress::MultiPresenter.new(title: "\u{1F4E5}\u{FE0E} Downloads")
|
|
193
|
+
multi_presenter = Progress::MultiPresenter.new(title: "\u{1F4E5}\u{FE0E} Downloads", output: err)
|
|
193
194
|
|
|
194
195
|
pool = Concurrent::FixedThreadPool.new(jobs)
|
|
195
196
|
|
|
196
197
|
futures = targets.map {|target|
|
|
197
198
|
Concurrent::Future.execute(executor: pool) do
|
|
198
|
-
thread_portal =
|
|
199
|
+
thread_portal = Container[:portal]
|
|
199
200
|
thread_downloader = thread_portal.mod_download_api.downloader
|
|
200
201
|
|
|
201
202
|
presenter = multi_presenter.register(
|
|
@@ -18,11 +18,11 @@ module Factorix
|
|
|
18
18
|
"my-mod_1.0.0.zip --category automation # Upload with category"
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
argument :file,
|
|
22
|
-
option :description,
|
|
23
|
-
option :category,
|
|
24
|
-
option :license,
|
|
25
|
-
option :source_url,
|
|
21
|
+
argument :file, required: true, desc: "Path to MOD zip file"
|
|
22
|
+
option :description, desc: "Markdown description"
|
|
23
|
+
option :category, desc: "MOD category"
|
|
24
|
+
option :license, desc: "License identifier"
|
|
25
|
+
option :source_url, desc: "Repository URL"
|
|
26
26
|
|
|
27
27
|
# Execute the upload command
|
|
28
28
|
#
|
|
@@ -44,7 +44,7 @@ module Factorix
|
|
|
44
44
|
mod_name = extract_mod_name(file_path)
|
|
45
45
|
metadata = build_metadata(description:, category:, license:, source_url:)
|
|
46
46
|
|
|
47
|
-
presenter = Progress::Presenter.new(title: "\u{1F4E4} Uploading #{file_path.basename}", output:
|
|
47
|
+
presenter = Progress::Presenter.new(title: "\u{1F4E4} Uploading #{file_path.basename}", output: err)
|
|
48
48
|
|
|
49
49
|
uploader = portal.mod_management_api.uploader
|
|
50
50
|
handler = Progress::UploadHandler.new(presenter)
|
|
@@ -61,7 +61,7 @@ module Factorix
|
|
|
61
61
|
result = PATH_TYPES.transform_values {|method_name| runtime.public_send(method_name).to_s }
|
|
62
62
|
|
|
63
63
|
if json
|
|
64
|
-
puts JSON.pretty_generate(result)
|
|
64
|
+
out.puts JSON.pretty_generate(result)
|
|
65
65
|
else
|
|
66
66
|
output_table(result)
|
|
67
67
|
end
|
|
@@ -70,7 +70,7 @@ module Factorix
|
|
|
70
70
|
private def output_table(result)
|
|
71
71
|
key_width = result.keys.map(&:length).max
|
|
72
72
|
result.each do |key, value|
|
|
73
|
-
puts "%-#{key_width}s %s" % [key, value]
|
|
73
|
+
out.puts "%-#{key_width}s %s" % [key, value]
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "dry/configurable"
|
|
4
3
|
require "dry/core"
|
|
5
4
|
require "dry/logger"
|
|
6
5
|
|
|
7
6
|
module Factorix
|
|
8
|
-
#
|
|
7
|
+
# DI container for dependency injection
|
|
9
8
|
#
|
|
10
|
-
# Provides dependency injection container
|
|
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
|
|
9
|
+
# Provides dependency injection container using dry-core's Container.
|
|
18
10
|
#
|
|
19
11
|
# @example Resolve dependencies
|
|
20
|
-
# runtime = Factorix::
|
|
21
|
-
class
|
|
12
|
+
# runtime = Factorix::Container[:runtime]
|
|
13
|
+
class Container
|
|
22
14
|
extend Dry::Core::Container::Mixin
|
|
23
|
-
extend Dry::Configurable
|
|
24
15
|
|
|
25
16
|
# Some items are registered with memoize: false to support independent event handlers
|
|
26
17
|
# for each parallel download task (e.g., progress tracking).
|
|
@@ -46,7 +37,7 @@ module Factorix
|
|
|
46
37
|
# Dispatcher level set to DEBUG to allow all messages through
|
|
47
38
|
# Backend controls filtering based on --log-level option
|
|
48
39
|
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")
|
|
40
|
+
dispatcher.add_backend(level: Factorix.config.log_level, stream: log_path.to_s, template: "[%<time>s] %<severity>s: %<message>s %<payload>s")
|
|
50
41
|
end
|
|
51
42
|
end
|
|
52
43
|
|
|
@@ -57,19 +48,19 @@ module Factorix
|
|
|
57
48
|
|
|
58
49
|
# Register download cache
|
|
59
50
|
register(:download_cache, memoize: true) do
|
|
60
|
-
c = config.cache.download
|
|
51
|
+
c = Factorix.config.cache.download
|
|
61
52
|
Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
|
|
62
53
|
end
|
|
63
54
|
|
|
64
55
|
# Register API cache (with compression for JSON responses)
|
|
65
56
|
register(:api_cache, memoize: true) do
|
|
66
|
-
c = config.cache.api
|
|
57
|
+
c = Factorix.config.cache.api
|
|
67
58
|
Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
|
|
68
59
|
end
|
|
69
60
|
|
|
70
61
|
# Register info.json cache (for MOD metadata from ZIP files)
|
|
71
62
|
register(:info_json_cache, memoize: true) do
|
|
72
|
-
c = config.cache.info_json
|
|
63
|
+
c = Factorix.config.cache.info_json
|
|
73
64
|
Cache::FileSystem.new(c.dir, **c.to_h.except(:dir))
|
|
74
65
|
end
|
|
75
66
|
|
|
@@ -146,73 +137,5 @@ module Factorix
|
|
|
146
137
|
register(:portal, memoize: false) do
|
|
147
138
|
Portal.new
|
|
148
139
|
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
140
|
end
|
|
218
141
|
end
|
|
@@ -128,7 +128,7 @@ module Factorix
|
|
|
128
128
|
MODVersionRequirement[operator:, version:]
|
|
129
129
|
rescue VersionParseError => e
|
|
130
130
|
# Skip version requirements with out-of-range version components
|
|
131
|
-
|
|
131
|
+
Container[:logger].warn("Skipping version requirement '#{version_string}': #{e.message}")
|
|
132
132
|
nil
|
|
133
133
|
end
|
|
134
134
|
|
data/lib/factorix/http/client.rb
CHANGED
|
@@ -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 =
|
|
166
|
-
http.read_timeout =
|
|
167
|
-
http.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
|
|
data/lib/factorix/info_json.rb
CHANGED
|
@@ -47,8 +47,8 @@ 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 =
|
|
51
|
-
logger =
|
|
50
|
+
cache = Container.resolve(:info_json_cache)
|
|
51
|
+
logger = Container.resolve(:logger)
|
|
52
52
|
cache_key = cache.key_for(zip_path.to_s)
|
|
53
53
|
|
|
54
54
|
if (cached_json = cache.read(cache_key, encoding: Encoding::UTF_8))
|
data/lib/factorix/mod_list.rb
CHANGED
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
|
@@ -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
|
|
11
|
+
# Configuration is done via Factorix.config:
|
|
12
12
|
#
|
|
13
13
|
# @example Configure paths in config file
|
|
14
|
-
# Factorix
|
|
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 =
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
yield.tap {|path|
|
|
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
|
-
|
|
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 #{
|
|
60
|
+
Please configure it in #{Container[:runtime].factorix_config_path}:
|
|
61
61
|
|
|
62
|
-
Factorix
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|