dns323 podcast and torrent downloader

I’ve expanded on my previous podcast downloader program so that it gets torrents as well. I was running Automatic but it seemed silly to have two programs doing more or less the same thing.

I also didn’t like the configuration of Automatic, which has a global set of regexps it applies to all feeds to decide whether to download an item. My program has separate regexps for each feed1 which I think is a bit easier to deal with. In order to make the config file easy to parse, it is actually another Ruby file, which is read with instance_eval. By having methods defined as feed and matches the config file can just use those and look relatively sane:

feed 'http://some.feed.url/blah.rss'
matches 'title.to.match.*\d+'

(Some people would call this a DSL, but I think that’s stretching a point)

To make the torrents get into Transmission, the torrent downloader we’re using, I just call the transmission-remote command. Sometimes it’s easier to use what works.

So here’s the full program (see my previous post for how to set things up on the DNS-323):

#!/ffp/bin/env ruby
 
require 'open-uri'
require 'rss'
 
CONFFILE='/ffp/etc/mypodder2.conf'
PODCASTDIR='/mnt/HD_a2/podcasts'
TORRENTDIR='/mnt/HD_a2/torrents'
TORRENT_UPLOAD='/ffp/bin/transmission-remote "127.0.0.1:9091" -a "%s"'
 
class ConfigParser
	def initialize(feeds)
		@feeds = feeds
	end
 
	def parse(file)
		instance_eval(File.read(file), file)
	end
 
	def feed(url)
		@current = Feed.new(url)
		@feeds << @current
	end
 
	def matches(pattern)
		@current.add_pattern(pattern) if @current
	end
 
	alias :or :matches
 
end
 
class Feed
	def initialize(url)
		@url = url
		@patterns = []
	end
 
	def uri
		@url
	end
 
	def add_pattern(pattern)
		if(pattern && pattern.length > 0)
			@patterns << Regexp.new(pattern, true)
		end
	end
 
	def allow?(item)
		icompare = item.title
		@patterns.empty? || @patterns.find {|p| p =~ icompare}
	end
end
 
class Main
	def initialize(options = {})
		@options = {:config => CONFFILE, :verbose => false}.merge(options)
		@feeds = []
		read_config
	end
 
	def read_config
		ConfigParser.new(@feeds).parse(@options[:config])
	end
 
	def vprintln(sometext)
		if(@options[:verbose])
			puts sometext
		end
	end
 
	def torrent?(item)
		item.enclosure.type =~ /.*torrent/
	end
 
	def run()
@feeds.each do |feed|
    vprintln "checking feed #{feed.uri}"
    rss_content = ""
    open(feed.uri) do |f|
        rss_content = f.read
    end
    rss = RSS::Parser.parse(rss_content, false)
 
    rss.items.each do |item|
        vprintln " + checking item #{item.title}"
        if(item.enclosure && feed.allow?(item))
            iurl = item.enclosure.url
            vprintln " + + allowed item #{iurl}"
            ctitle = rss.channel.title.gsub(/\W/, '_')
		base = PODCASTDIR
		base = TORRENTDIR if torrent?(item)
            system("mkdir -p #{File.join(base, ctitle)}")
            system("chmod a+rw #{File.join(base, ctitle)}")
            ifile = File.join(base, ctitle, File.basename(iurl))
            unless File.exists?(ifile)
            	puts "About to download #{iurl} as #{ifile}"
            	system("wget -O #{ifile} #{iurl}")
 
		if(torrent?(item) && File.exists?(ifile))
			system(sprintf(TORRENT_UPLOAD, ifile))
		end
            else
            	vprintln " + + file exists"
            end
        end
    end                                
end
	end
end
 
if __FILE__ == $0
	require 'optparse'
 
	options = {}
 
	opts = OptionParser.new do |op|
		op.on("-f CONFIG", "--config CONFIG", "Specify config file (default #{CONFFILE})") do |f|
		      options[:config] = f
	        end
 
		op.on("-v", "--verbose", "Output messages") do
		      options[:verbose] = true
	        end
 
	        # No argument, shows at tail.  This will print an options summary.
	        op.on_tail("-h", "--help", "Show this message") do
	              puts opts
	              exit
	        end
 
	end
 
	opts.parse(ARGV)
	Main.new(options).run
end

1 At the moment, it ‘OR’s together multiple regexps on a single feed, but I’m thinking I may change that so it’s easier to exclude certain matches as well as include.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*