gamerom 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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