sinatra-torrent 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +0 -1
- data/Readme.md +31 -5
- data/VERSION +1 -1
- data/lib/sinatra/torrent/activerecord.rb +39 -4
- data/lib/sinatra/torrent/hashing.rb +83 -0
- data/lib/sinatra/torrent/helpers.rb +38 -0
- data/lib/sinatra/torrent.rb +29 -31
- data/sinatra-torrent.gemspec +5 -4
- metadata +6 -5
- data/views/torrents_index.haml +0 -5
data/Rakefile
CHANGED
data/Readme.md
CHANGED
@@ -15,13 +15,9 @@ Usage
|
|
15
15
|
|
16
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
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
18
|
### I want options!
|
23
19
|
|
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
|
20
|
+
There needs to be a database of torrents and peers, this is taken care of by a database adapter. Currently I've written (a really basic) one for active record, so many databases are supported as is, but you can write your own for others (eg. mongo). I'm still finding my way around the Sinatra extensions api, so this is how you specify your own ActiveRecord settings:
|
25
21
|
|
26
22
|
require 'sinatra'
|
27
23
|
require 'sinatra/torrent/activerecord'
|
@@ -31,6 +27,36 @@ There needs to be a database of torrents and peers, this is taken care of by a d
|
|
31
27
|
}
|
32
28
|
require 'sinatra/torrent'
|
33
29
|
|
30
|
+
Rake
|
31
|
+
----
|
32
|
+
|
33
|
+
If a torrent takes longer than 1 second to generate on-the-fly, it'll be added to a queue for processing in a background task. If you require a special script in your Rakefile you'll be able to process the queue, add to it or pre-hash all the files in your download directory:
|
34
|
+
|
35
|
+
require 'rake'
|
36
|
+
|
37
|
+
# These need to be the same adaptor and settings as your app, of course!
|
38
|
+
require 'sinatra/torrent/activerecord'
|
39
|
+
Sinatra::Torrent::Database.settings = {
|
40
|
+
'adapter' => 'sqlite3',
|
41
|
+
'database' => 'torrents.db'
|
42
|
+
}
|
43
|
+
require 'sinatra/torrent/hashing'
|
44
|
+
|
45
|
+
# This line is optional, 'downloads' is the default
|
46
|
+
# If you've used `set :download_directory, 'files'` in your sinatra app, you need to do:
|
47
|
+
Sinatra::Torrent::DOWNLOAD_DIRECTORY = 'files'
|
48
|
+
|
49
|
+
# The rest of your Rakefile
|
50
|
+
|
51
|
+
* `rake hash:add filename.ext` will add `filename.ext` inside your download directory to the hash queue
|
52
|
+
* `rake hash:queue` will process all the hash jobs in the queue (you may want to set up a cron job to run this)
|
53
|
+
* `rake hash:all` will hash all files in the downloads directory right now, so none will be processed on-the-fly when the .torrent file is downloaded
|
54
|
+
|
55
|
+
To Do
|
56
|
+
-----
|
57
|
+
|
58
|
+
* Execute a user-writen block of code when an on-the-fly torrent creation times out. This will allow people to trigger the background rake task immediately, rather than waiting on cron.
|
59
|
+
|
34
60
|
Ummmm
|
35
61
|
-----
|
36
62
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
@@ -32,7 +32,11 @@ module Sinatra
|
|
32
32
|
end
|
33
33
|
|
34
34
|
add_index :torrents, :infohash, :unique => true
|
35
|
+
end
|
36
|
+
end
|
35
37
|
|
38
|
+
unless Peer.table_exists?
|
39
|
+
ActiveRecord::Schema.define do
|
36
40
|
create_table :peers do |table|
|
37
41
|
table.string :torrent_infohash, :null => false
|
38
42
|
table.string :peer_id
|
@@ -45,7 +49,19 @@ module Sinatra
|
|
45
49
|
table.timestamps
|
46
50
|
end
|
47
51
|
|
48
|
-
add_index :peers, [:peer_id,:torrent_infohash]
|
52
|
+
add_index :peers, [:peer_id,:torrent_infohash], :unique => true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
unless DelayedJob.table_exists?
|
57
|
+
ActiveRecord::Schema.define do
|
58
|
+
create_table :delayed_jobs do |table|
|
59
|
+
table.string :filename, :null => false
|
60
|
+
|
61
|
+
table.timestamp :created_at
|
62
|
+
end
|
63
|
+
|
64
|
+
add_index :delayed_jobs, :filename, :unique => true
|
49
65
|
end
|
50
66
|
end
|
51
67
|
end
|
@@ -129,15 +145,34 @@ module Sinatra
|
|
129
145
|
|
130
146
|
peer.save
|
131
147
|
end
|
148
|
+
|
149
|
+
|
150
|
+
# Registers a file to be hashed offline
|
151
|
+
def add_hashjob(filename)
|
152
|
+
DelayedJob.find_or_create_by_filename(filename)
|
153
|
+
# return eta in seconds, where possible
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
# Lists outstanding files to be hashed, by request date hopefully!
|
158
|
+
def list_hashjobs
|
159
|
+
DelayedJob.find(:all,:order => 'created_at DESC').collect{|dj| dj.filename}
|
160
|
+
end
|
161
|
+
|
162
|
+
# Removes a file from the 'to be hashed' list
|
163
|
+
def remove_hashjob(filename)
|
164
|
+
DelayedJob.find_by_filename(filename).delete rescue false
|
165
|
+
end
|
132
166
|
|
133
167
|
private
|
168
|
+
|
134
169
|
class Torrent < ActiveRecord::Base
|
135
170
|
serialize :metadata, Hash
|
136
171
|
end
|
137
172
|
|
138
|
-
class Peer < ActiveRecord::Base
|
139
|
-
|
140
|
-
end
|
173
|
+
class Peer < ActiveRecord::Base; end
|
174
|
+
|
175
|
+
class DelayedJob < ActiveRecord::Base; end
|
141
176
|
end
|
142
177
|
end
|
143
178
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'sinatra/torrent/helpers'
|
2
|
+
|
3
|
+
namespace :hash do
|
4
|
+
begin
|
5
|
+
torrent_db = Sinatra::Torrent::Database.new
|
6
|
+
rescue NameError
|
7
|
+
raise RuntimeError, "Please require a sinatra-torrent database adapter in your rakefile."
|
8
|
+
end
|
9
|
+
|
10
|
+
task :add do
|
11
|
+
ARGV[1..-1].each do |rel_location|
|
12
|
+
if (File.exists?(File.join(Sinatra::Torrent::DOWNLOADS_DIRECTORY,rel_location)))
|
13
|
+
torrent_db.add_hashjob(rel_location)
|
14
|
+
$stdout.puts "added to queue: #{rel_location}"
|
15
|
+
else
|
16
|
+
$stderr.puts "#{rel_location} doesn't exist in the downloads directory"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
task :all do
|
22
|
+
completed = 0
|
23
|
+
failed = 0
|
24
|
+
|
25
|
+
Dir[File.join(Sinatra::Torrent::DOWNLOADS_DIRECTORY,'**')].each do |filename|
|
26
|
+
rel_location = filename[Sinatra::Torrent::DOWNLOADS_DIRECTORY.length+1..-1]
|
27
|
+
|
28
|
+
begin
|
29
|
+
if !torrent_db.torrent_by_path_and_timestamp(rel_location,File.mtime(filename))
|
30
|
+
d = Sinatra::Torrent.create(filename)
|
31
|
+
|
32
|
+
torrent_db.store_torrent(rel_location,File.mtime(filename),d['metadata'],d['infohash'])
|
33
|
+
|
34
|
+
torrent_db.remove_hashjob(rel_location)
|
35
|
+
$stdout.puts "Hashed: #{rel_location}"
|
36
|
+
|
37
|
+
completed += 1
|
38
|
+
else
|
39
|
+
$stdout.puts "Already hashed: #{rel_location}"
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
$stderr.puts "Not hashed: #{rel_location} (Unknown error)"
|
43
|
+
failed += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
$stdout.puts "#{completed} hash job#{(completed == 1) ? '' : 's'} completed sucessfully"
|
48
|
+
$stderr.puts "#{failed} failed!" unless failed == 0
|
49
|
+
end
|
50
|
+
|
51
|
+
task :queue do
|
52
|
+
completed = 0
|
53
|
+
failed = 0
|
54
|
+
|
55
|
+
torrent_db.list_hashjobs.each do |rel_location|
|
56
|
+
filename = File.join(Sinatra::Torrent::DOWNLOADS_DIRECTORY,rel_location)
|
57
|
+
begin
|
58
|
+
if torrent_db.torrent_by_path_and_timestamp(rel_location,File.mtime(filename))
|
59
|
+
torrent_db.remove_hashjob(rel_location)
|
60
|
+
else
|
61
|
+
d = Sinatra::Torrent.create(filename)
|
62
|
+
|
63
|
+
torrent_db.store_torrent(rel_location,File.mtime(filename),d['metadata'],d['infohash'])
|
64
|
+
|
65
|
+
completed += 1
|
66
|
+
torrent_db.remove_hashjob(rel_location)
|
67
|
+
end
|
68
|
+
rescue Errno::ENOENT
|
69
|
+
$stderr.puts "#{rel_location} no longer exists to be hashed. Removing job."
|
70
|
+
torrent_db.remove_hashjob(rel_location)
|
71
|
+
|
72
|
+
failed += 1
|
73
|
+
rescue
|
74
|
+
$stderr.puts "Uncaught failure reason for #{rel_location}"
|
75
|
+
# TODO: trace?
|
76
|
+
failed += 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
$stdout.puts "#{completed} queued hash job#{(completed == 1) ? '' : 's'} completed sucessfully"
|
81
|
+
$stderr.puts "#{failed} failed!" unless failed == 0
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'bencode'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module Torrent
|
7
|
+
DOWNLOADS_DIRECTORY = 'downloads'
|
8
|
+
|
9
|
+
def self.create(filename)
|
10
|
+
d = {
|
11
|
+
'metadata' => {
|
12
|
+
'created by' => "sinatra-torrent (#{File.read(File.expand_path(File.join(__FILE__,'..','..','..','..','VERSION'))).strip}) (http://github.com/jphastings/sinatra-torrent)",
|
13
|
+
'creation date' => Time.now.to_i,
|
14
|
+
'info' => {
|
15
|
+
'name' => File.basename(filename),
|
16
|
+
'length' => File.size(filename),
|
17
|
+
'piece length' => 2**10, # TODO: Choose reasonable piece size
|
18
|
+
'pieces' => ''
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
begin
|
24
|
+
file = open(filename,'r')
|
25
|
+
|
26
|
+
begin
|
27
|
+
d['metadata']['info']['pieces'] += Digest::SHA1.digest(file.read(d['metadata']['info']['piece length']))
|
28
|
+
end until file.eof?
|
29
|
+
ensure
|
30
|
+
file.close
|
31
|
+
end
|
32
|
+
|
33
|
+
d['infohash'] = Digest::SHA1.hexdigest(d['metadata']['info'].bencode)
|
34
|
+
|
35
|
+
return d
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/sinatra/torrent.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'timeout'
|
2
|
-
require 'time'
|
3
|
-
require 'digest/sha1'
|
4
|
-
require 'bencode'
|
5
2
|
require 'sinatra/base'
|
3
|
+
require 'sinatra/torrent/helpers'
|
6
4
|
|
7
5
|
# This extension will serve up the contents of the specified folder as web seeded torrents.
|
8
6
|
# Both webseed versions are supported (shad0w's and GetRight's) and there is an inbuilt tracker
|
@@ -16,7 +14,7 @@ module Sinatra
|
|
16
14
|
# Putting the annouce URL of a tracker in here will use that tracker rather than the inbuilt one
|
17
15
|
app.set :external_tracker, nil
|
18
16
|
# Directory which holds all the files which will be provided as torrents
|
19
|
-
app.set :downloads_directory, File.dirname(__FILE__)
|
17
|
+
app.set :downloads_directory, File.join(File.dirname(__FILE__),Sinatra::Torrent::DOWNLOADS_DIRECTORY)
|
20
18
|
# Mount point for the downloads directory
|
21
19
|
app.set :downloads_mount, 'downloads'
|
22
20
|
# Mount point for the torrents directory
|
@@ -41,38 +39,31 @@ module Sinatra
|
|
41
39
|
filename = File.join(options.downloads_directory, rel_location)
|
42
40
|
halt(404, "That file doesn't exist! #{filename}") unless File.exists?(filename)
|
43
41
|
|
44
|
-
if
|
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
|
-
}
|
42
|
+
if !(d = options.database_adapter.torrent_by_path_and_timestamp(filename,File.mtime(filename)))
|
59
43
|
|
60
44
|
begin
|
61
|
-
file = open(filename,'r')
|
62
|
-
|
63
45
|
Timeout::timeout(1) do
|
64
|
-
|
65
|
-
d['metadata']['info']['pieces'] += Digest::SHA1.digest(file.read(d['metadata']['info']['piece length']))
|
66
|
-
end until file.eof?
|
46
|
+
d = Sinatra::Torrent.create(filename)
|
67
47
|
end
|
68
48
|
rescue Timeout::Error
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
49
|
+
eta = options.database_adapter.add_hashjob(filename)
|
50
|
+
|
51
|
+
begin
|
52
|
+
wait = case (eta/60).floor
|
53
|
+
when 0
|
54
|
+
'under a minute'
|
55
|
+
when 1
|
56
|
+
'about a minute'
|
57
|
+
else
|
58
|
+
"about #{(eta/60).floor} minutes"
|
59
|
+
end
|
60
|
+
rescue NoMethodError
|
61
|
+
wait = "a short while"
|
62
|
+
end
|
63
|
+
|
64
|
+
halt(503,"This torrent is taking too long to build, we're running it in the background. Please try again in #{wait}.")
|
73
65
|
end
|
74
66
|
|
75
|
-
d['infohash'] = Digest::SHA1.hexdigest(d['metadata']['info'].bencode)
|
76
67
|
options.database_adapter.store_torrent(filename,File.mtime(filename),d['metadata'],d['infohash'])
|
77
68
|
end
|
78
69
|
|
@@ -128,8 +119,15 @@ module Sinatra
|
|
128
119
|
end
|
129
120
|
|
130
121
|
# INDEX PAGE
|
122
|
+
|
123
|
+
# TODO: Have a 'fallback' index view?
|
131
124
|
app.get "/#{app.options.torrents_mount}/" do
|
132
|
-
|
125
|
+
locals = {:torrents => (Dir.glob("#{options.downloads_directory}/**").collect {|f| f[options.downloads_directory.length+1..-1] } rescue [])}
|
126
|
+
begin
|
127
|
+
haml :torrents_index,:locals => locals
|
128
|
+
rescue Errno::ENOENT
|
129
|
+
"<ul>"<<locals[:torrents].collect{|t| "<li><a href=\"/#{options.torrents_mount}/#{t}.torrent\">#{t}</a></li>" }.join<<"</ul>"
|
130
|
+
end
|
133
131
|
end
|
134
132
|
|
135
133
|
# DATA
|
@@ -158,4 +156,4 @@ module Sinatra
|
|
158
156
|
end
|
159
157
|
|
160
158
|
register Torrent
|
161
|
-
end
|
159
|
+
end
|
data/sinatra-torrent.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{sinatra-torrent}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["JP Hastings-Spital"]
|
12
|
-
s.date = %q{2011-02-
|
12
|
+
s.date = %q{2011-02-09}
|
13
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
14
|
s.email = %q{jphastings@gmail.com}
|
15
15
|
s.files = [
|
@@ -18,8 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
"VERSION",
|
19
19
|
"lib/sinatra/torrent.rb",
|
20
20
|
"lib/sinatra/torrent/activerecord.rb",
|
21
|
-
"sinatra
|
22
|
-
"
|
21
|
+
"lib/sinatra/torrent/hashing.rb",
|
22
|
+
"lib/sinatra/torrent/helpers.rb",
|
23
|
+
"sinatra-torrent.gemspec"
|
23
24
|
]
|
24
25
|
s.homepage = %q{http://github.com/jphastings/sinatra-torrent}
|
25
26
|
s.rdoc_options = ["--charset=UTF-8"]
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-torrent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- JP Hastings-Spital
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-09 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -48,8 +48,9 @@ files:
|
|
48
48
|
- VERSION
|
49
49
|
- lib/sinatra/torrent.rb
|
50
50
|
- lib/sinatra/torrent/activerecord.rb
|
51
|
+
- lib/sinatra/torrent/hashing.rb
|
52
|
+
- lib/sinatra/torrent/helpers.rb
|
51
53
|
- sinatra-torrent.gemspec
|
52
|
-
- views/torrents_index.haml
|
53
54
|
has_rdoc: true
|
54
55
|
homepage: http://github.com/jphastings/sinatra-torrent
|
55
56
|
licenses: []
|