rubypodder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: