ll-mktorrent 1.8.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b226f9099a7a0c1a43add68ff6e730b3229c1fb1
4
+ data.tar.gz: e3c5cb0daf276d661b6f57a044dd31c6a557268a
5
+ SHA512:
6
+ metadata.gz: 105583b686255b9f09ea34c62f88b2be042fa61da03ff62bce5622d5a2df2fcdbd2007909af8b78763d482dcb93227fde2d9ed628d96fbfe0d5f3f869fd2c67d
7
+ data.tar.gz: c75b506d907354b9123180150d41b01ad36f268cecbd0443a9ba2695b8a6e8101d060b5678b15a84906865c3b20fd6c2732ce9d1b2f5cc98c37b56ebb48650f4
data/lib/mktorrent.rb ADDED
@@ -0,0 +1,266 @@
1
+ require 'bencode'
2
+ require 'digest/sha1'
3
+ require 'date'
4
+ require 'uri'
5
+
6
+ # Sample usage
7
+ #t = Torrent.new("http://your.tracker.com")
8
+ #t.add_file("path/to/file.foo")
9
+ #t.write_torrent("~/Downloads/mytorrent.torrent")
10
+
11
+ class Torrent
12
+ include Comparable
13
+
14
+ attr_reader :torrent_file, :infohash
15
+ attr_accessor :info, :filehashes, :piecelength, :files, :defaultdir, :tracker, :size, :privacy, :webseed, :tracker_list, :pieces_from_file
16
+ attr_writer :creation_date, :from_file
17
+
18
+ # optionally initialize filename
19
+ def initialize(tracker, from_file = false)
20
+ @tracker = tracker
21
+ @piecelength = 512 * 1024 # 512 KB
22
+ @files = []
23
+ @filehashes = []
24
+ @size = 0
25
+ @tracker_list = [ [@tracker] ]
26
+ @defaultdir = "torrent"
27
+ @privacy = 0
28
+ @webseed = ""
29
+ @dirbase = ""
30
+ @from_file = from_file
31
+ build_the_torrent
32
+ yield(self) if block_given?
33
+ end
34
+
35
+ def all_files
36
+ if @files.any?
37
+ @files.collect { |file| file[:path] }
38
+ end
39
+ end
40
+
41
+ def from_file?
42
+ !!@from_file
43
+ end
44
+
45
+ def count
46
+ @files.count
47
+ end
48
+
49
+ def creation_date
50
+ @creation_date ||= DateTime.now.strftime("%s")
51
+ end
52
+
53
+ def path_for_reading_pieces(f)
54
+ if @dirbase.empty? # it's a single file torrent
55
+ f = File.join(File.join(f))
56
+ end
57
+ f = File.join(@dirbase, f) unless @dirbase.empty?
58
+ f
59
+ end
60
+
61
+ def read_pieces(files, length)
62
+ buffer = ""
63
+ files.each do |file|
64
+ f = path_for_reading_pieces(file)
65
+ next if File.directory?(f)
66
+ File.open(f) do |fh|
67
+ begin
68
+ read = fh.read(length - buffer.length)
69
+
70
+ # Make sure file not empty
71
+ unless read.nil?
72
+ if (buffer.length + read.length) == length
73
+ yield(buffer + read)
74
+ buffer = ""
75
+ else
76
+ buffer += read
77
+ end
78
+ end
79
+ end until fh.eof?
80
+ end
81
+ end
82
+
83
+ yield buffer
84
+ end
85
+
86
+ def build
87
+ @info = {
88
+ :announce => @tracker,
89
+ :'announce-list' => @tracker_list,
90
+ :'creation date' => creation_date,
91
+ :info => {
92
+ :name => @defaultdir,
93
+ :'piece length' => @piecelength,
94
+ :files => @files,
95
+ :private => @privacy,
96
+ }
97
+ }
98
+ @info[:info][:pieces] = ""
99
+ @info.merge!({ :'url-list' => @webseed }) if @webseed
100
+ if @files.count > 0
101
+ if from_file?
102
+ @info[:info][:pieces] = pieces_from_file
103
+ else
104
+ read_pieces(all_files, @piecelength) do |piece|
105
+ @info[:info][:pieces] += Digest::SHA1.digest(piece)
106
+ end
107
+ end
108
+ end
109
+ set_infohash
110
+ end
111
+
112
+ def self.from_file(filename)
113
+ data = data_from_file(filename)
114
+
115
+ to = new(data['announce'], true ) do |t|
116
+ t.tracker_list = data['announce-list']
117
+ t.creation_date = data['creation date']
118
+ t.defaultdir = data['info']['name']
119
+ t.piecelength = data['info']['piece length']
120
+ t.files = data['info']['files']
121
+ t.privacy = data['info']['private']
122
+ t.webseed = data['url-list']
123
+ t.pieces_from_file = data['info']['pieces']
124
+ t.build
125
+ end
126
+
127
+ yield to if block_given?
128
+ to
129
+ end
130
+
131
+ def self.data_from_file(filename)
132
+ torrent_file = File.absolute_path(filename)
133
+ data = nil
134
+ File.open(torrent_file, 'rb') do |f|
135
+ data = BEncode.load(StringIO.new f.read)
136
+ end
137
+ raise unless data
138
+ data
139
+ end
140
+
141
+ def write(filename)
142
+ build_the_torrent
143
+ @torrent_file = File.absolute_path(filename)
144
+ open(@torrent_file, 'wb') do |file|
145
+ file.write self.to_s
146
+ end
147
+ end
148
+
149
+ # Return the .torrent file as a string
150
+ def to_s
151
+ return "You must add at least one file." if(@files.count < 1)
152
+ build_the_torrent unless (@info[:info][:files].count == @files.count)
153
+ @info.bencode
154
+ end
155
+
156
+ def add_file(filepath)
157
+ path_for_torrent = path_for_torrent_from_file(filepath)
158
+
159
+ if((@files.select { |f| f[:path] == path_for_torrent } ).count > 0)
160
+ raise IOError, "Can't add duplicate file #{File.basename(filepath)}"
161
+ end
162
+
163
+ # Remove a leading slash
164
+ if ( ! @dirbase.empty?) && filepath[0] == '/'
165
+ filepath = filepath.slice(1, filepath.length)
166
+ end
167
+
168
+ if File.exist?(filepath)
169
+ @files << { path: path_for_torrent, length: File::open(filepath, "rb").size }
170
+ elsif @dirbase && File.exist?(File.join(@dirbase, filepath))
171
+ @files << { path: path_for_torrent, length: File::open(File.join(@dirbase, filepath), "rb").size }
172
+ else
173
+ raise IOError, "Couldn't access #{filepath}"
174
+ end
175
+ end
176
+
177
+ def add_directory(path)
178
+ path = Pathname.new(path)
179
+ @dirbase = File.dirname(path) unless path.relative?
180
+ add_directory_to_torrent(path)
181
+ end
182
+
183
+ def add_directory_to_torrent(path)
184
+ # Using Dir.entries instead of glob so that non-escaped paths can be used
185
+ Dir.entries(path).each do |entry|
186
+ # Ignore unix current and parent directories
187
+ next if entry == '.' or entry == '..'
188
+
189
+ filename = File.join(path, entry).gsub(@dirbase, '') # Add a relative path
190
+ if File.directory?(filename)
191
+ add_directory_to_torrent(filename)
192
+ else
193
+ add_file(filename)
194
+ end
195
+ end
196
+ end
197
+
198
+ def set_webseed(url)
199
+ validate_url!(url)
200
+ @webseed = url
201
+ end
202
+
203
+ def add_tracker(tracker)
204
+ @tracker_list << [tracker]
205
+ end
206
+
207
+ def set_private
208
+ @privacy = 1
209
+ end
210
+
211
+ def set_public
212
+ @privacy = 0
213
+ end
214
+
215
+ # Implement Comparable. Provides info about whether this torrent
216
+ # defines the same files as another one.
217
+ def <=>(obj)
218
+ build
219
+ obj.build
220
+ return -1 if infohash < obj.infohash
221
+ return 0 if infohash == obj.infohash
222
+ 1
223
+ rescue StandardError
224
+ -1
225
+ end
226
+
227
+ # Determine if this matches all the same resources
228
+ # (including tracker information)
229
+ def eql?(obj)
230
+ build
231
+ obj.build
232
+ obj.all_info_matches?(@info)
233
+ rescue StandardError
234
+ false
235
+ end
236
+
237
+ def all_info_matches?(info)
238
+ @info.eql?(info)
239
+ end
240
+
241
+ alias build_the_torrent build
242
+ alias write_torrent write
243
+
244
+ private
245
+ def validate_url!(url)
246
+ u = URI.parse(url)
247
+ if u.scheme.nil? || u.host.nil?
248
+ raise ArgumentError.new("#{url} is not a valid URL")
249
+ end
250
+ end
251
+
252
+ def path_for_torrent_from_file(filepath)
253
+ unless @dirbase.empty?
254
+ filepath = filepath.sub(@dirbase, '')
255
+ end
256
+
257
+ # Remove leading blank item
258
+ path_for_torrent = filepath.split('/') - [""]
259
+
260
+ path_for_torrent
261
+ end
262
+
263
+ def set_infohash
264
+ @infohash = Digest::SHA1.hexdigest @info[:info].bencode
265
+ end
266
+ end
@@ -0,0 +1,95 @@
1
+ require 'test_helper'
2
+
3
+ class MktorrentTest < Minitest::Test
4
+
5
+ def setup
6
+ @torrent = Torrent.new(TRACKER)
7
+ @torrent2 = Torrent.new(TRACKER)
8
+ # Lol. This is pretty bad :)
9
+ fail "Could not find #{VALIDFILEPATH}" unless File.exist? VALIDFILEPATH
10
+ fail "Could not find #{VALIDFILE2PATH}" unless File.exist? VALIDFILE2PATH
11
+ end
12
+
13
+ def test_create_torrent
14
+ assert_equal @torrent.tracker, TRACKER
15
+ end
16
+
17
+ def test_add_file_with_invalid_file
18
+ assert_raises(IOError) { @torrent.add_file("../tmp/bogusfile.vhd") }
19
+ end
20
+
21
+ def test_add_single_valid_file
22
+ @torrent.add_file(VALIDFILEPATH)
23
+ assert_equal @torrent.count, 1
24
+ assert(@torrent.info[:info][:files].select { |f| f[:name] == VALIDFILENAME })
25
+ end
26
+
27
+ def test_add_second_tracker
28
+ @torrent.add_tracker(SNDTRACKER)
29
+ assert_equal 2, @torrent.tracker_list.count
30
+ end
31
+
32
+ def test_add_multiple_trackers
33
+ @torrent.add_tracker(SNDTRACKER)
34
+ @torrent.add_tracker(THDTRACKER)
35
+ assert_equal SNDTRACKER, @torrent.tracker_list[1][0]
36
+ end
37
+
38
+ def test_add_valid_file_as_path_array
39
+ @torrent.add_file(VALIDFILEPATH)
40
+ path = @torrent.info[:info][:files].first[:path]
41
+ assert_kind_of Array, path, "Path should be an array, not #{path}"
42
+ end
43
+
44
+ def test_add_another_valid_file
45
+ @torrent.add_file(VALIDFILEPATH)
46
+ @torrent.add_file(VALIDFILE2PATH)
47
+ assert_equal 2, @torrent.count
48
+ end
49
+
50
+ def test_prevent_duplicate_file_from_being_added
51
+ @torrent.add_file(VALIDFILEPATH)
52
+ assert_raises(IOError) { @torrent.add_file(VALIDFILEPATH) }
53
+ end
54
+
55
+ def test_add_directory_increments_file_count
56
+ @torrent.add_directory(VALIDPATH)
57
+ assert_equal 3, @torrent.count
58
+ end
59
+
60
+ # When adding a directory, only the folder that's added (and everything below) should appear in the metadata
61
+ def test_add_directory_uses_relative_paths
62
+ assert [ VALIDFILEPATH, VALIDFILE2PATH ].each { |p| p.start_with?(VALIDPATH) }
63
+ @torrent.add_directory(VALIDPATH)
64
+ assert @torrent.files.each { |f| ! f[:path].join('/').start_with?(File.basename(File.dirname(VALIDPATH))) }
65
+ end
66
+
67
+ def test_default_privacy
68
+ @torrent.add_file(VALIDFILEPATH)
69
+ assert_equal 0, @torrent.privacy
70
+ end
71
+
72
+ def test_set_privacy
73
+ test_default_privacy
74
+ @torrent.set_private
75
+ assert_equal 1, @torrent.privacy
76
+ end
77
+
78
+ def test_set_valid_webseed
79
+ @torrent.set_webseed(WEBSEED)
80
+ assert_equal WEBSEED, @torrent.webseed
81
+ end
82
+
83
+ def test_add_invalid_webseed
84
+ assert_raises(ArgumentError) {
85
+ @torrent.set_webseed('uheoatnhuetano')
86
+ }
87
+ end
88
+
89
+ def test_torrent2_equality
90
+ assert_equal @torrent, @torrent2
91
+ @torrent2.tracker = 'http://example.com'
92
+ assert @torrent == @torrent2, 'info hash should still match, so torrent == torrent2'
93
+ assert !@torrent.eql?(@torrent2), 'announce uri changed, so torrent ! eql? torrent2'
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/unit'
3
+ require 'minitest/reporters'
4
+ require 'shellwords'
5
+
6
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'mktorrent')
7
+ Dir.glob('test/support/*.rb').each { |r| load r }
8
+
9
+ Minitest::Reporters.use! [
10
+ Minitest::Reporters::DefaultReporter.new(color: true),
11
+ ]
12
+
13
+
14
+ TRACKER = "http://test.example.com"
15
+ SNDTRACKER = "udp://test.tracker.tk:80"
16
+ THDTRACKER = "udp://open.testtracker.com:6500/announce"
17
+ WEBSEED = "http://seed.example.com/webseed"
18
+ EXPECTED_INFOHASH = "a8036d51ada8fb699c9f29d7861e5589f2d20cf9"
19
+ VALIDPATH = File.expand_path("#{File.dirname(__FILE__)}/test_data")
20
+ VALIDFILEPATH = File.expand_path("#{File.dirname(__FILE__)}/test_data/sample_file1.vhd")
21
+ VALIDFILE2PATH = File.expand_path("#{File.dirname(__FILE__)}/test_data/sample_file2.vhd")
22
+ VALIDFILENAME = "randomfile.vhd"
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ll-mktorrent
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Timothy Mukaibo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bencode
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-reporters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubygems-tasks
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.4
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.4
97
+ description: Create .torrent files easily with this gem. The code is ugly, but it
98
+ works :)
99
+ email: timothy@mukaibo.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - lib/mktorrent.rb
105
+ - test/mktorrent_test.rb
106
+ - test/test_helper.rb
107
+ homepage: https://github.com/mukaibot/mktorrent
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.6.13
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Create .torrent files easily with this gem
131
+ test_files:
132
+ - test/mktorrent_test.rb
133
+ - test/test_helper.rb