retroflix 0.0.3
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/MIT-LICENSE +20 -0
- data/README.md +50 -0
- data/bin/rf +8 -0
- data/lib/archive.rb +22 -0
- data/lib/conf.rb +18 -0
- data/lib/deep_struct.rb +27 -0
- data/lib/emulator.rb +23 -0
- data/lib/library.rb +39 -0
- data/lib/scrape.rb +133 -0
- data/random.rb +21 -0
- data/retroflix.yml +42 -0
- data/server.rb +204 -0
- data/site.rb +76 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d92944f606f69c813af81701e7d52e820c0dbb9
|
4
|
+
data.tar.gz: 94dc77c7a5c6cbe88cb0b1f0c747e656b438a325
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eaf6241347ddbcda5353d3e81e3ced4185469de262e848ce8962ddc2497cfd10a23bfc5b253452dc3b961042d0e5dccb22d7d6c80672fa9b89493831e76a58b5
|
7
|
+
data.tar.gz: 68e930eb461522a5ae63f1b35372cbe81c3d33dae6aa252f5dc0193b13273797700468e831b5106754cdbcbec3ee7fbf142319bc54f28f1cd32ea08f1dda5882
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2017 Mohammed Morsi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# RetroFlix - Web based retro game downloader & library manager
|
2
|
+
|
3
|
+
**RetroFlix** is a frontend to download and manage legacy & retro applications and launch them using their target emulators.
|
4
|
+
|
5
|
+
Currently only one game db and a handfull of systems are supported, but more may be added at some point, depending on interest.
|
6
|
+
|
7
|
+
**Important**: This software should only be used for *Legal* purposes, inorder to retrieve copies
|
8
|
+
of applications which the user already owns or otherwise has a legitimate license to / rights to use.
|
9
|
+
|
10
|
+
Currently the following sites & systems are supported:
|
11
|
+
|
12
|
+
* [emuparadise](https://www.emuparadise.me/)
|
13
|
+
* N64
|
14
|
+
* SNES
|
15
|
+
* NES
|
16
|
+
* Sega Genesis / Master System
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
RetroFlix is built as a [Sinatra](http://www.sinatrarb.com/) web service.
|
21
|
+
|
22
|
+
To use [install Ruby](https://www.ruby-lang.org/en/) and install the following gems:
|
23
|
+
|
24
|
+
```$ gem install sinatra rubyzip curb nokogiri thin```
|
25
|
+
|
26
|
+
|
27
|
+
If the previous produces any errors, check the gem documentation for the corresponding
|
28
|
+
dependencies (curb requires libcurl-dev which may need to be installed seperately).
|
29
|
+
|
30
|
+
If a recent version of Ruby is not available for your system, you may want to
|
31
|
+
try out [rbenv](https://github.com/rbenv/rbenv).
|
32
|
+
|
33
|
+
Launch it with
|
34
|
+
|
35
|
+
```$ ruby server.rb```
|
36
|
+
|
37
|
+
And navigate to [http://localhost:4567](http://localhost:4567) to download and manage games!
|
38
|
+
|
39
|
+
**Note**: Inorder to play games you will need to download the emulator for the corresponding systems. See the **emulators.rb** file for the current list of emulators used (may be configured there)
|
40
|
+
|
41
|
+
|
42
|
+
## Screens
|
43
|
+
|
44
|
+
<img src="https://raw.githubusercontent.com/wiki/movitto/retroflix/screens/my_library.png" width="40%"/>
|
45
|
+
|
46
|
+
<img src="https://raw.githubusercontent.com/wiki/movitto/retroflix/screens/game_info.png" width="40%" />
|
47
|
+
|
48
|
+
<img src="https://raw.githubusercontent.com/wiki/movitto/retroflix/screens/game_previews.png" width="40%"/>
|
49
|
+
|
50
|
+
<img src="https://raw.githubusercontent.com/wiki/movitto/retroflix/screens/game_list.png" width="40%"/>
|
data/bin/rf
ADDED
data/lib/archive.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Archive extraction utilities
|
2
|
+
# Part of RetroFlix
|
3
|
+
|
4
|
+
require 'tempfile'
|
5
|
+
require 'zip'
|
6
|
+
|
7
|
+
module RetroFlix
|
8
|
+
# Extract archive if valid format is detected, else simply return
|
9
|
+
# Currently only supports Zip files but other archive types may
|
10
|
+
# be added
|
11
|
+
def self.extract_archive(archive)
|
12
|
+
f = Tempfile.new('retroflix')
|
13
|
+
f.write archive
|
14
|
+
|
15
|
+
begin
|
16
|
+
zf = Zip::File.open(f.path).first
|
17
|
+
[zf.name, zf.get_input_stream.read]
|
18
|
+
rescue Zip::Error => e
|
19
|
+
archive
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/conf.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Config
|
2
|
+
# Part of RetroFlix
|
3
|
+
|
4
|
+
require_relative './deep_struct'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module RetroFlix
|
8
|
+
CONFIG_FILES = [File.expand_path('~/.retroflix.yml'),
|
9
|
+
File.expand_path('../../retroflix.yml', __FILE__)]
|
10
|
+
|
11
|
+
unless CONFIG_FILES.any? { |cf| File.exists?(cf) }
|
12
|
+
raise RuntimeError, "cannot find config at #{CONFIG_FILES.join(", ")}"
|
13
|
+
end
|
14
|
+
|
15
|
+
CONFIG_FILES.each { |cf|
|
16
|
+
Config = DeepStruct.new(YAML.load_file(cf)) if File.exists?(cf) && !defined?(Config)
|
17
|
+
}
|
18
|
+
end
|
data/lib/deep_struct.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class DeepStruct < OpenStruct
|
4
|
+
attr_accessor :keys
|
5
|
+
|
6
|
+
def initialize(hash=nil)
|
7
|
+
@table = {}
|
8
|
+
@hash_table = {}
|
9
|
+
@keys = []
|
10
|
+
|
11
|
+
if hash
|
12
|
+
hash.each do |k,v|
|
13
|
+
@keys << k
|
14
|
+
|
15
|
+
@table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
|
16
|
+
@hash_table[k.to_sym] = v
|
17
|
+
|
18
|
+
new_ostruct_member(k)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
@hash_table
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/emulator.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Emulator configuration
|
2
|
+
# Part of RetroFlix
|
3
|
+
|
4
|
+
module RetroFlix
|
5
|
+
# Run on main display
|
6
|
+
DISPLAY="DISPLAY=:0"
|
7
|
+
|
8
|
+
# Return configured emulator for system
|
9
|
+
def self.emulator_for(system)
|
10
|
+
emulator = Config.emulators.send(system)
|
11
|
+
"#{Config.emulators.env} #{emulator.bin} #{emulator.flags}"
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# fork / execs emaulator process & detaches
|
16
|
+
def self.play_game(system, game)
|
17
|
+
emulator = emulator_for(system).gsub("GAME", game_path_for(system, game))
|
18
|
+
|
19
|
+
pid = fork{ exec emulator }
|
20
|
+
Process.detach pid
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
data/lib/library.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Game library management routines.
|
2
|
+
# The library consists of games downloaded into the
|
3
|
+
# local games/ directory.
|
4
|
+
# Part of RetroFlix
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module RetroFlix
|
9
|
+
def self.game_dir_for(system)
|
10
|
+
"#{Config.meta.games_dir}#{system}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.game_path_for(system, game)
|
14
|
+
dir = "#{game_dir_for(system)}/#{game}"
|
15
|
+
Dir.glob("#{dir}/*").first
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.write_game(system, game, game_file, contents)
|
19
|
+
dir = game_dir_for(system)
|
20
|
+
FileUtils.mkdir_p("#{dir}/#{game}/") unless File.directory?("#{dir}/#{game}/")
|
21
|
+
File.write("#{dir}/#{game}/#{game_file}", contents)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.library_games_for(systems)
|
25
|
+
return library_games_for([systems]) unless systems.is_a?(Array)
|
26
|
+
systems.collect { |sys|
|
27
|
+
dir = "#{game_dir_for(sys)}/"
|
28
|
+
Dir.glob("#{dir}*").collect { |g| g.gsub(dir, "") }
|
29
|
+
}.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.have_game?(system, game)
|
33
|
+
library_games_for(system).include?(game)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.delete_game(system, game)
|
37
|
+
FileUtils.rm_rf("#{game_dir_for(system)}/#{game}")
|
38
|
+
end
|
39
|
+
end
|
data/lib/scrape.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Main scraper, retrieve game list, information,
|
2
|
+
# and contents from remote server.
|
3
|
+
# Part of RetroFlix
|
4
|
+
|
5
|
+
require 'curb'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
module RetroFlix
|
9
|
+
def self.cached(id, &setter)
|
10
|
+
@cache ||= {}
|
11
|
+
@cache_timestamps ||= {}
|
12
|
+
return @cache[id] if @cache.key?(id) &&
|
13
|
+
((Time.now - @cache_timestamps[id]) <
|
14
|
+
(60 * 60 * Config.meta.cache_time))
|
15
|
+
@cache_timestamps[id] = Time.now
|
16
|
+
@cache[id] = setter.call
|
17
|
+
end
|
18
|
+
|
19
|
+
###
|
20
|
+
|
21
|
+
# TODO support using multiple databases (how resolve conflicts ?)
|
22
|
+
def self.db_config
|
23
|
+
@db_config ||= Config.databases[Config.databases.default]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.base_url
|
27
|
+
@base_url ||= db_config.url
|
28
|
+
end
|
29
|
+
|
30
|
+
###
|
31
|
+
|
32
|
+
def self.systems
|
33
|
+
db_config.systems.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.system_url(system)
|
37
|
+
"#{base_url}/#{db_config.systems[system]}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.valid_system?(sys)
|
41
|
+
systems.collect { |s| s.to_s }.include? sys
|
42
|
+
end
|
43
|
+
|
44
|
+
###
|
45
|
+
|
46
|
+
# Return a hash of all games (name to relative url of info page) for given systems
|
47
|
+
def self.games_for(system)
|
48
|
+
url = system_url(system)
|
49
|
+
cached(url) do
|
50
|
+
http = Curl.get url
|
51
|
+
parser = Nokogiri::HTML(http.body_str)
|
52
|
+
games = {}
|
53
|
+
parser.xpath(db_config.paths.games).each { |node|
|
54
|
+
href = node.attribute("href").to_s
|
55
|
+
title = node.text
|
56
|
+
games[title] = href unless db_config.omit.any? { |o| title =~ Regexp.new(o) }
|
57
|
+
}
|
58
|
+
|
59
|
+
games
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
###
|
64
|
+
|
65
|
+
# Return game url for given system / game
|
66
|
+
def self.game_url_for(system, game)
|
67
|
+
"#{base_url}#{games_for(system)[game]}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return download url for given system / game
|
71
|
+
def self.download_url_for(system, game)
|
72
|
+
"#{game_url_for(system, game)}-download"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return parsed game info page
|
76
|
+
def self.game_page_for(system, game)
|
77
|
+
url = game_url_for(system, game)
|
78
|
+
cached(url) do
|
79
|
+
Nokogiri::HTML(Curl.get(url).body_str)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return full urls to all game screens
|
84
|
+
def self.screens_for(game_page)
|
85
|
+
game_page.xpath(db_config.paths.screens).collect { |node|
|
86
|
+
src = node.attribute("data-original").to_s
|
87
|
+
src == "" || src.nil? ? nil : "http:#{src}"
|
88
|
+
}.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return html content of game descriptions
|
92
|
+
def self.desc_for(game_page)
|
93
|
+
game_page.xpath(db_config.paths.desc).collect { |node|
|
94
|
+
node.inner_text.encode("UTF-8", invalid: :replace, undef: :replace)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Return hash of all metadata (screens, desc)
|
99
|
+
# for system/game
|
100
|
+
def self.game_meta_for(system, game)
|
101
|
+
page = game_page_for(system, game)
|
102
|
+
|
103
|
+
{:screens => screens_for(page),
|
104
|
+
:descs => desc_for(page) }
|
105
|
+
end
|
106
|
+
|
107
|
+
###
|
108
|
+
|
109
|
+
# Return link to donwload game
|
110
|
+
def self.download_link_for(system, game)
|
111
|
+
url = download_url_for(system, game)
|
112
|
+
http = Curl.get(url) { |http|
|
113
|
+
http.headers['Cookie'] = 'downloadcaptcha=1'
|
114
|
+
}
|
115
|
+
|
116
|
+
parser = Nokogiri::HTML(http.body_str)
|
117
|
+
parser.xpath(db_config.paths.dl).first.attribute('href').to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
# Download game, saves to system/game file
|
121
|
+
def self.download(system, game)
|
122
|
+
dl_url = download_link_for(system, game)
|
123
|
+
|
124
|
+
url = "#{base_url}/#{dl_url}"
|
125
|
+
|
126
|
+
http = Curl.get(url) { |http|
|
127
|
+
http.headers['Referer'] = url
|
128
|
+
http.follow_location = true
|
129
|
+
}
|
130
|
+
|
131
|
+
http.body_str
|
132
|
+
end
|
133
|
+
end # module RetroFlix
|
data/random.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Helper script to download random game
|
2
|
+
# Part of RetroFlix
|
3
|
+
|
4
|
+
require_relative './lib/conf'
|
5
|
+
require_relative './lib/scrape'
|
6
|
+
require_relative './lib/library'
|
7
|
+
|
8
|
+
system = RetroFlix::systems.sample
|
9
|
+
|
10
|
+
games = RetroFlix.games_for(system)
|
11
|
+
game = games.to_a.sample
|
12
|
+
name = game[0]
|
13
|
+
url = game[1]
|
14
|
+
|
15
|
+
page = RetroFlix.game_page_for(system, game)
|
16
|
+
screens = RetroFlix.screens_for(page)
|
17
|
+
desc = RetroFlix.desc_for(page)
|
18
|
+
|
19
|
+
RetroFlix.write_game(system, name, name, RetroFlix.download(system, name))
|
20
|
+
|
21
|
+
puts "Downloaded #{name} for #{system}"
|
data/retroflix.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
---
|
2
|
+
# App Wide Metadata
|
3
|
+
meta:
|
4
|
+
games_dir: ./games/
|
5
|
+
games_per_page: 5
|
6
|
+
cache_time: 24 # hours
|
7
|
+
|
8
|
+
# Game Databases
|
9
|
+
databases:
|
10
|
+
default: emuparadise
|
11
|
+
|
12
|
+
emuparadise:
|
13
|
+
url: https://www.emuparadise.me
|
14
|
+
systems:
|
15
|
+
N64: 'Nintendo_64_ROMs/List-All-Titles/9'
|
16
|
+
NES: 'Nintendo_Entertainment_System_ROMs/List-All-Titles/13'
|
17
|
+
SNES: 'Super_Nintendo_Entertainment_System_(SNES)_ROMs/List-All-Titles/5'
|
18
|
+
Genesis: 'Sega_Genesis_-_Sega_Megadrive_ROMs/List-All-Titles/6'
|
19
|
+
paths:
|
20
|
+
games: "//a[contains(@class, 'gamelist')]"
|
21
|
+
screens: "//a[contains(@class, 'sc')]/img"
|
22
|
+
desc: "//div[contains(@id, 'game-descriptions')]/div[contains(@class, 'description-text')]"
|
23
|
+
dl: "//a[contains(@id, 'download-link')]"
|
24
|
+
omit:
|
25
|
+
- "/\\[BIOS\\].*/"
|
26
|
+
- "/\\[Program\\].*/"
|
27
|
+
- "/\\[SegaNet\\].*/"
|
28
|
+
|
29
|
+
# TODO coolroms, other dbs
|
30
|
+
|
31
|
+
# Emulator binaries
|
32
|
+
emulators:
|
33
|
+
env: "DISPLAY=:0"
|
34
|
+
NES:
|
35
|
+
bin: nestopia
|
36
|
+
flags: "\"GAME\""
|
37
|
+
SNES:
|
38
|
+
bin: znes
|
39
|
+
flags: "\"GAME\""
|
40
|
+
Genesis:
|
41
|
+
bin: gens
|
42
|
+
flags: "\"GAME\""
|
data/server.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# Main sinatra application.
|
3
|
+
# Part of RetroFlix
|
4
|
+
|
5
|
+
require 'sinatra'
|
6
|
+
require_relative './lib/conf'
|
7
|
+
require_relative './lib/library'
|
8
|
+
require_relative './lib/scrape'
|
9
|
+
require_relative './lib/archive'
|
10
|
+
require_relative './lib/emulator'
|
11
|
+
require_relative './site'
|
12
|
+
|
13
|
+
# Landing page, just render layout & title
|
14
|
+
get '/' do
|
15
|
+
layout("/") { |doc|
|
16
|
+
doc.script(:type => "text/javascript",
|
17
|
+
:src => "slideshow.js")
|
18
|
+
doc.h1(:id => "main_title",
|
19
|
+
:class => "blink_me",
|
20
|
+
:style => "margin: auto;").text "RetroFlix"
|
21
|
+
0.upto(5){ |n|
|
22
|
+
doc.img(:class => "slideshow", :src => "slideshow/#{n}.jpg")
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Render list of all games for a system
|
28
|
+
get "/system/:system" do
|
29
|
+
system = validate_system!(params)
|
30
|
+
games = RetroFlix.games_for(system.intern)
|
31
|
+
|
32
|
+
layout(system) { |doc|
|
33
|
+
doc.h3.text "Games for #{system}"
|
34
|
+
doc.a(:href => "/system/#{system}/1").text "Previews"
|
35
|
+
|
36
|
+
games.keys.each { |game|
|
37
|
+
doc.div {
|
38
|
+
game_path = "#{system}/#{URI.encode(game)}"
|
39
|
+
info_path = "/game/#{game_path}"
|
40
|
+
play_path = "/play/#{game_path}"
|
41
|
+
dl_path = "/download/#{game_path}"
|
42
|
+
|
43
|
+
doc.a(:href => info_path).text game
|
44
|
+
|
45
|
+
if RetroFlix.have_game?(system, game)
|
46
|
+
doc.text " - "
|
47
|
+
doc.a(:href => play_path, :class => "play_link").text "[P]"
|
48
|
+
|
49
|
+
else
|
50
|
+
doc.text " - "
|
51
|
+
doc.a(:href => dl_path, :class => "dl_link").text "[D]"
|
52
|
+
end
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Paginated list of games (w/ screenshots)
|
59
|
+
get "/system/:system/:num" do
|
60
|
+
system = validate_system!(params)
|
61
|
+
num = validate_num!(params)
|
62
|
+
games = RetroFlix.games_for(system.intern)
|
63
|
+
|
64
|
+
gpp = RetroFlix::Config.meta.games_per_page
|
65
|
+
start_index = (num-1)*gpp
|
66
|
+
end_index = [start_index+gpp-1, games.size-1].min
|
67
|
+
|
68
|
+
is_first = start_index == 0
|
69
|
+
is_last = end_index == (games.size - 1)
|
70
|
+
last_page = (games.size / gpp).to_i + 1
|
71
|
+
|
72
|
+
layout(system) { |doc|
|
73
|
+
doc.h3.text "Games for #{system}"
|
74
|
+
|
75
|
+
unless is_first
|
76
|
+
doc.a(:href => "/system/#{system}/1").text "<< "
|
77
|
+
doc.a(:href => "/system/#{system}/#{num-1}").text "< "
|
78
|
+
end
|
79
|
+
|
80
|
+
doc.a(:href => "/system/#{system}").text "All"
|
81
|
+
|
82
|
+
unless is_last
|
83
|
+
doc.a(:href => "/system/#{system}/#{num+1}").text " >"
|
84
|
+
doc.a(:href => "/system/#{system}/#{last_page}").text " >>"
|
85
|
+
end
|
86
|
+
|
87
|
+
games.keys[start_index..end_index].each { |game|
|
88
|
+
meta = RetroFlix::game_meta_for(system.intern, game)
|
89
|
+
game_path = "#{system}/#{URI.encode(game)}"
|
90
|
+
|
91
|
+
doc.div(:class => "game_preview"){
|
92
|
+
doc.a(:href => "/game/#{game_path}") {
|
93
|
+
doc.img(:src => meta[:screens].first,
|
94
|
+
:class => "preview_screen")
|
95
|
+
}
|
96
|
+
|
97
|
+
doc.div(:class => "preview_text") {
|
98
|
+
doc.a(:href => "/game/#{game_path}").text game
|
99
|
+
}
|
100
|
+
|
101
|
+
doc.div(:style => "clear: both;")
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Games downloaded locally
|
108
|
+
get "/library" do
|
109
|
+
layout("library") { |doc|
|
110
|
+
RetroFlix::systems.each_with_index { |sys, i|
|
111
|
+
doc.h3.text sys
|
112
|
+
RetroFlix.library_games_for(sys).each { |game|
|
113
|
+
game_path = "#{sys}/#{URI.encode(game)}"
|
114
|
+
info_path = "/game/#{game_path}"
|
115
|
+
play_path = "/play/#{game_path}"
|
116
|
+
|
117
|
+
doc.div { |doc|
|
118
|
+
doc.a(:href => "#{info_path}").text game
|
119
|
+
doc.text " "
|
120
|
+
doc.a(:href => "#{play_path}", :class => "play_link").text "P"
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
unless i == (RetroFlix::systems.size-1)
|
125
|
+
doc.br
|
126
|
+
doc.br
|
127
|
+
end
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Game info page
|
133
|
+
get "/game/:system/:game" do
|
134
|
+
system = validate_system!(params)
|
135
|
+
game = URI.decode(params[:game])
|
136
|
+
meta = RetroFlix::game_meta_for(system.intern, game)
|
137
|
+
|
138
|
+
layout("#{system}-game") { |doc|
|
139
|
+
doc.h3(:class => "game_title").text game
|
140
|
+
doc.text "for the "
|
141
|
+
doc.a(:href => "/system/#{system}/1").text system
|
142
|
+
doc.div {
|
143
|
+
game_path = "#{system}/#{URI.encode(game)}"
|
144
|
+
play_path = "/play/#{game_path}"
|
145
|
+
delete_path = "/delete/#{game_path}"
|
146
|
+
dl_path = "/download/#{game_path}"
|
147
|
+
|
148
|
+
if RetroFlix.have_game?(system, game)
|
149
|
+
doc.a(:href => "#{play_path}", :class => "play_link").text "[Play]"
|
150
|
+
doc.text " / "
|
151
|
+
doc.a(:href => "#{delete_path}", :class => "delete_link").text "[Delete]"
|
152
|
+
|
153
|
+
else
|
154
|
+
doc.a(:href => "#{dl_path}", :class => "dl_link").text "Download"
|
155
|
+
end
|
156
|
+
}
|
157
|
+
|
158
|
+
meta[:descs].each_with_index { |desc, i|
|
159
|
+
doc.div.text desc
|
160
|
+
doc.hr if i == 0
|
161
|
+
}
|
162
|
+
|
163
|
+
meta[:screens].each { |screen|
|
164
|
+
doc.img(:src => screen)
|
165
|
+
}
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Play specified game
|
170
|
+
get "/play/:system/:game" do
|
171
|
+
system = validate_system!(params)
|
172
|
+
game = URI.decode(params[:game])
|
173
|
+
puts "Playing #{game}"
|
174
|
+
|
175
|
+
RetroFlix::play_game(system.intern, game)
|
176
|
+
|
177
|
+
redirect "/game/#{system}/#{game}"
|
178
|
+
end
|
179
|
+
|
180
|
+
# Play specified game
|
181
|
+
get "/delete/:system/:game" do
|
182
|
+
system = validate_system!(params)
|
183
|
+
game = URI.decode(params[:game])
|
184
|
+
puts "Playing #{game}"
|
185
|
+
|
186
|
+
RetroFlix::delete_game(system.intern, game)
|
187
|
+
|
188
|
+
redirect "/game/#{system}/#{game}"
|
189
|
+
end
|
190
|
+
|
191
|
+
# Download specified game
|
192
|
+
get "/download/:system/:game" do
|
193
|
+
system = validate_system!(params)
|
194
|
+
|
195
|
+
game = URI.decode(params[:game])
|
196
|
+
|
197
|
+
puts "Downloading #{game} for #{system}"
|
198
|
+
|
199
|
+
downloaded = RetroFlix::download(system.intern, game)
|
200
|
+
name, extracted = *RetroFlix.extract_archive(downloaded)
|
201
|
+
RetroFlix.write_game(system, game, name, extracted)
|
202
|
+
|
203
|
+
redirect "/game/#{system}/#{URI.encode(game)}"
|
204
|
+
end
|
data/site.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Sinatra website helpers
|
2
|
+
# Part of RetroFlix
|
3
|
+
|
4
|
+
# Constructs the main layout of the application,
|
5
|
+
# providing callback mechanism for main content area
|
6
|
+
def layout(section, &inner)
|
7
|
+
Nokogiri::HTML::Builder.new { |doc|
|
8
|
+
doc.html {
|
9
|
+
doc.head{
|
10
|
+
doc.title "RetroFlix"
|
11
|
+
|
12
|
+
doc.link(:rel => "stylesheet",
|
13
|
+
:type => "text/css",
|
14
|
+
:href => "/style.css")
|
15
|
+
}
|
16
|
+
|
17
|
+
doc.body {
|
18
|
+
doc.a(:href => "https://github.com/movitto/retroflix") {
|
19
|
+
doc.img(:style => "position: absolute; top: 0; right: 0; border: 0;",
|
20
|
+
:src => "https://camo.githubusercontent.com/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67",
|
21
|
+
:alt => "Fork me on GitHub",
|
22
|
+
:"data-canonical-src" => "https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png")
|
23
|
+
}
|
24
|
+
|
25
|
+
doc.div(:id => "sidebar") {
|
26
|
+
if section == "/"
|
27
|
+
doc.div.bold.text "RF"
|
28
|
+
else
|
29
|
+
doc.div {
|
30
|
+
doc.a(:href => "/").text "RF"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
if section == "library"
|
35
|
+
doc.div.bold.text "My Library"
|
36
|
+
else
|
37
|
+
doc.div {
|
38
|
+
doc.a(:href => "/library").text "My Library"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
RetroFlix::systems.each { |sys|
|
43
|
+
doc.div {
|
44
|
+
if section == sys.to_s
|
45
|
+
doc.div.bold.text sys
|
46
|
+
elsif section == "#{sys}-game"
|
47
|
+
doc.div.bold {
|
48
|
+
doc.a(:href => "/system/#{sys}/1").text sys
|
49
|
+
}
|
50
|
+
else
|
51
|
+
doc.a(:href => "/system/#{sys}/1").text sys
|
52
|
+
end
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
doc.div(:id => "main_content") {
|
58
|
+
inner.call(doc) if inner
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}.to_html
|
63
|
+
end
|
64
|
+
|
65
|
+
# Validates the system param
|
66
|
+
def validate_system!(params)
|
67
|
+
system = params[:system]
|
68
|
+
raise ArgumentError if !RetroFlix.valid_system?(system)
|
69
|
+
system
|
70
|
+
end
|
71
|
+
|
72
|
+
# Validates the num param
|
73
|
+
def validate_num!(params)
|
74
|
+
num = params[:num]
|
75
|
+
Integer(num) # raises ArgumentError if invalid int
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: retroflix
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mo Morsi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyzip
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: curb
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.3
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.9.3
|
69
|
+
description: Retro Game Library Manager
|
70
|
+
email: mo@morsi.org
|
71
|
+
executables:
|
72
|
+
- rf
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- MIT-LICENSE
|
77
|
+
- README.md
|
78
|
+
- bin/rf
|
79
|
+
- lib/archive.rb
|
80
|
+
- lib/conf.rb
|
81
|
+
- lib/deep_struct.rb
|
82
|
+
- lib/emulator.rb
|
83
|
+
- lib/library.rb
|
84
|
+
- lib/scrape.rb
|
85
|
+
- random.rb
|
86
|
+
- retroflix.yml
|
87
|
+
- server.rb
|
88
|
+
- site.rb
|
89
|
+
homepage: http://github.com/movitto/retroflix
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.5.1
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Manage collections of Retro games using an easy web interface, launch them
|
113
|
+
right from the browser!
|
114
|
+
test_files: []
|
115
|
+
has_rdoc:
|