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