rubypodder 0.1.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.
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,69 @@
1
+ = rubypodder - A podcast aggregator without an interface
2
+
3
+ This package contains rubypodder, a simple podcast receiver
4
+ inspired by bashpodder (http://linc.homeunix.org:8080/scripts/bashpodder/).
5
+
6
+ == Download
7
+
8
+ The latest version of rubypodder can be found at
9
+
10
+ http://rubyforge.org/projects/rubypodder/
11
+
12
+ == Installation
13
+
14
+ Place the rubypodder gem in your local repository and install with
15
+ the following:
16
+
17
+ gem install rubypodder
18
+
19
+ == Quick Start
20
+
21
+ Type
22
+
23
+ rubypodder
24
+
25
+ When it finishes, you should see a ~/.rubypodder directory.
26
+ This should contain an example rp.conf configuration file with a feed in
27
+ it to get you started. If there were any podcasts to download in the feed
28
+ they will be in a directory named after the date (e.g. 2007-01-20).
29
+ You can see what happened by looking in rp.log.
30
+
31
+ == Running the rubypodder Test Suite
32
+
33
+ If you wish to run the unit tests that come with rubypodder:
34
+
35
+ gem check rubypodder --test
36
+
37
+ A message will be given if any of the tests fail.
38
+
39
+ === Uninstallation
40
+
41
+ To uninstall the rubypodder gem use:
42
+
43
+ gem uninstall rubypodder
44
+
45
+ == Configuration
46
+
47
+ Create a file +~/.rubypodder/rp.conf+ containing podcast feeds, one per line.
48
+
49
+ == Usage
50
+
51
+ Type
52
+
53
+ rubypodder
54
+
55
+ and if there are any podcast episodes from your feeds that have not yet
56
+ been downloaded, they will be downloaded into a "date" directory such as
57
+ +~/.rubypodder/2007-01-18/+
58
+
59
+ A good idea is to use +crontab -e+ and add a line like
60
+ +0 5 * * * rubypodder+
61
+ which will set up a cron job which will do
62
+ this regularly.
63
+
64
+ == License
65
+
66
+ rubypodder is available under an MIT-style license.
67
+
68
+ :include: MIT-LICENSE
69
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
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 = "0.1.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.test_files = Dir.glob('tests/*.rb')
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "MIT-LICENSE"]
21
+ end
22
+ Rake::GemPackageTask.new(spec) do |pkg|
23
+ pkg.need_tar = true
24
+ end
25
+ task :default => "pkg/#{spec.name}-#{spec.version}.gem" do
26
+ puts "generated latest version"
27
+ end
data/bin/rubypodder ADDED
@@ -0,0 +1,2 @@
1
+ require 'rubypodder'
2
+ RubyPodder.new.run
data/lib/rubypodder.rb ADDED
@@ -0,0 +1,113 @@
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
+ attr_reader :conf_file, :log_file, :done_file, :date_dir
21
+
22
+ def initialize(file_base="~/.rubypodder/rp")
23
+ @file_base = File.expand_path(file_base)
24
+ @rp_dir = File.dirname(@file_base)
25
+ @conf_file = @file_base + ".conf"
26
+ @log_file = @file_base + ".log"
27
+ @done_file = @file_base + ".done"
28
+ create_default_config_file
29
+ @log = Logger.new(@log_file)
30
+ File.touch @done_file
31
+ @date_dir = create_date_dir
32
+ end
33
+
34
+ def create_default_config_file
35
+ expanded_path = File.expand_path(@conf_file)
36
+ return if File.exists?(expanded_path)
37
+ make_dirname(expanded_path)
38
+ rio(expanded_path) < "http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml"
39
+ end
40
+
41
+ def make_dirname(full_filename)
42
+ dirname = File.dirname(full_filename)
43
+ File.makedirs dirname
44
+ end
45
+
46
+ def date_string(time)
47
+ time.strftime("%Y-%m-%d")
48
+ end
49
+
50
+ def create_date_dir
51
+ date_dir = @rp_dir + "/" + date_string(Time.now)
52
+ File.makedirs date_dir
53
+ date_dir
54
+ end
55
+
56
+ def read_feeds
57
+ IO.readlines(@conf_file).each {|l| l.chomp!}
58
+ end
59
+
60
+ def parse_rss(rss_source)
61
+ RSS::Parser.parse(rss_source, false)
62
+ end
63
+
64
+ def dest_file_name(url)
65
+ @date_dir + "/" + File.basename(URI.parse(url).path)
66
+ end
67
+
68
+ def record_download(url)
69
+ rio(@done_file) << "#{url}\n"
70
+ end
71
+
72
+ def already_downloaded(url)
73
+ url_regexp = Regexp.new(url)
74
+ File.open(@done_file).grep(url_regexp).length > 0
75
+ end
76
+
77
+ def download(url)
78
+ return if already_downloaded(url)
79
+ @log.info(" Downloading: #{url}")
80
+ file_name = dest_file_name(url)
81
+ rio(file_name) < rio(url)
82
+ record_download(url)
83
+ end
84
+
85
+ def remove_dir_if_empty(dirname)
86
+ begin
87
+ Dir.rmdir(dirname)
88
+ rescue SystemCallError
89
+ @log.info("#{dirname} has contents, not removed")
90
+ else
91
+ @log.info("#{dirname} was empty, removed")
92
+ end
93
+ end
94
+
95
+ def run
96
+ @log.info("Starting")
97
+ read_feeds.each do |url|
98
+ http_body = rio(url).contents
99
+ rss = parse_rss(http_body)
100
+ @log.info("Channel: #{rss.channel.title}")
101
+ rss.items.each do |item|
102
+ download(item.enclosure.url)
103
+ end
104
+ end
105
+ remove_dir_if_empty(@date_dir)
106
+ @log.info("Finished")
107
+ end
108
+
109
+ end
110
+
111
+ if $0 == __FILE__
112
+ RubyPodder.new.run
113
+ end
@@ -0,0 +1,199 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+ require 'test/unit'
3
+ require 'rubypodder'
4
+
5
+ class TC_RubyPodder < Test::Unit::TestCase
6
+
7
+ def setup
8
+ system("rm -rf " + "/tmp/test_rp.conf")
9
+ system("rm -rf " + "/tmp/test_rp.done")
10
+ File.open("/tmp/test_rp.conf", "w") do |file|
11
+ file.write("http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml\n")
12
+ file.write("http://www.guardian.co.uk/podcasts/comedy/rickygervais/mp3.xml")
13
+ end
14
+ @subdir = "/tmp/subdir"
15
+ system("rm -rf " + @subdir)
16
+ @rp = RubyPodder.new("/tmp/test_rp")
17
+ end
18
+
19
+ def teardown
20
+ system("rm -rf " + "/tmp/test_rp.conf")
21
+ system("rm -rf " + "/tmp/test_rp.done")
22
+ system("rm -rf " + "/tmp/test_rp.log")
23
+ system("rm -rf " + "/tmp/#{@rp.date_string(Time.now)}")
24
+ system("rm -rf " + @subdir)
25
+
26
+ end
27
+
28
+ def test_initialize_with_test_config_file
29
+ assert_kind_of(RubyPodder, @rp)
30
+ assert_equal("/tmp/test_rp.conf", @rp.conf_file)
31
+ assert_equal("/tmp/test_rp.log", @rp.log_file)
32
+ assert_equal("/tmp/test_rp.done", @rp.done_file)
33
+ end
34
+
35
+ def test_initialize_with_default_config_file
36
+ ENV['HOME'] = "/tmp"
37
+ @rp = RubyPodder.new()
38
+ assert_kind_of(RubyPodder, @rp)
39
+ assert_equal(ENV['HOME'] + "/.rubypodder/rp.conf", @rp.conf_file)
40
+ assert_equal(ENV['HOME'] + "/.rubypodder/rp.log", @rp.log_file)
41
+ assert_equal(ENV['HOME'] + "/.rubypodder/rp.done", @rp.done_file)
42
+ system("rm -rf " + "/tmp/.rubypodder")
43
+ end
44
+
45
+ def test_initialize_with_absent_config_file
46
+ file_base = "/tmp/test_rp"
47
+ file_conf = file_base + ".conf"
48
+ system("rm -rf " + file_conf)
49
+ @rp = RubyPodder.new(file_base)
50
+ assert(File.exists?(file_conf))
51
+ system("rm -rf " + file_conf)
52
+ end
53
+
54
+ def test_make_dirname_one_subdir
55
+ filename = @subdir + "/file"
56
+ @rp.make_dirname(filename)
57
+ assert(File.exists?(@subdir))
58
+ end
59
+
60
+ def test_make_dirname_many_subdir
61
+ subsubdirs = "/subsubdir/subsubsubdir"
62
+ filename = @subdir + subsubdirs + "/file"
63
+ @rp.make_dirname(filename)
64
+ assert(File.exists?(@subdir + subsubdirs))
65
+ end
66
+
67
+ def test_date_string
68
+ t = Time.gm(2006,"dec",18,21,0,0)
69
+ date_string = @rp.date_string(t)
70
+ assert_equal(date_string, "2006-12-18")
71
+ end
72
+
73
+ def test_initialize_with_absent_config_file_dir
74
+ file_base = @subdir + "/test_rp"
75
+ file_conf = file_base + ".conf"
76
+ @rp = RubyPodder.new(file_base)
77
+ assert(File.exists?(file_conf))
78
+ end
79
+
80
+ def test_initialize_creates_date_dir
81
+ file_base = @subdir + "/test_rp"
82
+ @rp = RubyPodder.new(file_base)
83
+ date_dir = @subdir + "/" + @rp.date_string(Time.now)
84
+ assert_equal(date_dir, @rp.date_dir)
85
+ assert(File.exists?(date_dir), "Subdirectory #{date_dir} not created")
86
+ end
87
+
88
+ def test_read_feeds
89
+ feed_list = @rp.read_feeds
90
+ assert_kind_of(Array, feed_list)
91
+ assert_equal(2, feed_list.length)
92
+ assert_equal("http://downloads.bbc.co.uk/rmhttp/downloadtrial/radio4/thenowshow/rss.xml", feed_list[0])
93
+ end
94
+
95
+ def test_parse_rss
96
+ #rss_source = IO.read("test_feed.xml")
97
+ rss_source = <<-END_OF_STRING
98
+ <?xml version="1.0" encoding="utf-8"?>
99
+ <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
100
+ <channel>
101
+ <lastBuildDate>Fri, 27 Oct 2006 16:50:15 +0100</lastBuildDate>
102
+ <title>The Ricky Gervais Show</title>
103
+ <itunes:author>Guardian Unlimited</itunes:author>
104
+ <link>http://www.guardian.co.uk/rickygervais</link>
105
+ <generator>Podcast Maker v1.2.4 - http://www.potionfactory.com/podcastmaker</generator>
106
+ <description>Ricky Gervais, Steve Merchant and Karl Pilkington are back yada yada</description>
107
+ <itunes:subtitle />
108
+ <itunes:summary>Ricky Gervais, Steve Merchant and Karl Pilkington are back yada yada</itunes:summary>
109
+ <language>en</language>
110
+ <copyright>Ricky Gervais, Steve Merchant, Karl Pilkington</copyright>
111
+ <itunes:owner>
112
+ <itunes:name>Glyn Hughes</itunes:name>
113
+ <itunes:email>glyn@rickygervais.com</itunes:email>
114
+ </itunes:owner>
115
+ <image>
116
+ <url>http://podcast.rickygervais.com/gu_p123_300_144.jpg</url>
117
+ <title>The Ricky Gervais Show</title>
118
+ <link>http://www.guardian.co.uk/rickygervais</link>
119
+ <width>144</width>
120
+ <height>144</height>
121
+ </image>
122
+ <itunes:image href="http://podcast.rickygervais.com/gu_p123_300.jpg" />
123
+ <category>Comedy</category>
124
+ <itunes:category text="Comedy" />
125
+ <itunes:keywords>Ricky, Gervais, Steve, Merchant, Karl, Pilkington</itunes:keywords>
126
+ <itunes:explicit>yes</itunes:explicit>
127
+ <item>
128
+ <title>The Podfather Part I - Halloween</title>
129
+ <itunes:author>Guardian Unlimited</itunes:author>
130
+ <description>The first of three specials from Ricky, Steve and Karl yada yada</description>
131
+ <itunes:subtitle>The first of three specials from Ricky, Steve and Karl yada yada</itunes:subtitle>
132
+ <itunes:summary />
133
+ <enclosure type="audio/mpeg" url="http://podcast.rickygervais.com/guspecials_halloween.mp3" length="15723925" />
134
+ <guid>http://podcast.rickygervais.com/guspecials_halloween.mp3</guid>
135
+ <pubDate>Tue, 31 Oct 2006 00:00:01 +0000</pubDate>
136
+ <category>Comedy</category>
137
+ <itunes:explicit>yes</itunes:explicit>
138
+ <itunes:duration>00:37:22</itunes:duration>
139
+ <itunes:keywords>Ricky, Gervais, Steve, Merchant, Karl, Pilkington</itunes:keywords>
140
+ </item>
141
+ </channel>
142
+ </rss>
143
+ END_OF_STRING
144
+ rss = @rp.parse_rss(rss_source)
145
+ assert_equal(1, rss.items.length)
146
+ assert_equal("http://podcast.rickygervais.com/guspecials_halloween.mp3", rss.items[0].enclosure.url)
147
+ end
148
+
149
+ def test_dest_file_name(url)
150
+ correct_dest_file_name = @rp.date_dir + "/" + "podcast.mp3"
151
+ dest_file_name = @rp.dest_file_name("http://www.podcast.com/podcast.mp3")
152
+ assert_equal(correct_dest_file_name, dest_file_name)
153
+ dest_file_name = @rp.dest_file_name("http://www.podcast.com/subdir/podcast.mp3")
154
+ assert_equal(correct_dest_file_name, dest_file_name)
155
+ end
156
+
157
+ def test_download
158
+ @rp.download("http://www.google.com/index.html")
159
+ dest_file = @rp.date_dir + "/" + "index.html"
160
+ assert(File.exists?(dest_file))
161
+ assert(File.open(dest_file).grep(/google/))
162
+ end
163
+
164
+ def test_download_recorded
165
+ @rp.download("http://www.google.com/index.html")
166
+ assert(File.exists?(@rp.done_file))
167
+ assert(File.open(@rp.done_file).grep(/^http:\/\/www.google.com\/index.html$/))
168
+ end
169
+
170
+ def test_already_downloaded
171
+ url1 = "http://www.google.com/index.html"
172
+ assert(!@rp.already_downloaded(url1), "url1 should not be already downloaded before download of url1")
173
+ @rp.download(url1)
174
+ assert(@rp.already_downloaded(url1), "url1 should be already downloaded after download of url1")
175
+ url2 = "http://www.google.co.nz/index.html"
176
+ @rp.download(url2)
177
+ assert(@rp.already_downloaded(url2), "url2 should be already downloaded after download of url2")
178
+ assert(@rp.already_downloaded(url1), "url1 should still be already downloaded after download of url2")
179
+ end
180
+
181
+ def test_download_omits_done_items
182
+ @rp.download("http://www.google.com/index.html")
183
+ dest_file = @rp.date_dir + "/" + "index.html"
184
+ system("rm -rf " + dest_file)
185
+ @rp.download("http://www.google.com/index.html")
186
+ assert(!File.exists?(dest_file))
187
+ end
188
+
189
+ def test_remove_dir_if_empty
190
+ system("mkdir -p " + @subdir)
191
+ @rp.remove_dir_if_empty(@subdir)
192
+ assert(!File.exists?(@subdir))
193
+ system("mkdir -p " + @subdir)
194
+ system("touch " + @subdir + "/fish")
195
+ @rp.remove_dir_if_empty(@subdir)
196
+ assert(File.exists?(@subdir))
197
+ end
198
+
199
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: rubypodder
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2007-01-20 00:00:00 +13:00
8
+ summary: A podcast aggregator without an interface
9
+ require_paths:
10
+ - lib
11
+ email: lex.miller @nospam@ gmail.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: rubypodder
16
+ default_executable: rubypodder
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Lex Miller
31
+ files:
32
+ - lib/rubypodder.rb
33
+ - tests/tc_rubypodder.rb
34
+ - Rakefile
35
+ - README
36
+ - MIT-LICENSE
37
+ test_files:
38
+ - tests/tc_rubypodder.rb
39
+ rdoc_options: []
40
+ extra_rdoc_files:
41
+ - README
42
+ - MIT-LICENSE
43
+ executables:
44
+ - rubypodder
45
+ extensions: []
46
+ requirements: []
47
+ dependencies:
48
+ - !ruby/object:Gem::Dependency
49
+ name: rio
50
+ version_requirement:
51
+ version_requirements: !ruby/object:Gem::Version::Requirement
52
+ requirements:
53
+ -
54
+ - ">"
55
+ - !ruby/object:Gem::Version
56
+ version: 0.0.0
57
+ version: