nauvisian 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +29 -0
- data/.rubocop_todo.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +141 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +15 -0
- data/exe/nvsn +7 -0
- data/lib/nauvisian/api.rb +68 -0
- data/lib/nauvisian/cache/file_system.rb +55 -0
- data/lib/nauvisian/cache.rb +3 -0
- data/lib/nauvisian/cli/commands/mod/disable.rb +32 -0
- data/lib/nauvisian/cli/commands/mod/download.rb +34 -0
- data/lib/nauvisian/cli/commands/mod/enable.rb +32 -0
- data/lib/nauvisian/cli/commands/mod/info.rb +39 -0
- data/lib/nauvisian/cli/commands/mod/installed.rb +33 -0
- data/lib/nauvisian/cli/commands/mod/latest.rb +30 -0
- data/lib/nauvisian/cli/commands/mod/settings/dump.rb +30 -0
- data/lib/nauvisian/cli/commands/mod/versions.rb +31 -0
- data/lib/nauvisian/cli/commands/save/mod/list.rb +35 -0
- data/lib/nauvisian/cli/commands/save/mod/sync.rb +99 -0
- data/lib/nauvisian/cli/download_helper.rb +35 -0
- data/lib/nauvisian/cli/lister.rb +66 -0
- data/lib/nauvisian/cli/message_helper.rb +18 -0
- data/lib/nauvisian/cli.rb +36 -0
- data/lib/nauvisian/credential.rb +23 -0
- data/lib/nauvisian/deserializer.rb +96 -0
- data/lib/nauvisian/downloader.rb +57 -0
- data/lib/nauvisian/error.rb +16 -0
- data/lib/nauvisian/mod/detail.rb +15 -0
- data/lib/nauvisian/mod/release.rb +13 -0
- data/lib/nauvisian/mod.rb +20 -0
- data/lib/nauvisian/mod_list.rb +71 -0
- data/lib/nauvisian/mod_settings.rb +46 -0
- data/lib/nauvisian/platform.rb +78 -0
- data/lib/nauvisian/progress/bar.rb +24 -0
- data/lib/nauvisian/progress/null.rb +19 -0
- data/lib/nauvisian/progress.rb +4 -0
- data/lib/nauvisian/save.rb +89 -0
- data/lib/nauvisian/serializer.rb +111 -0
- data/lib/nauvisian/uri/s3.rb +22 -0
- data/lib/nauvisian/version.rb +6 -0
- data/lib/nauvisian/version24.rb +33 -0
- data/lib/nauvisian/version64.rb +33 -0
- data/lib/nauvisian.rb +33 -0
- data/nauvisian.gemspec +45 -0
- metadata +184 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../message_helper"
|
4
|
+
|
5
|
+
module Nauvisian
|
6
|
+
module CLI
|
7
|
+
module Commands
|
8
|
+
module Mod
|
9
|
+
module Settings
|
10
|
+
class Dump < Dry::CLI::Command
|
11
|
+
include MessageHelper
|
12
|
+
|
13
|
+
desc "Dump MOD settings"
|
14
|
+
option :mod_directory, desc: "Directory where MODs are installed", required: false, default: Nauvisian.platform.mod_directory.to_s
|
15
|
+
|
16
|
+
def call(**options)
|
17
|
+
mod_directory = Pathname(options[:mod_directory])
|
18
|
+
mod_settings_path = mod_directory / "mod-settings.dat"
|
19
|
+
settings = Nauvisian::ModSettings.load(mod_settings_path)
|
20
|
+
puts settings.to_json
|
21
|
+
rescue => e
|
22
|
+
message(e)
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../message_helper"
|
4
|
+
|
5
|
+
module Nauvisian
|
6
|
+
module CLI
|
7
|
+
module Commands
|
8
|
+
module Mod
|
9
|
+
class Versions < Dry::CLI::Command
|
10
|
+
include MessageHelper
|
11
|
+
|
12
|
+
desc "List available versions of MOD"
|
13
|
+
argument :mod, desc: "Target MOD", required: true
|
14
|
+
|
15
|
+
def call(mod:, **)
|
16
|
+
api = Nauvisian::API.new
|
17
|
+
mod = Nauvisian::Mod[name: mod]
|
18
|
+
releases = api.releases(mod).sort_by(&:released_at)
|
19
|
+
|
20
|
+
releases.each do |release|
|
21
|
+
puts release.version
|
22
|
+
end
|
23
|
+
rescue => e
|
24
|
+
message(e)
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../lister"
|
4
|
+
require_relative "../../../message_helper"
|
5
|
+
|
6
|
+
module Nauvisian
|
7
|
+
module CLI
|
8
|
+
module Commands
|
9
|
+
module Save
|
10
|
+
module Mod
|
11
|
+
class List < Dry::CLI::Command
|
12
|
+
include MessageHelper
|
13
|
+
|
14
|
+
desc "List MODs used in the given save"
|
15
|
+
argument :file, desc: "Save file of a Factorio game", required: true
|
16
|
+
option :format, default: "plain", values: Nauvisian::CLI::Lister.all, desc: "Output format"
|
17
|
+
|
18
|
+
def call(file:, **options)
|
19
|
+
file_path = Pathname(file)
|
20
|
+
save = Nauvisian::Save.load(file_path)
|
21
|
+
mods = save.mods.sort
|
22
|
+
rows = mods.map {|mod, version| {"Name" => mod.name, "Version" => version} }
|
23
|
+
|
24
|
+
lister = Nauvisian::CLI::Lister.for(options[:format].to_sym).new(%w(Name Version))
|
25
|
+
lister.list(rows)
|
26
|
+
rescue => e
|
27
|
+
message(e)
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../download_helper"
|
4
|
+
require_relative "../../../message_helper"
|
5
|
+
|
6
|
+
module Nauvisian
|
7
|
+
module CLI
|
8
|
+
module Commands
|
9
|
+
module Save
|
10
|
+
module Mod
|
11
|
+
class Sync < Dry::CLI::Command
|
12
|
+
include DownloadHelper
|
13
|
+
include MessageHelper
|
14
|
+
|
15
|
+
desc "Synchronize MODs and settings with the given save"
|
16
|
+
argument :save_file, desc: "Save file of a Factorio game", required: true
|
17
|
+
|
18
|
+
option :mod_directory, desc: "Directory where MODs are installed", required: false, default: Nauvisian.platform.mod_directory.to_s
|
19
|
+
option :exact, desc: "Use exact version", type: :boolean, default: false
|
20
|
+
option :verbose, desc: "Print extra information", type: :boolean, default: false
|
21
|
+
|
22
|
+
def call(save_file:, **options)
|
23
|
+
save_file_path = Pathname(save_file)
|
24
|
+
save = Nauvisian::Save.load(save_file_path)
|
25
|
+
mods_in_save = save.mods.sort # [[mod, version]]
|
26
|
+
|
27
|
+
options[:mod_directory] = Pathname(options[:mod_directory])
|
28
|
+
existing_mods = ExistingMods.new(**options)
|
29
|
+
|
30
|
+
downloader = Nauvisian::Downloader.new(credential: find_credential, progress: options[:verbose] ? Nauvisian::Progress::Bar : Nauvisian::Progress::Null)
|
31
|
+
|
32
|
+
mods_in_save.each do |mod, version|
|
33
|
+
next if mod.base?
|
34
|
+
|
35
|
+
release = existing_mods.release_to_download(mod, version)
|
36
|
+
next unless release
|
37
|
+
|
38
|
+
downloader.download(release, options[:mod_directory] / release.file_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
list = Nauvisian::ModList.new(mods_in_save.map {|mod, _version| [mod, true] })
|
42
|
+
list.save(options[:mod_directory] / "mod-list.json")
|
43
|
+
|
44
|
+
settings = Nauvisian::ModSettings.load(options[:mod_directory] / "mod-settings.dat")
|
45
|
+
settings["startup"] = save.startup_settings
|
46
|
+
settings.save(options[:mod_directory] / "mod-settings.dat")
|
47
|
+
rescue => e
|
48
|
+
message(e)
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
class ExistingMods
|
53
|
+
include DownloadHelper
|
54
|
+
include MessageHelper
|
55
|
+
|
56
|
+
def initialize(mod_directory:, exact:, verbose:)
|
57
|
+
@exact = exact
|
58
|
+
@verbose = verbose
|
59
|
+
|
60
|
+
zips = mod_directory.glob("*.zip")
|
61
|
+
directories = mod_directory.entries.select(&:directory?)
|
62
|
+
# [[mod, [version...]]
|
63
|
+
@mods = [*zips, *directories].filter_map {|path|
|
64
|
+
/(?<name>.*)_(?<version>\d+\.\d+\.\d+)(?:\.zip|$)\z/ =~ path.basename.to_s && [Nauvisian::Mod[name:], Nauvisian::Version24[version]]
|
65
|
+
}.group_by(&:first).transform_values {|v| v.map(&:last) }.to_a
|
66
|
+
end
|
67
|
+
|
68
|
+
def exact? = @exact
|
69
|
+
def verbose? = @verbose
|
70
|
+
|
71
|
+
def release_to_download(mod, version)
|
72
|
+
message "⚙ Checking #{mod.name} #{version} ... "
|
73
|
+
|
74
|
+
# TODO: Take version requirement into consideration
|
75
|
+
case @mods
|
76
|
+
in [*, [^mod, [*, ^version, *]], *]
|
77
|
+
message "✓ Exact version (#{version}) exists, nothing to do"
|
78
|
+
in [*, [^mod, [*versions]], *]
|
79
|
+
if exact?
|
80
|
+
message "📥 some versions are installed but exact version (#{version}) is requested"
|
81
|
+
find_release(mod, version:)
|
82
|
+
elsif versions.all? {|v| v < version }
|
83
|
+
message "📥 all versions are older than the version in the save (#{version}), downloading the latest"
|
84
|
+
find_release(mod)
|
85
|
+
else
|
86
|
+
message "✓ newer version exists, nothing to do"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
message "📥 MOD is not installed"
|
90
|
+
exact? ? find_release(mod, version:) : find_release(mod)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
module CLI
|
5
|
+
module DownloadHelper
|
6
|
+
private def find_credential(**credential_options)
|
7
|
+
case credential_options
|
8
|
+
in {user:, token:}
|
9
|
+
return Nauvisian::Credential[user:, token:]
|
10
|
+
in {user:}
|
11
|
+
puts "User is specified, but token is missing"
|
12
|
+
exit 1
|
13
|
+
in {token:}
|
14
|
+
puts "Token is specified, but user is missing"
|
15
|
+
exit 1
|
16
|
+
else
|
17
|
+
Nauvisian::Credential.from_env
|
18
|
+
end
|
19
|
+
rescue KeyError
|
20
|
+
Nauvisian::Credential.from_player_data_file
|
21
|
+
end
|
22
|
+
|
23
|
+
private def find_release(mod, version: nil)
|
24
|
+
api = Nauvisian::API.new
|
25
|
+
releases = api.releases(mod)
|
26
|
+
if version.nil?
|
27
|
+
releases.max_by(&:released_at)
|
28
|
+
else
|
29
|
+
if_none = -> { puts "Version: #{version} not found"; exit 1 }
|
30
|
+
releases.find(if_none) {|release| release.version == version }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Nauvisian
|
7
|
+
module CLI
|
8
|
+
class Lister
|
9
|
+
@listers = {}
|
10
|
+
|
11
|
+
def self.for(format)
|
12
|
+
@listers.fetch(format)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
@listers.keys.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.inherited(subclass)
|
20
|
+
demodulized = Nauvisian.inflector.demodulize(subclass.name)
|
21
|
+
underscored = Nauvisian.inflector.underscore(demodulized)
|
22
|
+
@listers[underscored.to_sym] = subclass
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(headers)
|
28
|
+
@headers = headers
|
29
|
+
end
|
30
|
+
|
31
|
+
class CSV < self
|
32
|
+
def list(rows)
|
33
|
+
CSV(headers: @headers, write_headers: true) do |out|
|
34
|
+
rows.each do |row|
|
35
|
+
out << row.values_at(*@headers)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Gfm < self
|
42
|
+
def list(rows)
|
43
|
+
puts @headers.join("|")
|
44
|
+
puts Array.new(@headers.length, "-").join("|")
|
45
|
+
rows.each do |row|
|
46
|
+
puts row.values_at(*@headers).join("|")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Plain < self
|
52
|
+
def list(rows)
|
53
|
+
rows.each do |row|
|
54
|
+
puts row.values_at(*@headers).join(" ")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Json < self
|
60
|
+
def list(rows)
|
61
|
+
puts rows.to_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
module CLI
|
5
|
+
module MessageHelper
|
6
|
+
private def message(exception_or_message)
|
7
|
+
case exception_or_message
|
8
|
+
in Exception
|
9
|
+
puts exception_or_message.message
|
10
|
+
in String
|
11
|
+
puts exception_or_message
|
12
|
+
else
|
13
|
+
raise TypeError, "must be Exception or String: %p" % message_or_exception
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/cli"
|
4
|
+
|
5
|
+
require "nauvisian"
|
6
|
+
|
7
|
+
require_relative "cli/commands/mod/disable"
|
8
|
+
require_relative "cli/commands/mod/download"
|
9
|
+
require_relative "cli/commands/mod/enable"
|
10
|
+
require_relative "cli/commands/mod/info"
|
11
|
+
require_relative "cli/commands/mod/installed"
|
12
|
+
require_relative "cli/commands/mod/latest"
|
13
|
+
require_relative "cli/commands/mod/settings/dump"
|
14
|
+
require_relative "cli/commands/mod/versions"
|
15
|
+
require_relative "cli/commands/save/mod/list"
|
16
|
+
require_relative "cli/commands/save/mod/sync"
|
17
|
+
|
18
|
+
module Nauvisian
|
19
|
+
module CLI
|
20
|
+
module Commands
|
21
|
+
extend Dry::CLI::Registry
|
22
|
+
|
23
|
+
register "mod disable", Nauvisian::CLI::Commands::Mod::Disable
|
24
|
+
register "mod enable", Nauvisian::CLI::Commands::Mod::Enable
|
25
|
+
register "mod download", Nauvisian::CLI::Commands::Mod::Download
|
26
|
+
register "mod info", Nauvisian::CLI::Commands::Mod::Info
|
27
|
+
register "mod installed", Nauvisian::CLI::Commands::Mod::Installed
|
28
|
+
register "mod latest", Nauvisian::CLI::Commands::Mod::Latest
|
29
|
+
register "mod versions", Nauvisian::CLI::Commands::Mod::Versions
|
30
|
+
register "mod settings dump", Nauvisian::CLI::Commands::Mod::Settings::Dump
|
31
|
+
|
32
|
+
register "save mod list", Nauvisian::CLI::Commands::Save::Mod::List
|
33
|
+
register "save mod sync", Nauvisian::CLI::Commands::Save::Mod::Sync
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Nauvisian
|
6
|
+
Credential = Data.define(:username, :token)
|
7
|
+
|
8
|
+
class Credential
|
9
|
+
class << self
|
10
|
+
private :new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_env
|
14
|
+
# NOTE: values of ENV are already frozen
|
15
|
+
self[username: ENV.fetch("FACTORIO_SERVICE_USERNAME"), token: ENV.fetch("FACTORIO_SERVICE_TOKEN")]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_player_data_file(player_data_file_path: Nauvisian.platform.user_data_directory / "player-data.json")
|
19
|
+
data = JSON.load_file(player_data_file_path)
|
20
|
+
self[username: data["service-username"].freeze, token: data["service-token"].freeze]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
class Deserializer
|
5
|
+
def initialize(stream)
|
6
|
+
raise ArgumentError, "can't read from the given argument" unless stream.respond_to?(:read)
|
7
|
+
|
8
|
+
@stream = stream
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_bytes(length)
|
12
|
+
raise ArgumentError, "nil length" if length.nil?
|
13
|
+
raise ArgumentError, "negative length" if length.negative?
|
14
|
+
return +"" if length.zero?
|
15
|
+
|
16
|
+
bytes = @stream.read(length)
|
17
|
+
raise EOFError if bytes.nil? || bytes.size < length
|
18
|
+
|
19
|
+
bytes
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_u8 = read_bytes(1).unpack1("C")
|
23
|
+
def read_u16 = read_bytes(2).unpack1("v")
|
24
|
+
def read_u32 = read_bytes(4).unpack1("V")
|
25
|
+
|
26
|
+
# https://wiki.factorio.com/Data_types#Space_Optimized
|
27
|
+
def read_optim_u16
|
28
|
+
byte = read_u8
|
29
|
+
byte == 0xFF ? read_u16 : byte
|
30
|
+
end
|
31
|
+
|
32
|
+
# https://wiki.factorio.com/Data_types#Space_Optimized
|
33
|
+
def read_optim_u32
|
34
|
+
byte = read_u8
|
35
|
+
byte == 0xFF ? read_u32 : byte
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_u16_tuple(length) = Array.new(length) { read_u16 }
|
39
|
+
def read_optim_tuple(bit_size, length) = Array.new(length) { read_optim(bit_size) }
|
40
|
+
|
41
|
+
def read_bool = read_u8 != 0
|
42
|
+
|
43
|
+
def read_str
|
44
|
+
length = read_optim_u32
|
45
|
+
read_bytes(length).force_encoding(Encoding::UTF_8)
|
46
|
+
end
|
47
|
+
|
48
|
+
# https://wiki.factorio.com/Property_tree#String
|
49
|
+
def read_str_property = read_bool ? "" : read_str
|
50
|
+
|
51
|
+
# https://wiki.factorio.com/Property_tree#Number
|
52
|
+
def read_double = read_bytes(8).unpack1("d")
|
53
|
+
|
54
|
+
# Assumed: method arguments are evaluated from left to right but...
|
55
|
+
# https://stackoverflow.com/a/36212870/16014712
|
56
|
+
|
57
|
+
def read_version64 = Nauvisian::Version64[read_u16, read_u16, read_u16, read_u16]
|
58
|
+
|
59
|
+
def read_version24 = Nauvisian::Version24[read_optim_u16, read_optim_u16, read_optim_u16]
|
60
|
+
|
61
|
+
# https://wiki.factorio.com/Property_tree#List
|
62
|
+
def read_list
|
63
|
+
length = read_optim_u32
|
64
|
+
Array(length) { read_property_tree }
|
65
|
+
end
|
66
|
+
|
67
|
+
# https://wiki.factorio.com/Property_tree#Dictionary
|
68
|
+
def read_dictionary
|
69
|
+
length = read_u32
|
70
|
+
length.times.each_with_object({}) do |_i, dict|
|
71
|
+
key = read_str_property
|
72
|
+
dict[key] = read_property_tree
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_property_tree
|
77
|
+
type = read_u8
|
78
|
+
_any_type_flag = read_bool
|
79
|
+
|
80
|
+
case type
|
81
|
+
when 1
|
82
|
+
read_bool
|
83
|
+
when 2
|
84
|
+
read_double
|
85
|
+
when 3
|
86
|
+
read_str_property
|
87
|
+
when 4
|
88
|
+
read_list
|
89
|
+
when 5
|
90
|
+
read_dictionary
|
91
|
+
else
|
92
|
+
raise Nauvisian::UnknownPropertyType, type
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
require "rack/utils"
|
7
|
+
|
8
|
+
module Nauvisian
|
9
|
+
class Downloader
|
10
|
+
def initialize(credential:, progress: Nauvisian::Progress::Null)
|
11
|
+
@credential = credential
|
12
|
+
@progress_class = progress
|
13
|
+
@cache = Nauvisian::Cache::FileSystem.new(name: "download", ttl: Float::INFINITY)
|
14
|
+
end
|
15
|
+
|
16
|
+
def download(release, output_path)
|
17
|
+
with_error_handling(release) do
|
18
|
+
@progress = @progress_class.new(release)
|
19
|
+
url = release.download_url.dup
|
20
|
+
url.query = Rack::Utils.build_nested_query(@credential.to_h)
|
21
|
+
data = @cache.fetch(url) { get(url) }
|
22
|
+
File.binwrite(output_path, data)
|
23
|
+
raise Nauvisian::DigestMismatch unless Digest::SHA1.file(output_path) == release.sha1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private def with_error_handling(release)
|
28
|
+
yield
|
29
|
+
rescue OpenURI::HTTPError => e
|
30
|
+
case e.io.status
|
31
|
+
in ["404", _]
|
32
|
+
raise Nauvisian::ModNotFound, release.mod
|
33
|
+
else
|
34
|
+
raise Nauvisian::Error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private def get(url)
|
39
|
+
url.open(content_length_proc: method(:set_total), progress_proc: method(:update_progress)) do |io|
|
40
|
+
case io.content_type
|
41
|
+
when "application/octet-stream"
|
42
|
+
return io.read
|
43
|
+
else # login requested
|
44
|
+
raise Nauvisian::AuthError, io.status[1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private def set_total(total) # rubocop:disable Naming/AccessorMethodName
|
50
|
+
@progress.total = total if total
|
51
|
+
end
|
52
|
+
|
53
|
+
private def update_progress(progress)
|
54
|
+
@progress.progress = progress
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ModNotFound < Error
|
7
|
+
def initialize(mod) = super "Mod not found: #{mod.name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
class AuthError < Error; end
|
11
|
+
class APIError < Error; end
|
12
|
+
class DigestMismatch < Error; end
|
13
|
+
class UnsupportedPlatform < Error; end
|
14
|
+
class UnsupportedVersion < Error; end
|
15
|
+
class UnknownPropertyType < Error; end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
class Mod
|
5
|
+
Detail = Data.define(:downloads_count, :name, :owner, :summary, :title, :category, :created_at, :description)
|
6
|
+
|
7
|
+
class Detail
|
8
|
+
def url = (URI("https://mods.factorio.com/mod/") + name).freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
private :new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nauvisian
|
4
|
+
Mod = Data.define(:name) do
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
def base?
|
8
|
+
name == "base"
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s = name
|
12
|
+
|
13
|
+
def <=>(other)
|
14
|
+
(base? && (other.base? ? 0 : -1)) || (other.base? ? 1 : name.casecmp(other.name))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require_relative "mod/detail"
|
20
|
+
require_relative "mod/release"
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Nauvisian
|
6
|
+
class ModList
|
7
|
+
DEFAULT_MOD_LIST_PATH = Nauvisian.platform.mod_directory / "mod-list.json"
|
8
|
+
private_constant :DEFAULT_MOD_LIST_PATH
|
9
|
+
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def self.load(from=DEFAULT_MOD_LIST_PATH)
|
13
|
+
raw_data = JSON.parse(File.read(from), symbolize_names: true)
|
14
|
+
new(raw_data[:mods].to_h {|e| [Mod[name: e[:name]], e[:enabled]] })
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(mods={})
|
18
|
+
@mods = {Nauvisian::Mod[name: "base"] => true}
|
19
|
+
mods.each do |mod, enabled|
|
20
|
+
next if mod.base?
|
21
|
+
|
22
|
+
@mods[mod] = enabled
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(to=DEFAULT_MOD_LIST_PATH)
|
27
|
+
File.write(to, JSON.pretty_generate({mods: @mods.map {|mod, enabled| {name: mod.name, enabled:} }}))
|
28
|
+
end
|
29
|
+
|
30
|
+
def each
|
31
|
+
return @mods.to_enum unless block_given?
|
32
|
+
|
33
|
+
@mods.each do |mod, enabled|
|
34
|
+
yield(mod, enabled)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def add(mod, enabled: nil)
|
39
|
+
raise ArgumentError, "Can't disable the base mod" if mod.base? && enabled == false
|
40
|
+
|
41
|
+
@mods[mod] = enabled.nil? ? true : enabled
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove(mod)
|
45
|
+
raise ArgumentError, "Can't remove the base mod" if mod.base?
|
46
|
+
|
47
|
+
@mods.delete(mod)
|
48
|
+
end
|
49
|
+
|
50
|
+
def exist?(mod) = @mods.key?(mod)
|
51
|
+
|
52
|
+
def enabled?(mod)
|
53
|
+
raise Nauvisian::ModNotFound, mod unless exist?(mod)
|
54
|
+
|
55
|
+
@mods[mod]
|
56
|
+
end
|
57
|
+
|
58
|
+
def enable(mod)
|
59
|
+
raise Nauvisian::ModNotFound, mod unless exist?(mod)
|
60
|
+
|
61
|
+
@mods[mod] = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def disable(mod)
|
65
|
+
raise ArgumentError, "Can't disalbe the base mod" if mod.base?
|
66
|
+
raise Nauvisian::ModNotFound, mod unless exist?(mod)
|
67
|
+
|
68
|
+
@mods[mod] = false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|