sinatra-torrent 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +43 -0
- data/Readme.md +37 -0
- data/VERSION +1 -0
- data/lib/sinatra/torrent.rb +161 -0
- data/lib/sinatra/torrent/activerecord.rb +143 -0
- data/sinatra-torrent.gemspec +43 -0
- data/views/torrents_index.haml +5 -0
- metadata +88 -0
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "sinatra-torrent"
|
8
|
+
gem.description = "An extension to Sinatra which will allow you to run a webseeded torrent tracker of files in the folder you specify."
|
9
|
+
gem.summary = "A sinatra extension to run webseeded torrent tracker"
|
10
|
+
gem.email = "jphastings@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/jphastings/sinatra-torrent"
|
12
|
+
gem.authors = ["JP Hastings-Spital"]
|
13
|
+
|
14
|
+
gem.add_dependency('sinatra','>=1.1.2') # Required for send_file modifications
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
data/Readme.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Sinatra-Torrent
|
2
|
+
===============
|
3
|
+
|
4
|
+
There was [a /. article](http://ask.slashdot.org/story/10/10/04/0035231) about BitTorrent replacing standard downloads and I thought: Yes.
|
5
|
+
|
6
|
+
Ruby doesn't appear to like BitTorrent very much, most libraries are pretty old and I figured I'd spruce up my favourite jazz legend themed DSL with a library to make serving torrents *ridiculously* easy.
|
7
|
+
|
8
|
+
Usage
|
9
|
+
-----
|
10
|
+
|
11
|
+
require 'sinatra'
|
12
|
+
require 'sinatra/torrent'
|
13
|
+
|
14
|
+
"Woah, that's pretty simple!" I hear you say. Why yes, I think it is.
|
15
|
+
|
16
|
+
All files you put in the `downloads` directory at the root of your sinatra app will be downloadable at `/downloads/your_file.ext` and it's torrent will be dynamically generated (and cached) at `/torrents/your_file.ext.torrent`. You will have trouble with larger files as it currently hashes as part of the request first time round. I'm planning on pushing this out to workers at some point. Not yet sure how I'm going to do that…
|
17
|
+
|
18
|
+
**NB.** Files that take longer than 1s to hash will fail at the moment!
|
19
|
+
|
20
|
+
The extension is in it's early stages at the moment, so many of the settings aren't adhered to, and there are some issues with the webseeding… however it *does* work.
|
21
|
+
|
22
|
+
### I want options!
|
23
|
+
|
24
|
+
There needs to be a database of torrents and peers, this is taken care of by a database adapter. Currently I've written (a breally basic) one for active record, so many databases are supported. I'm still finding my way around the Sinatra extensions api, so this is how you specify your own ActiveRecord settings:
|
25
|
+
|
26
|
+
require 'sinatra'
|
27
|
+
require 'sinatra/torrent/activerecord'
|
28
|
+
SinatraTorrentDatabase.settings = {
|
29
|
+
'adapater' => 'sqlite3',
|
30
|
+
'database' => 'torrents.db'
|
31
|
+
}
|
32
|
+
require 'sinatra/torrent'
|
33
|
+
|
34
|
+
Ummmm
|
35
|
+
-----
|
36
|
+
|
37
|
+
That's it for now. If you have any feed back - get in touch! You can use [twitter](http://twitter.com/jphastings), [github issues](http://github.com/jphastings/sinatra-torrent/issues) or any other medium you can think of.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'time'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'bencode'
|
5
|
+
require 'sinatra/base'
|
6
|
+
|
7
|
+
# This extension will serve up the contents of the specified folder as web seeded torrents.
|
8
|
+
# Both webseed versions are supported (shad0w's and GetRight's) and there is an inbuilt tracker
|
9
|
+
# with a modicum of intelligence, though an external tracker can be used if desired.
|
10
|
+
|
11
|
+
module Sinatra
|
12
|
+
module Torrent
|
13
|
+
|
14
|
+
# Options etc
|
15
|
+
def self.registered(app)
|
16
|
+
# Putting the annouce URL of a tracker in here will use that tracker rather than the inbuilt one
|
17
|
+
app.set :external_tracker, nil
|
18
|
+
# Directory which holds all the files which will be provided as torrents
|
19
|
+
app.set :downloads_directory, File.dirname(__FILE__)+'downloads'
|
20
|
+
# Mount point for the downloads directory
|
21
|
+
app.set :downloads_mount, 'downloads'
|
22
|
+
# Mount point for the torrents directory
|
23
|
+
app.set :torrents_mount, 'torrents'
|
24
|
+
# Load up a database adapter if one isn't already loaded
|
25
|
+
require 'sinatra/torrent/activerecord' unless (Sinatra::Torrent.const_defined?('Database') rescue false)
|
26
|
+
# Stores the instance of the database used to store tracker info.
|
27
|
+
app.set :database_adapter, Sinatra::Torrent::Database.new
|
28
|
+
# The comment added into torrents
|
29
|
+
app.set :torrent_comment, ''
|
30
|
+
# Do we wish to track external torrents too? (untested)
|
31
|
+
app.set :allow_external_torrents, false
|
32
|
+
# The frequency with which we ask trackers to announce themselves. Once every x seconds
|
33
|
+
app.set :announce_frequency, 30
|
34
|
+
|
35
|
+
# TORRENTS
|
36
|
+
|
37
|
+
app.mime_type :torrent, 'application/x-bittorrent'
|
38
|
+
|
39
|
+
# Serves up the torrents with appropriate announce URL
|
40
|
+
app.get Regexp.new("^/#{app.options.torrents_mount}/(.+)\.torrent$") do |rel_location|
|
41
|
+
filename = File.join(options.downloads_directory, rel_location)
|
42
|
+
halt(404, "That file doesn't exist! #{filename}") unless File.exists?(filename)
|
43
|
+
|
44
|
+
if true #!(d = options.database_adapter.torrent_by_path_and_timestamp(filename,File.mtime(filename)))
|
45
|
+
|
46
|
+
d = {
|
47
|
+
'metadata' => {
|
48
|
+
# TODO: Version?
|
49
|
+
'created by' => 'sinatra-torrent (0.0.1) (http://github.com/jphastings/sinatra-torrent)',
|
50
|
+
'creation date' => Time.now.to_i,
|
51
|
+
'info' => {
|
52
|
+
'name' => File.basename(rel_location),
|
53
|
+
'length' => File.size(filename),
|
54
|
+
'piece length' => 2**10, # TODO: Choose reasonable piece size
|
55
|
+
'pieces' => ''
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
begin
|
61
|
+
file = open(filename,'r')
|
62
|
+
|
63
|
+
Timeout::timeout(1) do
|
64
|
+
begin
|
65
|
+
d['metadata']['info']['pieces'] += Digest::SHA1.digest(file.read(d['metadata']['info']['piece length']))
|
66
|
+
end until file.eof?
|
67
|
+
end
|
68
|
+
rescue Timeout::Error
|
69
|
+
# TODO: Actually run it in the background!
|
70
|
+
halt(503,"This torrent is taking too long to build, we're [not currently supporting!] running it in the background. Please try again in a few minutes.")
|
71
|
+
ensure
|
72
|
+
file.close
|
73
|
+
end
|
74
|
+
|
75
|
+
d['infohash'] = Digest::SHA1.hexdigest(d['metadata']['info'].bencode)
|
76
|
+
options.database_adapter.store_torrent(filename,File.mtime(filename),d['metadata'],d['infohash'])
|
77
|
+
end
|
78
|
+
|
79
|
+
# These are options which could change between database retrievals
|
80
|
+
d['metadata'].merge!({
|
81
|
+
'httpseeds' => [File.join('http://'+env['HTTP_HOST'],URI.encode(options.torrents_mount),'webseed')],
|
82
|
+
'url-list' => [File.join('http://'+env['HTTP_HOST'],URI.encode(options.downloads_mount),URI.encode(rel_location)+'?'+d['infohash'])],
|
83
|
+
'announce' => options.external_tracker || File.join('http://'+env['HTTP_HOST'],URI.encode(options.torrents_mount),'announce'),
|
84
|
+
'comment' => options.torrent_comment,
|
85
|
+
})
|
86
|
+
|
87
|
+
content_type :torrent, :charset => 'utf-8'
|
88
|
+
d['metadata'].bencode
|
89
|
+
end
|
90
|
+
|
91
|
+
# TRACKER
|
92
|
+
|
93
|
+
# Tracker announce mount point
|
94
|
+
app.get "/#{app.options.torrents_mount}/announce" do
|
95
|
+
# Convert to a hex info_hash if required TODO: Is it required?
|
96
|
+
params['info_hash'] = Digest.hexencode(params['info_hash'] || '')
|
97
|
+
halt(400,"A valid info-hash was not given") if params['info_hash'].match(/^[0-9a-f]{40}$/).nil?
|
98
|
+
info = options.database_adapter.torrent_info(params['info_hash'])
|
99
|
+
|
100
|
+
if (!options.allow_external_torrents and !options.database_adapter.torrent_by_infohash(params['info_hash']))
|
101
|
+
return {
|
102
|
+
'failure reason' => 'This tracker does not track that torrent'
|
103
|
+
}.bencode
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO: Validation
|
107
|
+
|
108
|
+
params['ip'] ||= env['REMOTE_ADDR']
|
109
|
+
|
110
|
+
# Errmmm - HACK!
|
111
|
+
params['peer_id'] = params['peer_id'].force_encoding("ISO-8859-1")
|
112
|
+
|
113
|
+
# Registers this peer's announcement
|
114
|
+
options.database_adapter.announce(params)
|
115
|
+
|
116
|
+
{
|
117
|
+
'interval' => options.announce_frequency,
|
118
|
+
#'tracker id' => 'bleugh', # TODO: Keep this?
|
119
|
+
'complete' => info['complete'],
|
120
|
+
'incomplete' => info['incomplete'],
|
121
|
+
'peers' => options.database_adapter.peers_by_infohash(params['info_hash'],[params['peer_id']],(params['numwant'] || 50).to_i),
|
122
|
+
}.bencode
|
123
|
+
end
|
124
|
+
|
125
|
+
# TODO: Scrape
|
126
|
+
app.get '/torrents/scrape' do
|
127
|
+
# TODO: Make it work!
|
128
|
+
end
|
129
|
+
|
130
|
+
# INDEX PAGE
|
131
|
+
app.get "/#{app.options.torrents_mount}/" do
|
132
|
+
haml :torrents_index,:locals => {:torrents => Dir.glob("#{options.downloads_directory}/**").collect {|f| f[options.downloads_directory.length+1..-1] } }
|
133
|
+
end
|
134
|
+
|
135
|
+
# DATA
|
136
|
+
|
137
|
+
# BitTornado WebSeeding manager
|
138
|
+
app.get "/#{app.options.torrents_mount}/webseed" do
|
139
|
+
# Which file is the client looking for?
|
140
|
+
halt(404, "Torrent not tracked") unless (options.database_adapter.torrent_by_infohash(params[:infohash]))
|
141
|
+
|
142
|
+
# http://bittornado.com/docs/webseed-spec.txt
|
143
|
+
|
144
|
+
# TODO: intelligent wait period
|
145
|
+
halt(503,"15") if false # ask clients to wait 15 seconds before requesting again
|
146
|
+
end
|
147
|
+
|
148
|
+
# Provides the files for web download. Any query parameters are treated as a checksum for the file (via the torrent infohash)
|
149
|
+
app.get "/#{app.options.downloads_mount}/:filename" do
|
150
|
+
filename = File.join(options.downloads_directory,File.expand_path('/'+params[:filename]))
|
151
|
+
halt(404) unless File.exists?(filename)
|
152
|
+
|
153
|
+
# If there are query params then we assume it's specifying a specific version of the file by info_hash
|
154
|
+
halt(409,"The file is no longer the same as the one specified in your torrent") if !env['QUERY_STRING'].empty? and (options.database_adapter.torrent_by_path_and_timestamp(filename,File.mtime(filename))['infohash'] rescue nil) != env['QUERY_STRING']
|
155
|
+
send_file(filename)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
register Torrent
|
161
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# The active record adapter works with SQLite3 in memory by default
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module Torrent
|
6
|
+
# This is the wrapper class used by sinatra-torrent to communicate with
|
7
|
+
# any database system
|
8
|
+
class Database
|
9
|
+
# Default settings for the connection
|
10
|
+
@@settings = {
|
11
|
+
'adapter' => 'sqlite3',
|
12
|
+
'database' => ':memory:'
|
13
|
+
}
|
14
|
+
|
15
|
+
# Allows the set up of Active Record
|
16
|
+
def self.settings=(settings)
|
17
|
+
raise ArgumentError if !settings.is_a?(Hash)
|
18
|
+
@@settings = settings
|
19
|
+
end
|
20
|
+
|
21
|
+
# Makes sure the table is present & ready, log in using the settings
|
22
|
+
def initialize
|
23
|
+
@db = ActiveRecord::Base.establish_connection(@@settings)
|
24
|
+
|
25
|
+
unless Torrent.table_exists?
|
26
|
+
ActiveRecord::Schema.define do
|
27
|
+
create_table :torrents do |table|
|
28
|
+
table.text :path, :null => false
|
29
|
+
table.text :metadata, :null => false
|
30
|
+
table.string :infohash, :null => false
|
31
|
+
table.timestamp :timestamp, :null => false
|
32
|
+
end
|
33
|
+
|
34
|
+
add_index :torrents, :infohash, :unique => true
|
35
|
+
|
36
|
+
create_table :peers do |table|
|
37
|
+
table.string :torrent_infohash, :null => false
|
38
|
+
table.string :peer_id
|
39
|
+
table.integer :port
|
40
|
+
table.integer :uploaded
|
41
|
+
table.integer :downloaded
|
42
|
+
table.integer :left
|
43
|
+
table.string :ip
|
44
|
+
|
45
|
+
table.timestamps
|
46
|
+
end
|
47
|
+
|
48
|
+
add_index :peers, [:peer_id,:torrent_infohash],:unique => true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Stores a torrent in the database
|
54
|
+
def store_torrent(path,timestamp,metadata,infohash)
|
55
|
+
# Make sure this path and this infohash don't already exist
|
56
|
+
Torrent.find_by_path_and_timestamp(path,timestamp).delete rescue nil
|
57
|
+
Torrent.find_by_infohash(infohash).delete rescue nil
|
58
|
+
|
59
|
+
Torrent.new(
|
60
|
+
:path => path,
|
61
|
+
:metadata => metadata,
|
62
|
+
:infohash => infohash,
|
63
|
+
:timestamp => timestamp
|
64
|
+
).save
|
65
|
+
end
|
66
|
+
|
67
|
+
# Find a torrent by infohash
|
68
|
+
def torrent_by_infohash(infohash)
|
69
|
+
torrent = Torrent.find_by_infohash(infohash)
|
70
|
+
return false if torrent.nil?
|
71
|
+
|
72
|
+
{
|
73
|
+
'metadata' => torrent.metadata,
|
74
|
+
'infohash' => torrent.infohash,
|
75
|
+
'path' => torrent.path,
|
76
|
+
'timestamp'=> torrent.timestamp
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Finds a torrent by
|
81
|
+
def torrent_by_path_and_timestamp(path,timestamp)
|
82
|
+
torrent = Torrent.find_by_path_and_timestamp(path,timestamp)
|
83
|
+
return false if torrent.nil?
|
84
|
+
|
85
|
+
{
|
86
|
+
'metadata' => torrent.metadata,
|
87
|
+
'infohash' => torrent.infohash,
|
88
|
+
'path' => torrent.path,
|
89
|
+
'timestamp'=> torrent.timestamp
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Lists the currently registered peers for a given torrent
|
94
|
+
# if peer_ids is populated with any peer ids then they will be excluded from the list
|
95
|
+
def peers_by_infohash(infohash, peer_ids = [], peers = 50)
|
96
|
+
begin
|
97
|
+
# TODO: Random order & actual number of peers (if peer_ids is in returned amount)
|
98
|
+
Peer.find_by_torrent_infohash(infohash,:limit => peers).delete_if {|peer| peer_ids.include? peer.peer_id}.map do |peer|
|
99
|
+
{
|
100
|
+
'peer id' => peer.peer_id,
|
101
|
+
'ip' => peer.ip,
|
102
|
+
'port' => peer.port
|
103
|
+
}
|
104
|
+
end
|
105
|
+
rescue NoMethodError
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns information about the torrent as provided by it's infohash
|
111
|
+
def torrent_info(infohash)
|
112
|
+
info = Peer.find_by_sql(["SELECT (SELECT COUNT(*) FROM 'peers' WHERE `left` != 0 AND `torrent_infohash` = ?) as `incomplete`, (SELECT COUNT(*) FROM 'peers' WHERE `left` == 0 AND `torrent_infohash` = ?) as `complete`",infohash, infohash])[0]
|
113
|
+
|
114
|
+
{
|
115
|
+
'complete' => info.complete,
|
116
|
+
'incomplete' => info.incomplete
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Announce!
|
121
|
+
def announce(params)
|
122
|
+
peer = Peer.find_or_create_by_torrent_infohash_and_peer_id(params['info_hash'],params['peer_id'])
|
123
|
+
|
124
|
+
peer.ip ||= params['ip']
|
125
|
+
peer.port ||= params['port']
|
126
|
+
peer.uploaded = params['uploaded']
|
127
|
+
peer.downloaded = params['downloaded']
|
128
|
+
peer.left = params['left']
|
129
|
+
|
130
|
+
peer.save
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
class Torrent < ActiveRecord::Base
|
135
|
+
serialize :metadata, Hash
|
136
|
+
end
|
137
|
+
|
138
|
+
class Peer < ActiveRecord::Base
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{sinatra-torrent}
|
8
|
+
s.version = "0.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["JP Hastings-Spital"]
|
12
|
+
s.date = %q{2011-02-08}
|
13
|
+
s.description = %q{An extension to Sinatra which will allow you to run a webseeded torrent tracker of files in the folder you specify.}
|
14
|
+
s.email = %q{jphastings@gmail.com}
|
15
|
+
s.files = [
|
16
|
+
"Rakefile",
|
17
|
+
"Readme.md",
|
18
|
+
"VERSION",
|
19
|
+
"lib/sinatra/torrent.rb",
|
20
|
+
"lib/sinatra/torrent/activerecord.rb",
|
21
|
+
"sinatra-torrent.gemspec",
|
22
|
+
"views/torrents_index.haml"
|
23
|
+
]
|
24
|
+
s.homepage = %q{http://github.com/jphastings/sinatra-torrent}
|
25
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.rubygems_version = %q{1.3.7}
|
28
|
+
s.summary = %q{A sinatra extension to run webseeded torrent tracker}
|
29
|
+
|
30
|
+
if s.respond_to? :specification_version then
|
31
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
32
|
+
s.specification_version = 3
|
33
|
+
|
34
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
35
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 1.1.2"])
|
36
|
+
else
|
37
|
+
s.add_dependency(%q<sinatra>, [">= 1.1.2"])
|
38
|
+
end
|
39
|
+
else
|
40
|
+
s.add_dependency(%q<sinatra>, [">= 1.1.2"])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-torrent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- JP Hastings-Spital
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-08 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sinatra
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
- 2
|
34
|
+
version: 1.1.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: An extension to Sinatra which will allow you to run a webseeded torrent tracker of files in the folder you specify.
|
38
|
+
email: jphastings@gmail.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- Rakefile
|
47
|
+
- Readme.md
|
48
|
+
- VERSION
|
49
|
+
- lib/sinatra/torrent.rb
|
50
|
+
- lib/sinatra/torrent/activerecord.rb
|
51
|
+
- sinatra-torrent.gemspec
|
52
|
+
- views/torrents_index.haml
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/jphastings/sinatra-torrent
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.7
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A sinatra extension to run webseeded torrent tracker
|
87
|
+
test_files: []
|
88
|
+
|