gamerom 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.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "gamerom"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/gamerom ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "gamerom"
4
+
5
+ Gamerom::Cli.start(ARGV)
data/gamerom.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gamerom/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gamerom"
7
+ spec.version = Gamerom::VERSION
8
+ spec.authors = ["Lucas Mundim"]
9
+ spec.email = ["lucas.mundim@gmail.com"]
10
+
11
+ spec.summary = "The Video Game ROM downloader"
12
+ spec.description = "A command-line installer for game ROMs from many repositories."
13
+ spec.homepage = "https://github.com/lucasmundim/gamerom"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_runtime_dependency "mechanize", "~> 2.8.0"
33
+ spec.add_runtime_dependency "nokogiri", "~> 1.11.3"
34
+ spec.add_runtime_dependency "rest-client", "~> 2.1.0"
35
+ spec.add_runtime_dependency "thor", "~> 1.1.0"
36
+
37
+ # For more information and examples about making a new gem, checkout our
38
+ # guide at: https://bundler.io/guides/creating_gem.html
39
+ end
data/lib/gamerom.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gamerom/config"
4
+ require_relative "gamerom/game"
5
+ require_relative "gamerom/repo"
6
+ require_relative "gamerom/cli"
7
+ require_relative "gamerom/version"
8
+
9
+ module Gamerom
10
+ class Error < StandardError; end
11
+ end
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Gamerom
6
+ class Cli < Thor
7
+ class_option :verbose, :aliases => ['-v'], :type => :boolean, default: false, desc: "Show verbose backtrace"
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc 'config', 'Show config'
14
+ def config
15
+ cfg = {
16
+ ROM_ROOT: Gamerom::ROM_ROOT,
17
+ CACHE_DIR: Gamerom::CACHE_DIR,
18
+ GAME_DIR: Gamerom::GAME_DIR,
19
+ LOG_DIR: Gamerom::LOG_DIR,
20
+ }
21
+ pp cfg
22
+ end
23
+
24
+ desc 'info GAME_IDENTIFIER', 'Info for game GAME_IDENTIFIER (id/name)'
25
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
26
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
27
+ def info(game_identifier)
28
+ repo = Repo.new(options[:repo])
29
+ validate_platform repo, options[:platform]
30
+ puts "showing info for game #{game_identifier} on #{options[:platform]} platform on #{options[:repo]} repo..."
31
+ game = repo.find(options[:platform], game_identifier)
32
+ if game.nil?
33
+ shell.say "Game #{game_identifier} not found", :red
34
+ end
35
+ puts game
36
+ puts game.filename if game.installed?
37
+ rescue => e
38
+ render_error e, options
39
+ exit 1
40
+ end
41
+
42
+ desc 'install GAME_IDENTIFIER', 'Install game GAME_IDENTIFIER (id/name)'
43
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
44
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
45
+ def install(game_identifier)
46
+ repo = Repo.new(options[:repo])
47
+ validate_platform repo, options[:platform]
48
+ game = repo.find(options[:platform], game_identifier)
49
+ if game.nil?
50
+ shell.say "Game #{game_identifier} not found", :red
51
+ return
52
+ end
53
+ puts "installing game #{game.id} - #{game.name} - #{game.region} on #{options[:platform]} platform on #{options[:repo]} repo..."
54
+ if game.installed?
55
+ shell.say "Game already installed", :yellow
56
+ return
57
+ end
58
+ game.install
59
+ shell.say "Game installed", :green
60
+ rescue => e
61
+ render_error e, options
62
+ exit 1
63
+ end
64
+
65
+ desc 'install_all', 'Install all games'
66
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
67
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
68
+ option :region, :aliases => ['-g'], type: :string, required: false, desc: "Only from specified region"
69
+ def install_all
70
+ repo = Repo.new(options[:repo])
71
+ validate_platform repo, options[:platform]
72
+ games = repo.games options[:platform], region: options[:region]
73
+ games.each do |game|
74
+ install(game.id) unless game.installed?
75
+ end
76
+ rescue => e
77
+ render_error e, options
78
+ exit 1
79
+ end
80
+
81
+ desc 'list', 'List available games'
82
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
83
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
84
+ option :region, :aliases => ['-g'], type: :string, required: false, desc: "Only from specified region"
85
+ def list
86
+ repo = Repo.new(options[:repo])
87
+ validate_platform repo, options[:platform]
88
+ puts "listing available games for #{options[:platform]} platform on #{options[:repo]} repo..."
89
+ games = repo.games options[:platform], region: options[:region]
90
+ print_game_table(games)
91
+ rescue => e
92
+ render_error e, options
93
+ exit 1
94
+ end
95
+
96
+ desc 'platforms', 'List available platforms'
97
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
98
+ def platforms
99
+ puts "listing available platforms for #{options[:repo]} repo..."
100
+ platforms = { platforms: Repo.new(options[:repo]).platforms }
101
+ puts platforms.to_yaml
102
+ rescue => e
103
+ render_error e, options
104
+ exit 1
105
+ end
106
+
107
+ desc 'recover', 'Try to recover state from already downloaded roms'
108
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
109
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
110
+ def recover
111
+ repo = Repo.new(options[:repo])
112
+ validate_platform repo, options[:platform]
113
+ puts "recovering state of roms for #{options[:platform]} platform on #{options[:repo]} repo..."
114
+ games = repo.games options[:platform]
115
+ games_not_found = []
116
+ games.each do |game|
117
+ filename = nil
118
+ basename = "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}/#{game[:region]}/#{game[:name]}"
119
+ ['zip', '7z', 'rar'].each do |ext|
120
+ if File.exists? "#{basename}.#{ext}"
121
+ filename = "#{basename}.#{ext}"
122
+ end
123
+ end
124
+
125
+ if filename
126
+ game.update_state File.basename(filename)
127
+ puts "Found game #{game[:name]}"
128
+ else
129
+ games_not_found << game[:name]
130
+ end
131
+ end
132
+ if games_not_found.count > 0
133
+ puts "Games not found:"
134
+ puts games_not_found
135
+ end
136
+ rescue => e
137
+ puts e.message
138
+ exit 1
139
+ end
140
+
141
+ desc 'regions', 'List available regions'
142
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
143
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
144
+ def regions
145
+ repo = Repo.new(options[:repo])
146
+ validate_platform repo, options[:platform]
147
+ puts "listing available regions for #{options[:platform]} platform on #{options[:repo]} repo..."
148
+ puts repo.regions options[:platform]
149
+ rescue => e
150
+ render_error e, options
151
+ exit 1
152
+ end
153
+
154
+ desc 'repo', 'List available repo'
155
+ def repo
156
+ puts "listing available repo..."
157
+ puts Repo.list
158
+ rescue => e
159
+ render_error e, options
160
+ exit 1
161
+ end
162
+
163
+ desc 'search KEYWORD', 'Search games by KEYWORD'
164
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
165
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
166
+ option :region, :aliases => ['-g'], type: :string, required: false, desc: "Only from specified region"
167
+ def search(keyword)
168
+ repo = Repo.new(options[:repo])
169
+ validate_platform repo, options[:platform]
170
+ puts "searching available games for #{options[:platform]} platform on #{options[:repo]} repo..."
171
+ games = repo.games options[:platform], region: options[:region], keyword: keyword
172
+ print_game_table(games)
173
+ rescue => e
174
+ render_error e, options
175
+ exit 1
176
+ end
177
+
178
+ desc 'stats', 'Show platform stats'
179
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
180
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
181
+ def stats
182
+ repo = Repo.new(options[:repo])
183
+ validate_platform repo, options[:platform]
184
+ puts "stats for #{options[:platform]} platform on #{options[:repo]} repo..."
185
+ games = repo.games options[:platform]
186
+ total = games.count
187
+ installed = games.select { |game| game.installed? }.count
188
+ size = 0
189
+ if File.exists? "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}"
190
+ size = `du -hs "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}/"|awk '{ print $1 }'`
191
+ end
192
+ puts " All: #{installed}/#{total} - size: #{size}"
193
+ repo.regions(options[:platform]).each do |region|
194
+ games = repo.games(options[:platform], region: region)
195
+ total = games.count
196
+ installed = games.select { |game| game.installed? }.count
197
+ size = 0
198
+ if File.exists? "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}/#{region}"
199
+ size = `du -hs "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}/#{region}/"|awk '{ print $1 }'`
200
+ end
201
+ puts " #{region}: #{installed}/#{total} - size: #{size}"
202
+ end
203
+ puts
204
+ rescue => e
205
+ render_error e, options
206
+ exit 1
207
+ end
208
+
209
+ desc 'stats_all', 'Show stats for all platforms'
210
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
211
+ def stats_all
212
+ repo = Repo.new(options[:repo])
213
+ repo.platforms.keys.each do |platform|
214
+ a = Gamerom::Cli.new
215
+ a.options = { platform: platform, repo: options[:repo] }
216
+ a.stats
217
+ end
218
+ rescue => e
219
+ render_error e, options
220
+ exit 1
221
+ end
222
+
223
+ desc 'uninstall GAME_IDENTIFIER', 'Uninstall game GAME_IDENTIFIER (id/name)'
224
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
225
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
226
+ def uninstall(game_identifier)
227
+ repo = Repo.new(options[:repo])
228
+ validate_platform repo, options[:platform]
229
+ game = repo.find(options[:platform], game_identifier)
230
+ if game.nil?
231
+ shell.say "Game #{game_identifier} not found", :red
232
+ return
233
+ end
234
+ puts "uninstalling game #{game.id} - #{game.name} - #{game.region} on #{options[:platform]} platform..."
235
+ if !game.installed?
236
+ shell.say "Game is not installed", :yellow
237
+ return
238
+ end
239
+ game.uninstall
240
+ shell.say "Game uninstalled", :green
241
+ rescue => e
242
+ render_error e, options
243
+ exit 1
244
+ end
245
+
246
+ desc 'uninstall_all', 'Uninstall all games'
247
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
248
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
249
+ option :region, :aliases => ['-g'], type: :string, required: false, desc: "Only from specified region"
250
+ def uninstall_all
251
+ repo = Repo.new(options[:repo])
252
+ validate_platform repo, options[:platform]
253
+ games = repo.games options[:platform], region: options[:region]
254
+ games.each do |game|
255
+ uninstall(game.id) if game.installed?
256
+ end
257
+ rescue => e
258
+ render_error e, options
259
+ exit 1
260
+ end
261
+
262
+ desc 'update_all_databases', 'Update all local databases'
263
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
264
+ def update_all_databases
265
+ repo = Repo.new(options[:repo])
266
+ repo.platforms.keys.each do |platform|
267
+ a = Gamerom::Cli.new
268
+ a.options = { platform: platform, repo: options[:repo] }
269
+ a.update_database
270
+ end
271
+ shell.say 'All game databases updated', :green
272
+ rescue => e
273
+ render_error e, options
274
+ exit 1
275
+ end
276
+
277
+ desc 'update_database', 'Update local database'
278
+ option :repo, :aliases => ['-r'], type: :string, required: true, desc: "Which repo to use", enum: Gamerom::Repo.list.map(&:to_s)
279
+ option :platform, :aliases => ['-p'], type: :string, required: true, desc: "Which platform to use"
280
+ def update_database
281
+ repo = Repo.new(options[:repo])
282
+ validate_platform repo, options[:platform]
283
+ puts "updating #{options[:platform]} platform on #{options[:repo]} repo..."
284
+ repo.update_database options[:platform]
285
+ shell.say "Game database updated for platform #{options[:platform]} on #{options[:repo]} repo", :green
286
+ rescue => e
287
+ render_error e, options
288
+ exit 1
289
+ end
290
+
291
+ desc 'version', 'Print program version'
292
+ def version
293
+ puts Gamerom::VERSION
294
+ end
295
+
296
+ private
297
+ def print_game_table(games)
298
+ results = []
299
+
300
+ games.each do |game|
301
+ results << [
302
+ game.id,
303
+ game.name,
304
+ game.region,
305
+ game.installed? ? shell.set_color('installed', :green) : '-',
306
+ ]
307
+ end
308
+ results.sort_by! { |columns| columns[1] }
309
+ results.unshift ['ID', 'NAME', 'REGION', 'INSTALLED']
310
+ shell.print_table(results)
311
+ end
312
+
313
+ def render_error exception, options
314
+ shell.say exception.message, :red
315
+ shell.say exception.full_message.force_encoding('utf-8'), :red if options[:verbose]
316
+ end
317
+
318
+ def validate_platform(repo, platform)
319
+ raise "Expected '--platform' to be one of #{repo.platforms.keys.join(', ')}; got #{platform}" if !repo.platforms.keys.include? options[:platform]
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,19 @@
1
+ # 'frozen_string_literal' => true
2
+
3
+ require 'fileutils'
4
+ require 'logger'
5
+ require 'rest-client'
6
+ require 'mechanize'
7
+
8
+ module Gamerom
9
+ ROM_ROOT = ENV['ROM_ROOT'] || File.expand_path("~/.gamerom")
10
+ CACHE_DIR = ENV['CACHE_DIR'] || "#{ROM_ROOT}/cache"
11
+ GAME_DIR = ENV['GAME_DIR'] || "#{ROM_ROOT}/games"
12
+ LOG_DIR = ENV['LOG_DIR'] || "#{ROM_ROOT}/logs"
13
+ STATE_DIR = ENV['STATE_DIR'] || "#{ROM_ROOT}/state"
14
+ end
15
+
16
+ FileUtils.mkdir_p(Gamerom::LOG_DIR)
17
+ logger = Logger.new("#{Gamerom::LOG_DIR}/requests.log")
18
+ RestClient.log = logger
19
+ Mechanize.log = logger
@@ -0,0 +1,49 @@
1
+ # 'frozen_string_literal' => true
2
+
3
+ require 'fileutils'
4
+ require 'ostruct'
5
+
6
+ module Gamerom
7
+ class Game < OpenStruct
8
+ def filename
9
+ "#{self.filepath}/#{File.read(self.state_filename)}"
10
+ end
11
+
12
+ def filepath
13
+ "#{Gamerom::GAME_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}"
14
+ end
15
+
16
+ def install
17
+ self.repo.install self do |filename|
18
+ self.update_state filename
19
+ end
20
+ end
21
+
22
+ def installed?
23
+ File.exists? self.state_filename
24
+ end
25
+
26
+ def state_filename
27
+ "#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}/#{self.id}"
28
+ end
29
+
30
+ def to_s
31
+ "#{self.id} - #{self.name} - #{self.region}#{self.installed? ? " (#{shell.set_color 'installed', :green})" : ''}"
32
+ end
33
+
34
+ def uninstall
35
+ FileUtils.rm_rf self.filename
36
+ FileUtils.rm_rf self.state_filename
37
+ end
38
+
39
+ def update_state filename
40
+ FileUtils.mkdir_p("#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}")
41
+ File.write(self.state_filename, filename)
42
+ end
43
+
44
+ private
45
+ def shell
46
+ @shell ||= Thor::Shell::Color.new
47
+ end
48
+ end
49
+ end