srcforge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+
2
+ 0.1.0 - First functional version, awaiting feedback.
3
+
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Simple script to simplify the downloading of packages from sourceforge.
4
+ #
5
+ # Author:: Gonzalo Garramuno
6
+ # Copyright:: 2006
7
+ # License:: Ruby
8
+ #
9
+
10
+ require 'sourceforge'
11
+ SourceForge.new
12
+
13
+ __END__
@@ -0,0 +1,105 @@
1
+
2
+ This script allows you to easily automate the downloading of
3
+ the latest version of any sourceforge project (as stored up
4
+ to 31/03/2006) from any of sourceforge server by having it
5
+ parse the web pages for the project and extract the latest
6
+ release.
7
+ It supports connecting through a proxy if either the
8
+ environment variable HTTP_PROXY or http_proxy is defined.
9
+ Also, downloads can be resumed like wget, in case the
10
+ download is abruptly terminated.
11
+ If .md5 checksums are available, they will also be downloaded
12
+ and verified using ruby's digest/md5.
13
+
14
+
15
+ Use:
16
+ srcforge -h
17
+
18
+ to obtain a list of the latest command-line switches.
19
+
20
+
21
+
22
+ Common Usage:
23
+
24
+ > srcforge [options] <project>
25
+
26
+ where <project> is a valid sourceforge project, as named in the
27
+ http://sourceforge.net/projects/<project>.
28
+
29
+ The default behavior of the script is to try to to download files through
30
+ a proxy if possible and to download binary files for your platform and
31
+ That is, .exe's or .zip's for windows, and tar.gz files for others.
32
+
33
+
34
+ Example 1: Downloading binaries
35
+ ---------
36
+
37
+ > srcforge scons
38
+ Using proxy 127.0.0.1:8118
39
+ Connecting to sourceforge...
40
+ Files to download:
41
+ 1) scons-0.96.92.win32.exe *YES*
42
+
43
+ Downloading from 'easynews.dl.sourceforge.net':
44
+ scons-0.96.92.win32.exe 437641 bytes.
45
+ |--------------------------------------------------------------------|
46
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
47
+
48
+
49
+
50
+ Example 2: Downloading source code only
51
+ ---------
52
+
53
+ > srcforge scons -src
54
+ Using proxy 127.0.0.1:8118
55
+ Connecting to sourceforge...
56
+ Files to download:
57
+ 1) scons-src-0.96.92.tar.gz *YES*
58
+
59
+ Downloading from 'easynews.dl.sourceforge.net':
60
+ scons-src-0.96.92.tar.gz 1375075 bytes.
61
+ |--------------------------------------------------------------------|
62
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
63
+
64
+
65
+
66
+
67
+ Example 3: Downloading everything for all platforms
68
+ ---------
69
+
70
+ > srcforge scons -all
71
+ Using proxy 127.0.0.1:8118
72
+ Connecting to sourceforge...
73
+ Files to download:
74
+ 1) scons-0.96.92-1.noarch.rpm *YES*
75
+ 2) scons-0.96.92.tar.gz *YES*
76
+ 3) scons-0.96.92.win32.exe *YES*
77
+ 4) scons-0.96.92.zip *YES*
78
+ 5) scons-local-0.96.92.tar.gz *YES*
79
+ 6) scons-local-0.96.92.zip *YES*
80
+
81
+ Downloading from 'easynews.dl.sourceforge.net':
82
+ scons-0.96.92-1.noarch.rpm 644187 bytes.
83
+ .....etc....
84
+
85
+
86
+
87
+
88
+
89
+ Example 4: Choose what to download
90
+ ---------
91
+
92
+ > srcforge scons -all -ch
93
+ Using proxy 127.0.0.1:8118
94
+ Connecting to sourceforge...
95
+ Files to download:
96
+ 1) scons-0.96.92-1.noarch.rpm
97
+ 2) scons-0.96.92.tar.gz
98
+ 3) scons-0.96.92.win32.exe
99
+ 4) scons-0.96.92.zip
100
+ 5) scons-local-0.96.92.tar.gz
101
+ 6) scons-local-0.96.92.zip
102
+
103
+ Choose one or more files to download:
104
+ (Use '1 3 4' to toggle file or '3-5' for toggling a range)
105
+ >
@@ -0,0 +1,475 @@
1
+
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ # Additional libraries ( from rubygems )
7
+ require 'web/htmltools/xmltree' # narf htmltools parser
8
+ require 'Getopt/Declare'
9
+
10
+
11
+ #
12
+ # Trap interrupts and exit gracefully.
13
+ #
14
+ BEGIN {
15
+ trap('INT') {
16
+ $stderr.puts "[CTRL-C] Aborting..."
17
+ exit(1)
18
+ }
19
+ }
20
+
21
+
22
+ class SourceForge
23
+ CHECKSUMS = 'md5'
24
+
25
+ DEFAULT_SERVERS = [
26
+ 'easynews', # US
27
+ 'umn', # US
28
+ 'ufpr', # brazil
29
+ 'kent', # UK
30
+ 'surfnet', # NL
31
+ 'internap', # US
32
+ 'ovh', # FR
33
+ 'switch', # CH
34
+ 'heanet', # IE
35
+ 'superb', # US
36
+ 'nchc', # TW
37
+ 'jaist', # JP
38
+ 'mesh', # DE
39
+ ]
40
+
41
+ #
42
+ # Parse command-line arguments.
43
+ #
44
+ def parse_args
45
+ @args = Getopt::Declare.new(<<'EOH')
46
+
47
+ This script allows you to easily automate the downloading of
48
+ the latest version of any sourceforge project (as stored up
49
+ to 31/03/2006) from any of sourceforge server by having it
50
+ parse the web pages for the project and extract the latest
51
+ release.
52
+ It supports connecting through a proxy if either the
53
+ environment variable HTTP_PROXY or http_proxy is defined.
54
+ Also, downloads can be resumed like wget, in case the
55
+ download is abruptly terminated.
56
+ If .md5 checksums are available, they will also be downloaded
57
+ and verified using ruby's digest/md5.
58
+
59
+ Options:
60
+ -np connect to the net without a proxy
61
+ -all Try download all files for all OSes.
62
+ Default: only current os.
63
+ -o <dir:d> Output directory to place files in.
64
+ Default: .
65
+ -src Try to download source files only.
66
+ -ls list valid names of servers to download
67
+ from.
68
+ -s <server:s> name of the server to download from.
69
+ Default: #{@server}.
70
+ -ch [<num:+i>...] Choose to download some files only.
71
+ <project:s>... name of the project(s) to download.
72
+
73
+ EOH
74
+
75
+ @server = @args['-s'] || DEFAULT_SERVERS[0]
76
+
77
+ dir = @args['-o'] || '.'
78
+ Dir.chdir(dir)
79
+
80
+ ### This proxy is set to use Privoxy/TOR's default configuration.
81
+ ### If no proxy, make @proxy_host = nil or use -np option.
82
+ unless @args['-np']
83
+ @proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
84
+ if @proxy.kind_of?(String)
85
+ @proxy =~ /http:\/\/([^:]+):(\d+)/
86
+ @proxy_host = $1
87
+ @proxy_port = $2
88
+ puts "Using proxy #{@proxy_host}:#{@proxy_port}"
89
+ end
90
+ @proxy_user = nil
91
+ @proxy_pass = nil
92
+ end
93
+
94
+ if @args['-ls']
95
+ puts DEFAULT_SERVERS
96
+ exit
97
+ end
98
+
99
+ @project_list = @args['<project>']
100
+ unless @project_list
101
+ $stderr.puts "Error: required parameter '<project>' missing."
102
+ $stderr.puts
103
+ $stderr.puts "(try '#{$0} -help' for more information)"
104
+ exit(1)
105
+ end
106
+
107
+ @proxy = Net::HTTP::Proxy(@proxy_host, @proxy_port,
108
+ @proxy_user, @proxy_pass)
109
+ end
110
+
111
+
112
+ #
113
+ # Given a project, try to find the latest file releases for it
114
+ # in sourceforge.
115
+ #
116
+ # Return: Array of files
117
+ #
118
+ def find_latest_releases
119
+ puts "Connecting to sourceforge..."
120
+ url = URI.parse( 'http://sourceforge.net/projects/' + @project )
121
+ req = Net::HTTP::Get.new(url.path)
122
+
123
+
124
+ http = @proxy.start(url.host)
125
+ res = http.request(req)
126
+ if res.code.to_i != 200
127
+ $stderr.puts "Could not open connection to sourceforge. Error code: #{res.code}"
128
+ $stderr.puts res.message
129
+ $stderr.puts res.body
130
+ exit(res.code.to_i)
131
+ end
132
+
133
+
134
+ res.body =~ /(\/project\/showfiles.php\?group_id=\d+)/
135
+ path = $1
136
+ unless path
137
+ $stderr.puts "Non-existent project \"#{@project}\"."
138
+ exit(1)
139
+ end
140
+
141
+ req = Net::HTTP::Get.new(path)
142
+ res = http.request(req)
143
+ if res.code.to_i != 200
144
+ $stderr.puts "Could not get #{path}. Error code: #{res.code}"
145
+ $stderr.puts res.message
146
+ $stderr.puts res.body
147
+ exit(200)
148
+ end
149
+
150
+ p = HTMLTree::XMLParser.new(false, true)
151
+ p.feed( res.body )
152
+ xml = p.tree
153
+
154
+ re = /^http:\/\/prdownloads\.sourceforge\.net\/#{@project}\/([^\?]+)/
155
+
156
+ files = []
157
+ xml.elements.each('//tr/td/a') { |x|
158
+ parent = x.parent.parent
159
+ clas = parent.attributes['class']
160
+ next if clas != 'show'
161
+ attr = x.attributes['href']
162
+ next if attr !~ re
163
+ files << $1
164
+ }
165
+
166
+ return files
167
+ end
168
+
169
+ #
170
+ # Start downloading files stored in @downloads array
171
+ #
172
+ def start_downloads
173
+ path = "http://#{@server}.dl.sourceforge.net/sourceforge/#{@project}/"
174
+ url = URI.parse( path )
175
+ puts
176
+ puts "Downloading from '#{url.host}':"
177
+
178
+ @downloads.each { |d|
179
+ print "\t#{d}"
180
+
181
+ begin
182
+ f = nil
183
+ header = nil
184
+ existSize = 0
185
+
186
+ http = @proxy.start(url.host)
187
+ if File.exists?(d) and File.size(d) > 0
188
+ existSize = File.size(d)
189
+ f = open(d, 'ab')
190
+ header = { 'Range' => 'bytes=%d-' % existSize }
191
+ else
192
+ f = open(d, 'wb')
193
+ end
194
+ req = Net::HTTP::Get.new( url.path + d, header )
195
+
196
+ http.request( req, header ) { |response|
197
+ webSize = response.content_length.to_i + existSize
198
+ if webSize == existSize
199
+ puts "\tAlready downloaded."
200
+ break
201
+ end
202
+
203
+ code = response.code.to_i
204
+ if code == 302
205
+ puts "\t** NOT IN MIRROR **"
206
+ break
207
+ elsif code != 200 and code != 206
208
+ puts response.body
209
+ break
210
+ else
211
+ size = webSize - existSize
212
+ if size == webSize
213
+ puts "\t#{webSize} bytes."
214
+ else
215
+ if code != 204
216
+ f = open(d, 'wb')
217
+ puts "\t#{webSize} bytes."
218
+ else
219
+ puts " Continue #{size} of #{webSize} bytes."
220
+ end
221
+ end
222
+ ###
223
+ line_length = 68
224
+ puts "\t|" + '-'*line_length + '|'
225
+ num = (existSize.to_f / webSize.to_f * line_length).to_i
226
+ $stdout.print "\t " + '#' * num
227
+ $stdout.flush
228
+ numBytes = existSize.to_f
229
+ response.read_body { |data|
230
+ f.print data
231
+ numBytes += data.size
232
+ newnum = (numBytes / webSize.to_f * line_length).to_i - num
233
+ if newnum > 0
234
+ $stdout.print '+' * newnum
235
+ $stdout.flush
236
+ num += newnum
237
+ end
238
+ }
239
+ puts
240
+ end
241
+ }
242
+ # When using TOR, sometimes when a circuit changes, you can
243
+ # end up with a timeout/response error. We just retry the request.
244
+ rescue Timeout::Error, Net::HTTPBadResponse => e
245
+ puts " #{e} - retry"
246
+ retry
247
+ end
248
+
249
+ f.close
250
+ }
251
+ end
252
+
253
+
254
+ #
255
+ # Filter downloads. If any regex of filter matches, all other
256
+ # elements are removed.
257
+ # Also, checksum files are added to downloads (if available)
258
+ #
259
+ def filter_downloads(filter)
260
+ checksums = @downloads.grep(/\.#{CHECKSUMS}$/i)
261
+
262
+ filter.each { |re|
263
+ unless @downloads.grep(re).empty?
264
+ @downloads.delete_if { |f| f !~ re }
265
+ break
266
+ end
267
+ }
268
+
269
+ @downloads.each { |d|
270
+ item = checksums.grep(/^#{d}\.#{CHECKSUMS}$/)
271
+ next if item.empty?
272
+ @checksums << item[0]
273
+ }
274
+ @downloads += @checksums
275
+ end
276
+
277
+ #
278
+ # Print out a menu with the list of files to download.
279
+ #
280
+ def file_menu(filter)
281
+ puts "Files to download:"
282
+ @downloads.each_with_index { |x, i|
283
+ print "\t%2d) %-40s" % [i+1, x]
284
+ if filter.include?(i+1)
285
+ print "\t*YES*"
286
+ end
287
+ print "\n"
288
+ }
289
+ end
290
+
291
+ #
292
+ # Ask user to choose some files.
293
+ #
294
+ def choose_files
295
+ puts
296
+ puts "Choose one or more files to download:"
297
+ puts "(Use '1 3 4' to toggle file or '3-5' for toggling a range)"
298
+ print ">"
299
+ cmd = $stdin.gets.split(/[,\s]+/)
300
+ answer = []
301
+ cmd.each { |c|
302
+ if c =~ /(\d+)-(\d+)/
303
+ answer += ($1.to_i..$2.to_i).to_a
304
+ else
305
+ answer << c.to_i
306
+ end
307
+ }
308
+ return answer.uniq
309
+ end
310
+
311
+ #
312
+ # Do the download of files
313
+ #
314
+ def do_download
315
+ @checksums = []
316
+
317
+ if @args['-src'] and @downloads.size > 1
318
+ @downloads.delete_if { |f| f =~ /\.(exe|rpm|deb)$/i }
319
+
320
+ # If files are labelled with src or source, remove
321
+ # all other files from list.
322
+ src_re = /(?:src|source)/i
323
+ unless @downloads.grep(src_re).empty?
324
+ @downloads.delete_if { |f| f !~ src_re }
325
+ end
326
+
327
+ # If sources are available in multiple compression
328
+ # formats, prefer smallest linux format.
329
+ filter_downloads(
330
+ [
331
+ /\.tar\.7za$/i,
332
+ /\.tar\.bz2$/i,
333
+ /\.tar\.gz$/i,
334
+ /\.tgz$/i,
335
+ /\.zip$/i,
336
+ /\.tar$/i,
337
+ ]
338
+ )
339
+ else
340
+ # Delete any source file archives...
341
+ @downloads.delete_if { |f| f =~ /(?:src|source)/i }
342
+
343
+
344
+ if @downloads.size > 1 and not @args['-all']
345
+ # if more than one download, choose based on platform
346
+ filter = [
347
+ /\.tar\.bz2$/i,
348
+ /\.tar\.gz$/i,
349
+ /\.tgz$/i,
350
+ /\.sh$/i
351
+ ]
352
+
353
+ case RUBY_PLATFORM
354
+ when /mswin/
355
+ unless @downloads.grep(/\.(exe|zip)$/).empty?
356
+ filter = [
357
+ /\.exe$/i,
358
+ /\.zip$/i,
359
+ /\.tar\.bz2$/i,
360
+ /\.tar\.gz$/i,
361
+ /\.tgz$/i,
362
+ /\.sh$/i
363
+ ]
364
+ end
365
+ when /linux/
366
+ unless @downloads.grep(/\.(rpm|deb)$/).empty?
367
+ filter = [
368
+ /\.rpm$/i,
369
+ /\.deb$/i,
370
+ /\.tar\.bz2$/i,
371
+ /\.tar\.gz$/i,
372
+ /\.tgz$/i,
373
+ /\.sh$/i
374
+ ]
375
+ end
376
+ when /osx/
377
+ unless @downloads.grep(/\.dmg$/).empty?
378
+ filter = [
379
+ /\.dmg$/i,
380
+ /\.tar\.bz2$/i,
381
+ /\.tar\.gz$/i,
382
+ /\.tgz$/i,
383
+ /\.sh$/i
384
+ ]
385
+ end
386
+ end
387
+
388
+ filter_downloads(filter)
389
+ end
390
+ end
391
+
392
+ if @downloads.empty?
393
+ puts "Nothing to download."
394
+ exit(1)
395
+ end
396
+
397
+
398
+ filter = (1..@downloads.size).to_a
399
+
400
+ if @args['-ch']
401
+ filter = @args['-ch']
402
+ end
403
+
404
+ if @args['-ch'] and filter.empty?
405
+ answer = [1]
406
+ while not answer.empty?
407
+ file_menu(filter)
408
+ answer = choose_files
409
+ filter = filter + answer - ( filter & answer )
410
+ end
411
+ else
412
+ file_menu(filter)
413
+ end
414
+
415
+ unless filter.empty?
416
+ i = 0
417
+ @downloads.delete_if {
418
+ i += 1
419
+ not filter.include?(i)
420
+ }
421
+ end
422
+
423
+ if @downloads.empty?
424
+ puts "Nothing to download."
425
+ exit(1)
426
+ end
427
+
428
+ start_downloads
429
+ end
430
+
431
+
432
+ #
433
+ # For each checksum downloaded, verify it.
434
+ #
435
+ def verify_checksums
436
+ return if @checksums.empty?
437
+
438
+ require 'digest/md5'
439
+
440
+ puts "Verifying Checksums..."
441
+
442
+ @checksums.each { |c|
443
+ File.open(c, 'r') { |cm|
444
+ while not cm.eof?
445
+ line = cm.readline
446
+ checksum, file = line.split(/\s+/)
447
+ sum = Digest::MD5.new
448
+ File.open(file, 'rb') { |f|
449
+ while not f.eof?
450
+ sum << f.read(8196)
451
+ end
452
+ }
453
+ print "\t#{file}...\t"
454
+ if sum != checksum
455
+ puts "FAILED!!!"
456
+ else
457
+ puts "OK"
458
+ end
459
+ end
460
+ }
461
+ }
462
+ end
463
+
464
+ #
465
+ # Constructor.
466
+ #
467
+ def initialize
468
+ parse_args
469
+ @project_list.each { |@project|
470
+ @downloads = find_latest_releases
471
+ do_download
472
+ verify_checksums
473
+ }
474
+ end
475
+ end
@@ -0,0 +1,31 @@
1
+ require "rubygems"
2
+
3
+ spec = Gem::Specification.new do |spec|
4
+ spec.name = "srcforge"
5
+ spec.version = '0.1.0'
6
+ spec.author = "Gonzalo Garramuno"
7
+ spec.email = 'ggarram@advance.dsl.com.ar'
8
+ spec.homepage = 'http://www.rubyforge.org/projects/srcforge/'
9
+ spec.summary = 'Download tool for files at sourceforge.net.'
10
+ spec.executables = ['srcforge']
11
+ spec.files = ['bin/srcforge', 'lib/sourceforge.rb']
12
+ spec.description = <<-EOF
13
+ This script allows you to easily automate the downloading of
14
+ the latest version of any sourceforge project (as stored up
15
+ to 31/03/2006) from any of sourceforge server by having it
16
+ parse the web pages for the project and extract the latest
17
+ release.
18
+ It supports connecting through a proxy if either the
19
+ environment variable HTTP_PROXY or http_proxy is defined.
20
+ Also, downloads can be resumed like wget, in case the
21
+ download is abruptly terminated.
22
+ If .md5 checksums are available, they will also be downloaded
23
+ and verified using ruby's digest/md5.
24
+ EOF
25
+ spec.add_dependency("narf", ">= 0.7.3")
26
+ spec.add_dependency("getopt-declare", ">= 1.12")
27
+ spec.extra_rdoc_files = ["HISTORY.txt", "srcforge.gemspec"] + Dir.glob("docs/README.txt")
28
+ spec.has_rdoc = true
29
+ spec.rubyforge_project = 'srcforge'
30
+ spec.required_ruby_version = '>= 1.8.0'
31
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: srcforge
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-05-17 00:00:00 -03:00
8
+ summary: Download tool for files at sourceforge.net.
9
+ require_paths:
10
+ - lib
11
+ email: ggarram@advance.dsl.com.ar
12
+ homepage: http://www.rubyforge.org/projects/srcforge/
13
+ rubyforge_project: srcforge
14
+ description: This script allows you to easily automate the downloading of the latest version of any sourceforge project (as stored up to 31/03/2006) from any of sourceforge server by having it parse the web pages for the project and extract the latest release. It supports connecting through a proxy if either the environment variable HTTP_PROXY or http_proxy is defined. Also, downloads can be resumed like wget, in case the download is abruptly terminated. If .md5 checksums are available, they will also be downloaded and verified using ruby's digest/md5.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Gonzalo Garramuno
30
+ files:
31
+ - bin/srcforge
32
+ - lib/sourceforge.rb
33
+ - HISTORY.txt
34
+ - srcforge.gemspec
35
+ - docs/README.txt
36
+ test_files: []
37
+
38
+ rdoc_options: []
39
+
40
+ extra_rdoc_files:
41
+ - HISTORY.txt
42
+ - srcforge.gemspec
43
+ - docs/README.txt
44
+ executables:
45
+ - srcforge
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies:
51
+ - !ruby/object:Gem::Dependency
52
+ name: narf
53
+ version_requirement:
54
+ version_requirements: !ruby/object:Gem::Version::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 0.7.3
59
+ version:
60
+ - !ruby/object:Gem::Dependency
61
+ name: getopt-declare
62
+ version_requirement:
63
+ version_requirements: !ruby/object:Gem::Version::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "1.12"
68
+ version: