matthewgarysmith-rubypodder 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +60 -0
- data/Rakefile +32 -0
- data/bin/rubypodder +8 -0
- data/lib/rubypodder.rb +155 -0
- data/tests/tc_rubypodder.rb +259 -0
- data/tests/tc_stdout.rb +52 -0
- data/tests/ts_rubypodder.rb +4 -0
- metadata +88 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Lex Miller
|
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
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
= rubypodder - A podcast aggregator without an interface
|
2
|
+
|
3
|
+
This package contains rubypodder, a simple podcast receiver
|
4
|
+
inspired by bashpodder (http://en.wikipedia.org/wiki/BashPodder).
|
5
|
+
|
6
|
+
== Homepage
|
7
|
+
|
8
|
+
http://rubyforge.org/projects/rubypodder/
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
|
12
|
+
rubypodder can be downloaded and installed with:
|
13
|
+
gem install rubypodder
|
14
|
+
|
15
|
+
== Quick Start
|
16
|
+
|
17
|
+
Type
|
18
|
+
rubypodder
|
19
|
+
When it finishes, you should see a ~/.rubypodder directory.
|
20
|
+
This should contain an example rp.conf configuration file with a feed in
|
21
|
+
it to get you started. If there were any podcasts to download in the feed
|
22
|
+
they will be in a directory named after the date (e.g. 2007-01-20).
|
23
|
+
You can see what happened by looking in rp.log.
|
24
|
+
|
25
|
+
== Running the rubypodder Test Suite
|
26
|
+
|
27
|
+
If you wish to run the unit tests that come with rubypodder:
|
28
|
+
gem check rubypodder --test
|
29
|
+
A message will be given if any of the tests fail.
|
30
|
+
|
31
|
+
=== Uninstallation
|
32
|
+
|
33
|
+
To uninstall the rubypodder gem use:
|
34
|
+
gem uninstall rubypodder
|
35
|
+
|
36
|
+
== Configuration
|
37
|
+
|
38
|
+
Create a file
|
39
|
+
~/.rubypodder/rp.conf
|
40
|
+
containing podcast feeds, one per line.
|
41
|
+
|
42
|
+
== Usage
|
43
|
+
|
44
|
+
Type
|
45
|
+
rubypodder
|
46
|
+
|
47
|
+
and if there are any podcast episodes from your feeds that have not yet
|
48
|
+
been downloaded, they will be downloaded into a "date" directory such as
|
49
|
+
~/.rubypodder/2007-01-18/
|
50
|
+
|
51
|
+
A good idea is to use +crontab -e+ and add a line like
|
52
|
+
0 5 * * * rubypodder
|
53
|
+
which will set up a cron job which will do this regularly.
|
54
|
+
|
55
|
+
== License
|
56
|
+
|
57
|
+
rubypodder is available under an MIT-style license.
|
58
|
+
|
59
|
+
:include: MIT-LICENSE
|
60
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
Gem::manage_gems
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
spec = Gem::Specification.new do |s|
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.name = "rubypodder"
|
7
|
+
s.version = "1.0.0"
|
8
|
+
s.author = "Lex Miller"
|
9
|
+
s.email = "lex.miller @nospam@ gmail.com"
|
10
|
+
s.summary = "A podcast aggregator without an interface"
|
11
|
+
s.files = FileList['lib/*.rb', 'tests/*', 'Rakefile'].to_a
|
12
|
+
s.require_path = "lib"
|
13
|
+
s.bindir = "bin"
|
14
|
+
s.executables = ["rubypodder"]
|
15
|
+
s.default_executable = "rubypodder"
|
16
|
+
s.autorequire = "rubypodder"
|
17
|
+
s.add_dependency("rio")
|
18
|
+
s.add_dependency("rake")
|
19
|
+
s.add_dependency("mocha")
|
20
|
+
s.test_files = Dir.glob('tests/*.rb')
|
21
|
+
s.has_rdoc = true
|
22
|
+
s.extra_rdoc_files = ["README", "MIT-LICENSE"]
|
23
|
+
end
|
24
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
25
|
+
pkg.need_tar = true
|
26
|
+
end
|
27
|
+
task :default => "pkg/#{spec.name}-#{spec.version}.gem" do
|
28
|
+
puts "generated latest version"
|
29
|
+
end
|
30
|
+
task :test do
|
31
|
+
ruby "tests/ts_rubypodder.rb"
|
32
|
+
end
|
data/bin/rubypodder
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
require 'rubypodder'
|
3
|
+
require 'optparse'
|
4
|
+
opts = OptionParser.new
|
5
|
+
opts.on("-v", "--version") { puts RubyPodder::Version; exit }
|
6
|
+
opts.on("-h", "--help") { puts opts.to_s + "See http://rubypodder.rubyforge.org/\n"; exit }
|
7
|
+
opts.parse(*ARGV)
|
8
|
+
RubyPodder.new.run
|
data/lib/rubypodder.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'rss/1.0'
|
2
|
+
require 'rss/2.0'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rio'
|
7
|
+
require 'logger'
|
8
|
+
require 'ftools'
|
9
|
+
|
10
|
+
class File
|
11
|
+
|
12
|
+
def self.touch(fn)
|
13
|
+
File.open(fn, "w").close unless File.exist?(fn)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class RubyPodder
|
19
|
+
|
20
|
+
Version = 'rubypodder v1.0.0'
|
21
|
+
|
22
|
+
attr_reader :conf_file, :log_file, :done_file, :date_dir
|
23
|
+
|
24
|
+
def initialize(file_base="~/.rubypodder/rp")
|
25
|
+
@file_base = File.expand_path(file_base)
|
26
|
+
@rp_dir = File.dirname(@file_base)
|
27
|
+
@conf_file = @file_base + ".conf"
|
28
|
+
@log_file = @file_base + ".log"
|
29
|
+
@done_file = @file_base + ".done"
|
30
|
+
create_default_config_file
|
31
|
+
@log = Logger.new(@log_file)
|
32
|
+
File.touch @done_file
|
33
|
+
@date_dir = create_date_dir
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_default_config_file
|
37
|
+
expanded_path = File.expand_path(@conf_file)
|
38
|
+
return if File.exists?(expanded_path)
|
39
|
+
make_dirname(expanded_path)
|
40
|
+
rio(expanded_path) < "http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_dirname(full_filename)
|
44
|
+
dirname = File.dirname(full_filename)
|
45
|
+
File.makedirs dirname
|
46
|
+
end
|
47
|
+
|
48
|
+
def date_string(time)
|
49
|
+
time.strftime("%Y-%m-%d")
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_date_dir
|
53
|
+
date_dir = File.join(@rp_dir, date_string(Time.now))
|
54
|
+
File.makedirs date_dir
|
55
|
+
date_dir
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_feeds
|
59
|
+
#IO.readlines(@conf_file).each {|l| l.chomp!}
|
60
|
+
a = rio(@conf_file).chomp.readlines.reject {|i| i =~ /^#/}
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_rss(rss_source)
|
64
|
+
RSS::Parser.parse(rss_source, false)
|
65
|
+
end
|
66
|
+
|
67
|
+
def dest_file_name(url)
|
68
|
+
dest = File.join(@date_dir, File.basename(URI.parse(url).path))
|
69
|
+
dest = dest.gsub(/\s+/,'_').gsub('%20','_')
|
70
|
+
while File.exists? dest
|
71
|
+
ext = File.extname(dest)
|
72
|
+
name = File.basename(dest, ext)
|
73
|
+
name += "_00" unless name =~ /_\d+$/
|
74
|
+
name.succ!
|
75
|
+
dest = File.join(File.dirname(dest), name + ext)
|
76
|
+
end
|
77
|
+
return dest
|
78
|
+
end
|
79
|
+
|
80
|
+
def record_download(url, guid)
|
81
|
+
rio(@done_file) << "#{url}\n"
|
82
|
+
rio(@done_file) << "#{guid}\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def already_downloaded(url, guid)
|
86
|
+
previously_downloaded = [url.strip.downcase, guid.strip.downcase]
|
87
|
+
File.open(@done_file).detect do |line|
|
88
|
+
previously_downloaded.include?(line.strip.downcase)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def download(url, guid)
|
93
|
+
return if already_downloaded(url, guid)
|
94
|
+
@log.info(" Downloading: #{url}")
|
95
|
+
begin
|
96
|
+
file_name = dest_file_name(url)
|
97
|
+
rio(file_name) < rio(url)
|
98
|
+
rescue
|
99
|
+
@log.error(" Failed to download #{url}")
|
100
|
+
else
|
101
|
+
record_download(url, guid)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def download_all(items)
|
106
|
+
items.each do |item|
|
107
|
+
begin
|
108
|
+
guid = nil
|
109
|
+
if item.respond_to?(:guid) && item.guid.respond_to?(:content)
|
110
|
+
guid = item.guid.content
|
111
|
+
end
|
112
|
+
download(item.enclosure.url, guid)
|
113
|
+
rescue
|
114
|
+
@log.warn(" No media to download for this item")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def remove_dir_if_empty(dirname)
|
120
|
+
begin
|
121
|
+
Dir.rmdir(dirname)
|
122
|
+
rescue SystemCallError
|
123
|
+
@log.info("#{dirname} has contents, not removed")
|
124
|
+
else
|
125
|
+
@log.info("#{dirname} was empty, removed")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def run
|
130
|
+
@log.info("Starting (#{Version})")
|
131
|
+
read_feeds.each do |url|
|
132
|
+
begin
|
133
|
+
http_body = open(url, 'User-Agent' => 'Ruby-Wget').read
|
134
|
+
rescue
|
135
|
+
@log.error(" Can't read from #{url}")
|
136
|
+
next
|
137
|
+
end
|
138
|
+
begin
|
139
|
+
rss = parse_rss(http_body)
|
140
|
+
rescue
|
141
|
+
@log.error(" Can't parse this feed")
|
142
|
+
next
|
143
|
+
end
|
144
|
+
@log.info("Channel: #{rss.channel.title}")
|
145
|
+
download_all(rss.items)
|
146
|
+
end
|
147
|
+
remove_dir_if_empty(@date_dir)
|
148
|
+
@log.info("Finished")
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
if $0 == __FILE__
|
154
|
+
RubyPodder.new.run
|
155
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rubypodder'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
class TC_RubyPodder < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
system("rm -rf " + "/tmp/test_rp.conf")
|
10
|
+
system("rm -rf " + "/tmp/test_rp.done")
|
11
|
+
File.open("/tmp/test_rp.conf", "w") do |file|
|
12
|
+
file.write("# This is just a comment\n")
|
13
|
+
file.write("http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml\n")
|
14
|
+
file.write("# This is just another comment\n")
|
15
|
+
file.write("http://www.guardian.co.uk/podcasts/comedy/rickygervais/mp3.xml\n")
|
16
|
+
end
|
17
|
+
@subdir = "/tmp/subdir"
|
18
|
+
system("rm -rf " + @subdir)
|
19
|
+
@rp = RubyPodder.new("/tmp/test_rp")
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
system("rm -rf " + "/tmp/test_rp.conf")
|
24
|
+
system("rm -rf " + "/tmp/test_rp.done")
|
25
|
+
system("rm -rf " + "/tmp/test_rp.log")
|
26
|
+
system("rm -rf " + "/tmp/#{@rp.date_string(Time.now)}")
|
27
|
+
system("rm -rf " + @subdir)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_initialize_with_test_config_file
|
32
|
+
assert_kind_of(RubyPodder, @rp)
|
33
|
+
assert_equal("/tmp/test_rp.conf", @rp.conf_file)
|
34
|
+
assert_equal("/tmp/test_rp.log", @rp.log_file)
|
35
|
+
assert_equal("/tmp/test_rp.done", @rp.done_file)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_initialize_with_default_config_file
|
39
|
+
ENV['HOME'] = "/tmp"
|
40
|
+
@rp = RubyPodder.new()
|
41
|
+
assert_kind_of(RubyPodder, @rp)
|
42
|
+
assert_equal(ENV['HOME'] + "/.rubypodder/rp.conf", @rp.conf_file)
|
43
|
+
assert_equal(ENV['HOME'] + "/.rubypodder/rp.log", @rp.log_file)
|
44
|
+
assert_equal(ENV['HOME'] + "/.rubypodder/rp.done", @rp.done_file)
|
45
|
+
system("rm -rf " + "/tmp/.rubypodder")
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_initialize_with_absent_config_file
|
49
|
+
file_base = "/tmp/test_rp"
|
50
|
+
file_conf = file_base + ".conf"
|
51
|
+
system("rm -rf " + file_conf)
|
52
|
+
@rp = RubyPodder.new(file_base)
|
53
|
+
assert(File.exists?(file_conf))
|
54
|
+
system("rm -rf " + file_conf)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_make_dirname_one_subdir
|
58
|
+
filename = @subdir + "/file"
|
59
|
+
@rp.make_dirname(filename)
|
60
|
+
assert(File.exists?(@subdir))
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_make_dirname_many_subdir
|
64
|
+
subsubdirs = "/subsubdir/subsubsubdir"
|
65
|
+
filename = @subdir + subsubdirs + "/file"
|
66
|
+
@rp.make_dirname(filename)
|
67
|
+
assert(File.exists?(@subdir + subsubdirs))
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_date_string
|
71
|
+
t = Time.gm(2006,"dec",18,21,0,0)
|
72
|
+
date_string = @rp.date_string(t)
|
73
|
+
assert_equal(date_string, "2006-12-18")
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_initialize_with_absent_config_file_dir
|
77
|
+
file_base = @subdir + "/test_rp"
|
78
|
+
file_conf = file_base + ".conf"
|
79
|
+
@rp = RubyPodder.new(file_base)
|
80
|
+
assert(File.exists?(file_conf))
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_initialize_creates_date_dir
|
84
|
+
file_base = @subdir + "/test_rp"
|
85
|
+
@rp = RubyPodder.new(file_base)
|
86
|
+
date_dir = @subdir + "/" + @rp.date_string(Time.now)
|
87
|
+
assert_equal(date_dir, @rp.date_dir)
|
88
|
+
assert(File.exists?(date_dir), "Subdirectory #{date_dir} not created")
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_read_feeds
|
92
|
+
feed_list = @rp.read_feeds
|
93
|
+
assert_kind_of(Array, feed_list)
|
94
|
+
assert_equal(2, feed_list.length)
|
95
|
+
assert_equal("http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml", feed_list[0])
|
96
|
+
assert_equal("http://www.guardian.co.uk/podcasts/comedy/rickygervais/mp3.xml", feed_list[1])
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_parse_rss
|
100
|
+
#rss_source = IO.read("test_feed.xml")
|
101
|
+
rss_source = <<-END_OF_STRING
|
102
|
+
<?xml version="1.0" encoding="utf-8"?>
|
103
|
+
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
|
104
|
+
<channel>
|
105
|
+
<lastBuildDate>Fri, 27 Oct 2006 16:50:15 +0100</lastBuildDate>
|
106
|
+
<title>The Ricky Gervais Show</title>
|
107
|
+
<itunes:author>Guardian Unlimited</itunes:author>
|
108
|
+
<link>http://www.guardian.co.uk/rickygervais</link>
|
109
|
+
<generator>Podcast Maker v1.2.4 - http://www.potionfactory.com/podcastmaker</generator>
|
110
|
+
<description>Ricky Gervais, Steve Merchant and Karl Pilkington are back yada yada</description>
|
111
|
+
<itunes:subtitle />
|
112
|
+
<itunes:summary>Ricky Gervais, Steve Merchant and Karl Pilkington are back yada yada</itunes:summary>
|
113
|
+
<language>en</language>
|
114
|
+
<copyright>Ricky Gervais, Steve Merchant, Karl Pilkington</copyright>
|
115
|
+
<itunes:owner>
|
116
|
+
<itunes:name>Glyn Hughes</itunes:name>
|
117
|
+
<itunes:email>glyn@rickygervais.com</itunes:email>
|
118
|
+
</itunes:owner>
|
119
|
+
<image>
|
120
|
+
<url>http://podcast.rickygervais.com/gu_p123_300_144.jpg</url>
|
121
|
+
<title>The Ricky Gervais Show</title>
|
122
|
+
<link>http://www.guardian.co.uk/rickygervais</link>
|
123
|
+
<width>144</width>
|
124
|
+
<height>144</height>
|
125
|
+
</image>
|
126
|
+
<itunes:image href="http://podcast.rickygervais.com/gu_p123_300.jpg" />
|
127
|
+
<category>Comedy</category>
|
128
|
+
<itunes:category text="Comedy" />
|
129
|
+
<itunes:keywords>Ricky, Gervais, Steve, Merchant, Karl, Pilkington</itunes:keywords>
|
130
|
+
<itunes:explicit>yes</itunes:explicit>
|
131
|
+
<item>
|
132
|
+
<title>The Podfather Part I - Halloween</title>
|
133
|
+
<itunes:author>Guardian Unlimited</itunes:author>
|
134
|
+
<description>The first of three specials from Ricky, Steve and Karl yada yada</description>
|
135
|
+
<itunes:subtitle>The first of three specials from Ricky, Steve and Karl yada yada</itunes:subtitle>
|
136
|
+
<itunes:summary />
|
137
|
+
<enclosure type="audio/mpeg" url="http://podcast.rickygervais.com/guspecials_halloween.mp3" length="15723925" />
|
138
|
+
<guid>http://podcast.rickygervais.com/guspecials_halloween.mp3</guid>
|
139
|
+
<pubDate>Tue, 31 Oct 2006 00:00:01 +0000</pubDate>
|
140
|
+
<category>Comedy</category>
|
141
|
+
<itunes:explicit>yes</itunes:explicit>
|
142
|
+
<itunes:duration>00:37:22</itunes:duration>
|
143
|
+
<itunes:keywords>Ricky, Gervais, Steve, Merchant, Karl, Pilkington</itunes:keywords>
|
144
|
+
</item>
|
145
|
+
</channel>
|
146
|
+
</rss>
|
147
|
+
END_OF_STRING
|
148
|
+
rss = @rp.parse_rss(rss_source)
|
149
|
+
assert_equal(1, rss.items.length)
|
150
|
+
assert_equal("http://podcast.rickygervais.com/guspecials_halloween.mp3", rss.items[0].enclosure.url)
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_item_with_no_enclosure
|
154
|
+
rss_source = <<-END_OF_STRING
|
155
|
+
<?xml version="1.0" encoding="utf-8"?>
|
156
|
+
<rss>
|
157
|
+
<channel>
|
158
|
+
<title>The Ricky Gervais Show</title>
|
159
|
+
<item>
|
160
|
+
<title>The Podfather Part I - Halloween</title>
|
161
|
+
</item>
|
162
|
+
</channel>
|
163
|
+
</rss>
|
164
|
+
END_OF_STRING
|
165
|
+
|
166
|
+
# Code here to test that a run doesn't throw an exception if there is no enclosure.
|
167
|
+
# (Remove enclosure from the above test XML)
|
168
|
+
#
|
169
|
+
rss = @rp.parse_rss(rss_source)
|
170
|
+
assert_nothing_raised { @rp.download_all(rss.items) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_dest_file_name(url)
|
174
|
+
correct_dest_file_name = @rp.date_dir + "/" + "podcast.mp3"
|
175
|
+
dest_file_name = @rp.dest_file_name("http://www.podcast.com/podcast.mp3")
|
176
|
+
assert_equal(correct_dest_file_name, dest_file_name)
|
177
|
+
dest_file_name = @rp.dest_file_name("http://www.podcast.com/subdir/podcast.mp3")
|
178
|
+
assert_equal(correct_dest_file_name, dest_file_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_download
|
182
|
+
@rp.download("http://www.google.com/index.html")
|
183
|
+
dest_file = @rp.date_dir + "/" + "index.html"
|
184
|
+
assert(File.exists?(dest_file))
|
185
|
+
assert(File.open(dest_file).grep(/google/))
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_download_recorded
|
189
|
+
@rp.download("http://www.google.com/index.html")
|
190
|
+
assert(File.exists?(@rp.done_file))
|
191
|
+
assert(File.open(@rp.done_file).grep(/^http:\/\/www.google.com\/index.html$/))
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_already_downloaded
|
195
|
+
url1 = "http://www.google.com/index.html"
|
196
|
+
assert(!@rp.already_downloaded(url1), "url1 should not be already downloaded before download of url1")
|
197
|
+
@rp.download(url1)
|
198
|
+
File.open( @rp.log_file ) do |f|
|
199
|
+
return if (f.any? { |line| line =~ /ERROR/ }) # Don't test this case if no internet connection
|
200
|
+
end
|
201
|
+
assert(@rp.already_downloaded(url1), "url1 should be already downloaded after download of url1")
|
202
|
+
url2 = "http://www.google.co.nz/index.html"
|
203
|
+
@rp.download(url2)
|
204
|
+
assert(@rp.already_downloaded(url2), "url2 should be already downloaded after download of url2")
|
205
|
+
assert(@rp.already_downloaded(url1), "url1 should still be already downloaded after download of url2")
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_download_omits_done_items
|
209
|
+
dest_file = @rp.date_dir + "/" + "index.html"
|
210
|
+
system("rm -rf " + dest_file)
|
211
|
+
@rp.record_download("http://www.google.com/index.html")
|
212
|
+
@rp.download("http://www.google.com/index.html")
|
213
|
+
assert(!File.exists?(dest_file), "#{dest_file} should not be downloaded again if already recorded as done")
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_download_error_is_logged
|
217
|
+
@rp.download("http://very.very.broken.url/oh/no/oh/dear.xml")
|
218
|
+
File.open( @rp.log_file ) do |f|
|
219
|
+
assert(f.any? { |line| line =~ /ERROR/ }, "Error in download should be logged")
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_remove_dir_if_empty
|
224
|
+
system("mkdir -p " + @subdir)
|
225
|
+
@rp.remove_dir_if_empty(@subdir)
|
226
|
+
assert(!File.exists?(@subdir))
|
227
|
+
system("mkdir -p " + @subdir)
|
228
|
+
system("touch " + @subdir + "/fish")
|
229
|
+
@rp.remove_dir_if_empty(@subdir)
|
230
|
+
assert(File.exists?(@subdir))
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_log_contains_version
|
234
|
+
File.open("/tmp/test_rp.conf", "w") { |file| file.write("# Empty config file\n") }
|
235
|
+
@rp = RubyPodder.new("/tmp/test_rp")
|
236
|
+
@rp.run
|
237
|
+
File.open( @rp.log_file ) do |f|
|
238
|
+
assert(f.any? { |line| line =~ /Starting.+#{RubyPodder::Version}/ }, "'Starting' log entry should contain '#{RubyPodder::Version}'")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_log_contains_error_for_unreadable_feed_url
|
243
|
+
File.open("/tmp/test_rp.conf", "w") { |file| file.write("http://very.very.broken.url/oh/no/oh/dear.xml\n") }
|
244
|
+
@rp = RubyPodder.new("/tmp/test_rp")
|
245
|
+
@rp.run
|
246
|
+
File.open( @rp.log_file ) do |f|
|
247
|
+
assert(f.any? { |line| line =~ /ERROR/ }, "Error in feed url should be logged")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_log_contains_error_for_unparsable_rss_source
|
252
|
+
@rp.stubs(:parse_rss).raises(RSS::NotWellFormedError, 'This is not well formed XML')
|
253
|
+
@rp.run
|
254
|
+
File.open( @rp.log_file ) do |f|
|
255
|
+
assert(f.any? { |line| line =~ /ERROR/ }, "Parse error in rss source should be logged")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
data/tests/tc_stdout.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
class StringStream < String
|
4
|
+
def write(message)
|
5
|
+
self.<< message
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def wrap(&b)
|
10
|
+
raise "Expected block!" unless block_given?
|
11
|
+
s = StringStream.new
|
12
|
+
old = $stdout.clone
|
13
|
+
$stdout = s
|
14
|
+
b.call
|
15
|
+
$stdout = old
|
16
|
+
s
|
17
|
+
end
|
18
|
+
|
19
|
+
def last_log
|
20
|
+
log = '/tmp/.rubypodder/rp.log'
|
21
|
+
File.exists?(log) ? File.mtime('/tmp/.rubypodder/rp.log') : Time.at(0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def exits_without_doing_anything(&b)
|
25
|
+
raise "Expected block!" unless block_given?
|
26
|
+
before = last_log
|
27
|
+
b.call
|
28
|
+
after = last_log
|
29
|
+
before == after
|
30
|
+
end
|
31
|
+
|
32
|
+
class TC_stdout < Test::Unit::TestCase
|
33
|
+
def setup
|
34
|
+
ENV['HOME'] = "/tmp"
|
35
|
+
@bindir = File.join(File.dirname(__FILE__), "..", "bin")
|
36
|
+
end
|
37
|
+
|
38
|
+
def teardown
|
39
|
+
system("rm -rf " + "/tmp/.rubypodder")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_version
|
43
|
+
assert_equal "rubypodder v1.0.0\n", wrap { puts `ruby #{@bindir}/rubypodder --version` }
|
44
|
+
assert exits_without_doing_anything { `ruby #{@bindir}/rubypodder --version` }, "--version doesn't exit immediately"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_help
|
48
|
+
assert_match %r{See http://rubypodder.rubyforge.org/}, wrap { puts `ruby #{@bindir}/rubypodder --help` }
|
49
|
+
assert exits_without_doing_anything { `ruby #{@bindir}/rubypodder --help` }, "--help doesn't exit immediately"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: matthewgarysmith-rubypodder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lex Miller
|
8
|
+
autorequire: rubypodder
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-24 00:00:00 -08:00
|
13
|
+
default_executable: rubypodder
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rio
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rake
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mocha
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
description:
|
43
|
+
email: lex.miller @nospam@ gmail.com
|
44
|
+
executables:
|
45
|
+
- rubypodder
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README
|
50
|
+
- MIT-LICENSE
|
51
|
+
files:
|
52
|
+
- lib/rubypodder.rb
|
53
|
+
- tests/tc_rubypodder.rb
|
54
|
+
- tests/ts_rubypodder.rb
|
55
|
+
- tests/tc_stdout.rb
|
56
|
+
- Rakefile
|
57
|
+
- README
|
58
|
+
- MIT-LICENSE
|
59
|
+
has_rdoc: true
|
60
|
+
homepage:
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.2.0
|
82
|
+
signing_key:
|
83
|
+
specification_version: 2
|
84
|
+
summary: A podcast aggregator without an interface
|
85
|
+
test_files:
|
86
|
+
- tests/tc_rubypodder.rb
|
87
|
+
- tests/tc_stdout.rb
|
88
|
+
- tests/ts_rubypodder.rb
|