overdrive 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,57 @@
1
+ Overdrive
2
+ =========
3
+
4
+ Overdrive is a RSS frontend for the transmission bit torrent client. It has hooks to make downloading TV series
5
+ much easier, and allows recipes with callbacks to make filtering show much easier.
6
+
7
+ Installation
8
+ ------------
9
+
10
+ Overdrive is a ruby gem that uses gemcutter. To install, simply do the following:
11
+
12
+ sudo gem install gemcutter
13
+ sudo gem tumble
14
+
15
+ then
16
+
17
+ sudo gem install overdrive
18
+
19
+ This should install the overdrive binary in your path.
20
+
21
+ Usage
22
+ -----
23
+
24
+ To do anything useful, you need to setup a recipe. You can store the recipe (in Linux) at
25
+
26
+ /etc/overdrive.rb
27
+ /var/transmission/config/overdrive.rb
28
+ $HOME/.overdrive/overdrive.rb
29
+
30
+ and you can add paths via the -c argument:
31
+
32
+ overdrive -c /path/to/recipe.rb
33
+
34
+ A sample recipe might look like this:
35
+
36
+ add_feed "http://www.mytorrents.com/torrents.rss"
37
+ add_feed "http://www.myothertorrents.com/torrents.rss"
38
+
39
+ add_title "24"
40
+ add_title "30 Rock"
41
+ add_title "Alias"
42
+
43
+ filter do |item|
44
+ parsed = parse_metadata(item)
45
+ if parsed[:title] && parsed[:series] && parsed[:episode]
46
+ title = parsed[:title].split(' ').map { |t| t[0..0].upcase + t[1..-1] }.join(' ')
47
+
48
+ if titles.include?(title)
49
+ target = "/videos/#{title}/Season #{parsed[:series].to_i.to_s}"
50
+ download(item.url, :download_dir => target)
51
+ end
52
+ end
53
+ end
54
+
55
+ You can get a full list of command line arguments by running
56
+
57
+ overdrive -h
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "overdrive"
5
+ s.executables = "overdrive"
6
+ s.summary = "An RSS frontend for transmission"
7
+ s.email = "myles@madpilot.com.au"
8
+ s.homepage = "http://github.com/madpilot/overdrive"
9
+ s.description = "An RSS frontend for transmissions. It defines a basic DSL that allows you decide what you do once the files have been downloaded"
10
+ s.authors = ["Myles Eftos"]
11
+ s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
12
+
13
+ s.add_dependency 'daemons'
14
+ s.add_dependency 'transmission-client'
15
+ s.add_dependency 'SyslogLogger'
16
+
17
+ Jeweler::GemcutterTasks.new
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/overdrive ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(File.expand_path(__FILE__)), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'syslog_logger'
6
+ require 'daemons'
7
+ require 'options'
8
+ require 'feed'
9
+ require 'torrent'
10
+ require 'lexicon'
11
+ require 'dsl'
12
+ require 'transmission-client'
13
+
14
+ options = Options.parse
15
+
16
+ if options[:ontop]
17
+ options[:logger] = Logger.new(STDOUT)
18
+ else
19
+ options[:logger] = SyslogLogger.new('overdrive')
20
+ end
21
+
22
+ Daemons.call(options) do
23
+ @last_run = nil
24
+ @feed = Feed.new(options)
25
+
26
+ options[:logger].info("Running every #{options[:interval].to_s} minute#{options[:interval] == 1 ? "" : "s"}")
27
+ loop {
28
+ if @last_run == nil || (@last_run.to_i + options[:interval] * 60) <= Time.now.to_i
29
+ @feed.check
30
+ @last_run = Time.now
31
+ end
32
+ sleep(1)
33
+ }
34
+ end
data/lib/dsl.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'transmission-client'
2
+
3
+ class DSL
4
+ attr_accessor :titles
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ @items, @torrents, @feeds, @titles = [], [], [], []
9
+
10
+ contents = ''
11
+ options[:filter_paths].each do |path|
12
+ begin
13
+ File.open(path, 'r') do |fh|
14
+ @options[:logger].info "Adding #{path} to recipe list"
15
+ contents += fh.read
16
+ end
17
+ rescue => e
18
+ @options[:logger].info "Couldn't add #{path} to recipe list: #{e}"
19
+ end
20
+ end
21
+
22
+ eval <<-END
23
+ def run_dsl(obj = nil, type = nil)
24
+ @items = []
25
+ @torrents = []
26
+
27
+ case(type)
28
+ when :filter
29
+ @items = obj
30
+ when :torrent
31
+ @torrents = obj
32
+ end
33
+
34
+ #{contents}
35
+ end
36
+ END
37
+
38
+ # Prime the DSL
39
+ @primed = false
40
+ run_dsl
41
+ @primed = true
42
+ end
43
+
44
+ def perform_filter(items)
45
+ run_dsl(items, :filter)
46
+ end
47
+
48
+ def download_complete(torrents)
49
+ run_dsl(torrents, :torrent)
50
+ end
51
+
52
+ def add_feed(feed)
53
+ @feeds << feed unless @primed
54
+ end
55
+
56
+ def feeds
57
+ @feeds
58
+ end
59
+
60
+ def add_title(title)
61
+ @titles << title unless @primed
62
+ end
63
+
64
+ def parse_metadata(item)
65
+ Lexicon.parse(item)
66
+ end
67
+
68
+ def log
69
+ options[:logger]
70
+ end
71
+
72
+ def options
73
+ @options
74
+ end
75
+
76
+ def download(link, options = {})
77
+ Torrent.download(link, options, self.options)
78
+ end
79
+
80
+ def filter &block
81
+ @items.each { |item| yield(item) }
82
+ end
83
+
84
+ def after &block
85
+ @torrents.each { |torrent| yield(torrent) }
86
+ end
87
+ end
data/lib/feed.rb ADDED
@@ -0,0 +1,75 @@
1
+ require 'feedzirra'
2
+ require 'yaml'
3
+
4
+ class Feed
5
+ def initialize(options)
6
+ @options = options
7
+ @dsl = DSL.new(@options)
8
+ end
9
+
10
+ def check
11
+ @results ? update_feeds : check_feeds
12
+ check_downloads
13
+ end
14
+
15
+ def check_feeds
16
+ number_processed = 0
17
+ feeds = @dsl.feeds
18
+ feed_size = feeds.size
19
+
20
+ begin
21
+ @options[:logger].info "Fetching #{feed_size} feed#{feed_size == 1 ? "" : "s"}" if @options[:verbose]
22
+
23
+ @results = Feedzirra::Feed.fetch_and_parse(feeds)
24
+
25
+ @options[:logger].info "Processing #{feed_size} feed#{feed_size == 1 ? "" : "s"}" if @options[:verbose]
26
+
27
+ @results.each do |url, rss|
28
+ if rss.is_a?(Fixnum)
29
+ @options[:logger].error "Couldn't download #{url}"
30
+ else
31
+ @dsl.perform_filter(rss.entries)
32
+ number_processed += 1
33
+ end
34
+ end
35
+ rescue => e
36
+ @options[:logger].error "#{e}"
37
+ end
38
+
39
+ @options[:logger].info "Processed #{number_processed} out of #{feed_size} feed#{feed_size == 1 ? "" : "s"}" if @options[:verbose]
40
+ return number_processed
41
+ end
42
+
43
+ def update_feeds
44
+ number_processed = 0
45
+ number_skipped = 0
46
+ feeds = @results
47
+ feed_size = feeds.size
48
+
49
+ begin
50
+ @options[:logger].info "Fetching #{feed_size} feed#{feed_size ? "" : "s"}" if @options[:verbose]
51
+
52
+ results = Feedzirra::Feed.update(feeds.values)
53
+ @options[:logger].info "Processing #{feed_size} feed#{feed_size ? "" : "s"}" if @options[:verbose]
54
+
55
+ if results.updated?
56
+ @dsl.perform_filter(results.new_entries)
57
+ else
58
+ number_skipped += 1
59
+ end
60
+ number_processed += 1
61
+ rescue => e
62
+ @options[:logger].error "#{e}"
63
+ end
64
+
65
+ @options[:logger].info "Processed #{number_processed} out of #{feed_size} feed#{feed_size ? "" : "s"} (#{number_skipped} skipped)" if @options[:verbose]
66
+ return number_processed
67
+ end
68
+
69
+ def check_downloads
70
+ return if @options[:dry_run]
71
+ @options[:logger].info "Checking Download Status" if @options[:verbose]
72
+ t = Transmission::Client.new(@options[:transmission_server], @options[:transmission_port])
73
+ @dsl.download_complete(t.torrents)
74
+ end
75
+ end
data/lib/lexicon.rb ADDED
@@ -0,0 +1,111 @@
1
+ class Lexicon
2
+ def self.parse(item)
3
+ if item.is_a?(String)
4
+ title = item
5
+ else
6
+ title = item.title
7
+ end
8
+
9
+ parsed = {
10
+ :title => nil,
11
+ :series => nil,
12
+ :episode => nil,
13
+ :size => nil,
14
+ :publish_date => nil,
15
+ :high_def => false
16
+ }
17
+
18
+ tokens = title.split(/\.| /)
19
+ titles = []
20
+ state = :title
21
+
22
+ while tokens.length > 0
23
+ token = tokens.shift
24
+
25
+ case(state)
26
+ when :title
27
+ if token =~ /s(\d+)e(\d+)/i
28
+ parsed[:series] = Regexp.last_match[1].to_i
29
+ parsed[:episode] = Regexp.last_match[2].to_i
30
+ state = :done
31
+ elsif token =~ /^(\d)x?(\d\d)$/i
32
+ parsed[:series] = Regexp.last_match[1].to_i
33
+ parsed[:episode] = Regexp.last_match[2].to_i
34
+ state = :done
35
+ elsif token =~ /^s(\d+)$/i
36
+ parsed[:series] = Regexp.last_match[1].to_i
37
+ state = :series
38
+ else
39
+ titles << token
40
+ end
41
+ when :series
42
+ if token =~ /^e(\d+)$/i
43
+ parsed[:episode] = Regexp.last_match[1].to_i
44
+ state = :done
45
+ end
46
+ when :done
47
+ if token =~ /HDTV|720p|1080|mkv/i
48
+ parsed[:high_def] = true
49
+ end
50
+ end
51
+ end
52
+
53
+ # Reparse to look for some hd/sd info
54
+ if state != :done
55
+ tokens = title.split(/\.| /)
56
+ titles = [] if state == :title
57
+
58
+ while tokens.length > 0
59
+ token = tokens.shift
60
+ case(state)
61
+ when :title
62
+ if token =~ /HDTV|720p|1080|mkv/i
63
+ parsed[:high_def] = true
64
+ state = :done
65
+ else
66
+ titles << token
67
+ end
68
+ when :series
69
+ if token =~ /HDTV|720p|1080|mkv/i
70
+ parsed[:high_def] = true
71
+ state = :done
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # Try to split on some common lines
78
+ if state != :done
79
+ tokens = title.split(/\.| /)
80
+ titles = [] if state == :title
81
+
82
+ while tokens.length > 0
83
+ token = tokens.shift
84
+ case(state)
85
+ when :title
86
+ if token =~ /vid|DVDRip|xvid|divx|repack|internal/i
87
+ state = :done
88
+ elsif token =~ /avi|ts|mkv|mpg/i
89
+ state = :done
90
+ else
91
+ titles << token
92
+ end
93
+ when :series
94
+ if token =~ /vid|DVDRip|xvid|divx|repack|internal/i
95
+ state = :done
96
+ elsif token =~ /avi|ts|mkv|mpg/i
97
+ state = :done
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ if state == :title
104
+ titles = title.split(/\.| /)
105
+ end
106
+
107
+ parsed[:title] = titles.join(' ')
108
+ parsed[:publish_date] = item.published if item.published
109
+ return parsed
110
+ end
111
+ end
data/lib/options.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'optparse'
2
+
3
+ class Options
4
+ def self.parse
5
+ options = {
6
+ :multiple => false
7
+ }
8
+
9
+ optparse = OptionParser.new do |opts|
10
+ opts.banner = "Usage: #{opts.program_name} [options]"
11
+
12
+ options[:verbose] = false
13
+ opts.on('-V', '--verbose', 'Be more verbose') do
14
+ options[:verbose] = true
15
+ end
16
+
17
+ options[:dry_run] = false
18
+ opts.on('-d', '--dry-run', "Don't actually queue torrents up") do |dry_run|
19
+ options[:dry_run] = true
20
+ end
21
+
22
+ options[:interval] = 15
23
+ opts.on('-i [n]', '--interval [n]', /\d+/, 'Number of minutes between checks (Default: 15)') do |interval|
24
+ options[:interval] = interval.to_i
25
+ end
26
+
27
+ opts.on('-h', '--help', "You're looking at it") do
28
+ puts opts
29
+ exit(-1)
30
+ end
31
+
32
+ options[:ontop] = false
33
+ opts.on('-f', '--foreground', "Run in the foreground") do
34
+ options[:ontop] = true
35
+ end
36
+
37
+ options[:filter_paths] = [ File.join('', File.dirname(__FILE__), '..', 'recipe.rb'), File.join('', 'etc', 'overdrive.rb'), File.join('', 'var' , 'transmission' , 'config' , 'overdrive.rb') ]
38
+ options[:filter_paths] << File.join(ENV['HOME'], '.overdrive', 'overdrive.rb') if ENV['HOME']
39
+ options[:filter_paths] += ENV['OVERDRIVE_RECIPES'].split(':') if ENV['OVERDRIVE_RECIPES']
40
+
41
+ opts.on('-r [recipe]', '--recipes [recipe]', /.+/, 'Reads in additional recipes') do |recipes|
42
+ options[:filter_paths] << recipes
43
+ end
44
+
45
+ options[:transmission_server] = 'localhost'
46
+ opts.on('-s [server]', '--transmission_server [server]', /.+/, 'Address of the transmission server (Default: localhost)') do |server|
47
+ options[:transmission_server] = server
48
+ end
49
+
50
+ options[:transmission_port] = 9091
51
+ opts.on('-p [port]', '--transmission_port', /\d+/, 'Port of the transmission server (Default: 9091)') do |port|
52
+ options[:transmission_port] = port.to_i
53
+ end
54
+
55
+ opts.on('-v', '--version') do
56
+ File.open(File.join(File.dirname(__FILE__), '..', 'VERSION')) do |fh|
57
+ puts fh.read
58
+ end
59
+ exit(0)
60
+ end
61
+ end
62
+
63
+ begin
64
+ optparse.parse!
65
+ options
66
+ rescue OptionParser::InvalidOption => e
67
+ puts optparse
68
+ exit(-1)
69
+ end
70
+ end
71
+ end
data/lib/torrent.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'open-uri'
2
+ require 'base64'
3
+
4
+ class Torrent
5
+ include OpenURI::OpenRead
6
+
7
+ def self.download(link, http_options = {}, options = {})
8
+ return if options[:dry_run]
9
+
10
+ uri = URI.parse(link)
11
+ download_dir = http_options.delete(:download_dir)
12
+ torrent = uri.read(http_options)
13
+ t = Transmission::Client.new(options[:transmission_server], options[:transmission_port])
14
+ t.add_torrent('metainfo' => Base64.encode64(torrent), 'download-dir' => download_dir)
15
+ end
16
+ end
@@ -0,0 +1,200 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1" ?>
2
+ <rss version="2.0">
3
+ <channel>
4
+ <ttl>10</ttl>
5
+ <title>My Torrent Site</title>
6
+ <link>http://www.mytorrentsite.com</link>
7
+ <description>This is a dummy torrent site</description>
8
+ <language>en-usde</language>
9
+ <item><title>dollhouse.s02e08.internal.hdtv.xvid-2hd.avi</title>
10
+ <link>http://www.mytorrentsite.com/download/dollhouse.s02e08.internal.hdtv.xvid-2hd.avi.torrent</link>
11
+
12
+ <description>
13
+ Category: Dollhouse
14
+ Size: 349.30 MB
15
+ </description>
16
+ </item>
17
+ <item><title>Ghost.Adventures.S03E07.HDTV.XviD-CRiMSON</title>
18
+ <link>http://www.mytorrentsite.com/download/ghost.adventures.s03e07.hdtv.xvid-crimson.avi.torrent</link>
19
+ <description>
20
+ Category: (Reality TV - Un-scripted)
21
+ Size: 350.15 MB
22
+ </description>
23
+ </item>
24
+ <item><title>Dollhouse.S02E08.720p.HDTV.x264-IMMERSE[NUKED]</title>
25
+
26
+ <link>http://www.mytorrentsite.com/download/dollhouse.s02e08.720p.hdtv.x264-immerse.mkv.torrent</link>
27
+ <description>
28
+ Category: Dollhouse
29
+ Size: 1.09 GB
30
+ </description>
31
+ </item>
32
+ <item><title>Sanctuary.US.S02E09.720p.HDTV.x264-ORENJi</title>
33
+ <link>http://www.mytorrentsite.com/download/sanctuary.us.s02e09.720p.hdtv.x264-orenji.mkv.torrent</link>
34
+ <description>
35
+ Category: Sanctuary
36
+ Size: 1.09 GB
37
+ </description>
38
+
39
+ </item>
40
+ <item><title>National.Geographic.Samurai.Sword.720p.HDTV.x264-DiCH</title>
41
+ <link>http://www.mytorrentsite.com/download/dich-national.geographic.ss.720p.hdtv.x264.mkv.torrent</link>
42
+ <description>
43
+ Category: (National Geographic)
44
+ Size: 1.09 GB
45
+ </description>
46
+ </item>
47
+ <item><title>Conan.O'Brien.12.11.2009.Zach.Braff.1080i.AC3.MPEG2-TSR [TS][Transport Stream]</title>
48
+ <link>http://www.mytorrentsite.com/download/conan.o%27brien.12.11.2009.zach.braff.1080i.ac3.mpeg2.tsr.ts.torrent</link>
49
+
50
+ <description>
51
+ Category: Tonight Show
52
+ Size: 4.18 GB
53
+ </description>
54
+ </item>
55
+ <item><title>Dollhouse S02E07 [reencode] [P0W4] [iPod].mp4</title>
56
+ <link>http://www.mytorrentsite.com/download/Dollhouse%20S02E07%20%5Breencode%5D%20%5BP0W4%5D%20%5BiPod%5D.mp4.torrent</link>
57
+ <description>
58
+ Category: (Portable TV Episodes)
59
+ Size: 387.05 MB
60
+ </description>
61
+ </item>
62
+ <item><title>Law.and.Order.S20E11.REPACK.720p.HDTV.x264-IMMERSE</title>
63
+ <link>http://www.mytorrentsite.com/download/law.and.order.s20e11.repack.720p.hdtv.x264-immerse.mkv.torrent</link>
64
+
65
+ <description>
66
+ Category: Law and Order
67
+ Size: 1.09 GB
68
+ </description>
69
+ </item>
70
+ <item><title>Hardcore TV (1994, HBO series)</title>
71
+ <link>http://www.mytorrentsite.com/download/Hardcore%20TV%20%281994%20HBO%20Complete%20Series%29.torrent</link>
72
+ <description>
73
+ Category: (US Comedy)
74
+ Size: 1.88 GB
75
+ </description>
76
+ </item>
77
+ <item><title>yes.virginia.hdtv.xvid-notv.avi</title>
78
+
79
+ <link>http://www.mytorrentsite.com/download/yes.virginia.hdtv.xvid-notv.avi.torrent</link>
80
+ <description>
81
+ Category: (Cartoons)
82
+ Size: 175.16 MB
83
+ </description>
84
+ </item>
85
+ <item><title>Crash.S02E12.HDTV.XviD-RED</title>
86
+ <link>http://www.mytorrentsite.com/download/Crash.S02E12.HDTV.XviD-RED.avi.torrent</link>
87
+ <description>
88
+ Category: (US Drama)
89
+ Size: 550.88 MB
90
+ </description>
91
+
92
+ </item>
93
+ <item><title>Being.Erica.S02.INTERNAL.HDTV.XviD-BitMeTV</title>
94
+ <link>http://www.mytorrentsite.com/download/Being.Erica.S02.INTERNAL.HDTV.XviD-BitMeTV.torrent</link>
95
+ <description>
96
+ Category: (Canadian TV)
97
+ Size: 4.10 GB
98
+ </description>
99
+ </item>
100
+ <item><title>Friday.Night.Lights.S04E06.HDTV.XviD-RED</title>
101
+ <link>http://www.mytorrentsite.com/download/Friday.Night.Lights.S04E06.HDTV.XviD-RED.avi.torrent</link>
102
+
103
+ <description>
104
+ Category: Friday Night Lights
105
+ Size: 350.65 MB
106
+ </description>
107
+ </item>
108
+ <item><title>National.Geographic.Inside.The.Pentagon.Xvid.DVDRip.avi</title>
109
+ <link>http://www.mytorrentsite.com/download/National.Geographic.Inside.The.Pentagon.Xvid.DVDRip.avi.torrent</link>
110
+ <description>
111
+ Category: (National Geographic)
112
+ Size: 700.17 MB
113
+ </description>
114
+ </item>
115
+ <item><title>Sanctuary.US.S02E09.Penance.HDTV.XviD-FQM</title>
116
+ <link>http://www.mytorrentsite.com/download/sanctuary.us.s02e09.hdtv.xvid-fqm.avi.torrent</link>
117
+
118
+ <description>
119
+ Category: Sanctuary
120
+ Size: 350.02 MB
121
+ </description>
122
+ </item>
123
+ <item><title>Million.Dollar.Listing.S03E03.Dolphins.Rats.and.Next.Door.Neighbors.HDTV.XviD-SAiNTS.avi</title>
124
+ <link>http://www.mytorrentsite.com/download/Million.Dollar.Listing.S03E03.Dolphins.Rats.and.Next.Door.Neighbors.HDTV.XviD-SAiNTS.avi.torrent</link>
125
+ <description>
126
+ Category: (Reality TV - Un-scripted)
127
+ Size: 349.30 MB
128
+ </description>
129
+ </item>
130
+ <item><title>Dollhouse.S02E07.Meet.Jane.Doe.720p.AC3.MPEG2-TSR [TS][Transport Stream]</title>
131
+
132
+ <link>http://www.mytorrentsite.com/download/dollhouse.s02e07.meet.jane.doe.720p.ac3.mpeg2.tsr.ts.torrent</link>
133
+ <description>
134
+ Category: Dollhouse
135
+ Size: 4.49 GB
136
+ </description>
137
+ </item>
138
+ <item><title>Dollhouse.S02E07.REPACK.720p.HDTV.x264-IMMERSE</title>
139
+ <link>http://www.mytorrentsite.com/download/dollhouse.s02e07.repack.720p.hdtv.x264-immerse.mkv.torrent</link>
140
+ <description>
141
+ Category: Dollhouse
142
+ Size: 1.09 GB
143
+ </description>
144
+
145
+ </item>
146
+ <item><title>The.Real.World.Road.Rules.Challenge.S18E10.DSR.XviD-CRiMSON</title>
147
+ <link>http://www.mytorrentsite.com/download/the.real.world.road.rules.challenge.s18e10.dsr.xvid-crimson.avi.torrent</link>
148
+ <description>
149
+ Category: (Reality TV - Competitive)
150
+ Size: 348.92 MB
151
+ </description>
152
+ </item>
153
+ <item><title>The.Real.World.Road.Rules.Challenge.S18E09.DSR.XviD-CRiMSON</title>
154
+ <link>http://www.mytorrentsite.com/download/The.Real.World.Road.Rules.Challenge.S18E09.DSR.XviD-CRiMSON.avi.torrent</link>
155
+
156
+ <description>
157
+ Category: (Reality TV - Competitive)
158
+ Size: 348.60 MB
159
+ </description>
160
+ </item>
161
+ <item><title>Ugly.Betty.S04E09.720p.HDTV.x264-CTU</title>
162
+ <link>http://www.mytorrentsite.com/download/ugly.betty.s04e09.720p.hdtv.x264-ctu.mkv.torrent</link>
163
+ <description>
164
+ Category: Ugly Betty
165
+ Size: 1.09 GB
166
+ </description>
167
+ </item>
168
+ <item><title>Ugly.Betty.S04E09.Be-Shure.HDTV.XviD-FQM</title>
169
+ <link>http://www.mytorrentsite.com/download/ugly.betty.s04e09.hdtv.xvid-fqm.avi.torrent</link>
170
+
171
+ <description>
172
+ Category: Ugly Betty
173
+ Size: 350.03 MB
174
+ </description>
175
+ </item>
176
+ <item><title>Dollhouse S02E04 [reencode] [2hd] [iPod].mp4</title>
177
+ <link>http://www.mytorrentsite.com/download/Dollhouse%20S02E04%20%5Breencode%5D%20%5B2hd%5D%20%5BiPod%5D.mp4.torrent</link>
178
+ <description>
179
+ Category: (Portable TV Episodes)
180
+ Size: 383.00 MB
181
+ </description>
182
+ </item>
183
+ <item><title>Dollhouse S02E03 [reencode] [xii] [iPod].mp4</title>
184
+
185
+ <link>http://www.mytorrentsite.com/download/Dollhouse%20S02E03%20%5Breencode%5D%20%5Bxii%5D%20%5BiPod%5D.mp4.torrent</link>
186
+ <description>
187
+ Category: (Portable TV Episodes)
188
+ Size: 388.40 MB
189
+ </description>
190
+ </item>
191
+ <item><title>Maury.2009.12.11.DSR.XviD-sHoTV</title>
192
+ <link>http://www.mytorrentsite.com/download/maury.2009.12.11.dsr.xvid-shotv.avi.torrent</link>
193
+ <description>
194
+ Category: Maury
195
+ Size: 349.14 MB
196
+ </description>
197
+
198
+ </item>
199
+ </channel>
200
+ </rss>
data/test/recipe.rb ADDED
@@ -0,0 +1,2 @@
1
+ add_title('Test Title 1')
2
+ add_title('Test Title 2')
@@ -0,0 +1,28 @@
1
+ $:.unshift File.join(File.dirname(File.expand_path(__FILE__)), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'daemons'
5
+ require 'options'
6
+ require 'feed'
7
+ require 'torrent'
8
+ require 'lexicon'
9
+ require 'dsl'
10
+
11
+ require 'redgreen'
12
+ require 'test/unit'
13
+ require 'mocha'
14
+ require 'shoulda'
15
+ require 'fakefs/safe'
16
+ require 'fakeweb'
17
+
18
+ module TestHelper
19
+ def bitme_rss
20
+ File.open(File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', 'test.rss')) do |rss|
21
+ rss.read
22
+ end
23
+ end
24
+
25
+ def filter_paths
26
+ [ File.join(File.dirname(File.expand_path(__FILE__)), '..', 'receipe.rb') ]
27
+ end
28
+ end
data/test/unit/dsl.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'test_helper'
2
+
3
+ class DslTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ context '' do
7
+ setup do
8
+ @logger = Logger.new(File.open('/dev/null', 'w'))
9
+
10
+ @options = {
11
+ :logger => @logger,
12
+ :filter_paths => filter_paths
13
+ }
14
+
15
+ @feed = Feed.new(@options)
16
+ @dsl = DSL.new(@options)
17
+
18
+ FakeWeb.register_uri(:get, "http://www.mytorrentsite.com/feed.rss", :body => bitme_rss)
19
+ end
20
+
21
+ context 'setup' do
22
+ should 'prime the DSL on initialize' do
23
+ assert @dsl.instance_variable_get(:@primed)
24
+ end
25
+ end
26
+
27
+ context 'loader' do
28
+ should 'define run_dsl dynamically' do
29
+ assert @dsl.respond_to?(:run_dsl)
30
+ end
31
+
32
+ should 'map perform_filter to run_dsl' do
33
+ @items = []
34
+ @dsl.expects(:run_dsl).with(@items, :filter)
35
+ @dsl.perform_filter(@items)
36
+ end
37
+
38
+ should 'map download_complete to run_dsl' do
39
+ @items = []
40
+ @dsl.expects(:run_dsl).with(@items, :torrent)
41
+ @dsl.download_complete(@items)
42
+ end
43
+ end
44
+
45
+ context 'add_title' do
46
+ should 'register a title if @primed is false' do
47
+ @dsl.instance_variable_set(:@primed, false)
48
+ assert 2, @dsl.instance_variable_get(:@titles).size
49
+ @dsl.add_title('Test Title 3')
50
+ assert 3, @dsl.instance_variable_get(:@titles).size
51
+ end
52
+
53
+ should 'not register a title if @primed is true' do
54
+ @dsl.instance_variable_set(:@primed, true)
55
+ assert 2, @dsl.instance_variable_get(:@titles).size
56
+ @dsl.add_title('Test Title 3')
57
+ assert 2, @dsl.instance_variable_get(:@titles).size
58
+ end
59
+ end
60
+
61
+ context 'add_feed' do
62
+ should 'register a feed if @primed is false' do
63
+ @dsl.instance_variable_set(:@primed, false)
64
+ assert 2, @dsl.instance_variable_get(:@feeds).size
65
+ @dsl.add_title('http://www.feedsfeedsfeeds.com/feed.rss')
66
+ assert 3, @dsl.instance_variable_get(:@feeds).size
67
+ end
68
+
69
+ should 'not register a title if @primed is true' do
70
+ @dsl.instance_variable_set(:@primed, true)
71
+ assert 2, @dsl.instance_variable_get(:@feeds).size
72
+ @dsl.add_title('http://www.feedsfeedsfeeds.com/feed.rss')
73
+ assert 2, @dsl.instance_variable_get(:@feeds).size
74
+ end
75
+ end
76
+
77
+ context 'parse_metadata' do
78
+ should 'call Lexicon::parse' do
79
+ item = mock
80
+ ret = mock
81
+ Lexicon.expects(:parse).with(item).returns(ret)
82
+ assert_equal ret, @dsl.parse_metadata(item)
83
+ end
84
+ end
85
+
86
+ context 'download' do
87
+ should 'call Torrent::download' do
88
+ link = mock
89
+ options = mock
90
+ @dsl.stubs(:options).returns({})
91
+ Torrent.expects(:download).with(link, options, {})
92
+ @dsl.download(link, options)
93
+ end
94
+ end
95
+
96
+ context 'log' do
97
+ should 'logs' do
98
+ logger = mock
99
+ options = {:logger => logger}
100
+ @dsl.stubs(:options).returns(options)
101
+ assert_equal logger, @dsl.log
102
+ end
103
+ end
104
+ end
105
+ end
data/test/unit/feed.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'test_helper'
2
+ require 'logger'
3
+
4
+ class FeedTest < Test::Unit::TestCase
5
+ include TestHelper
6
+
7
+ context '' do
8
+ setup do
9
+ @feed_urls = [ 'http://www.mytorrentsite.com/feed.rss' ]
10
+ DSL.any_instance.stubs(:feeds).returns(@feed_urls)
11
+
12
+ @logger = Logger.new(File.open('/dev/null', 'w'))
13
+ @feed = Feed.new(
14
+ {
15
+ :on_top => true,
16
+ :filter_paths => filter_paths,
17
+ :logger => @logger
18
+ })
19
+
20
+ feed = Feedzirra::Feed.parse(bitme_rss)
21
+ feed_ret = {}
22
+ @feed_urls.each { |f| feed_ret[f] = feed }
23
+ Feedzirra::Feed.stubs(:fetch_and_parse).returns(feed_ret)
24
+ end
25
+
26
+ context 'check' do
27
+ setup do
28
+ @transmission_client = mock
29
+ Transmission::Client.stubs(:new).returns(@transmission_client)
30
+ @transmission_client.stubs(:torrents).returns([])
31
+ end
32
+
33
+ should 'call check_feed' do
34
+ DSL.any_instance.expects(:download_complete).with([])
35
+ @feed.expects(:check_feeds)
36
+ @feed.check
37
+ end
38
+ end
39
+
40
+ context 'check_feed' do
41
+ should 'download each RSS feed' do
42
+ DSL.any_instance.expects(:perform_filter).times(@feed_urls.size)
43
+ @logger.expects(:error).never
44
+ assert_equal @feed_urls.size, @feed.check_feeds
45
+ end
46
+ end
47
+
48
+ context 'update_feed' do
49
+ should 'do nothing if there is no updated entries' do
50
+ feed = Feedzirra::Feed.parse(bitme_rss)
51
+ feed.entries.stubs(:values).returns([])
52
+ @feed.instance_variable_set(:@results, feed.entries)
53
+ Feedzirra::Feed.stubs(:update).returns(feed)
54
+ Feedzirra::Feed.stubs(:updated?).returns(false)
55
+ Feedzirra::Feed.stubs(:new_entries).returns([])
56
+
57
+ DSL.any_instance.expects(:perform_filter).never
58
+ @logger.expects(:error).never
59
+ assert_equal 1, @feed.update_feeds
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+
3
+ class LexiconTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ context '' do
7
+ setup do
8
+ @item = mock
9
+ @item.stubs(:published).returns(DateTime.now)
10
+ end
11
+
12
+ should 'parse dollhouse.s02e08.internal.hdtv.xvid-2hd.avi' do
13
+ @item.stubs(:title).returns('dollhouse.s02e08.internal.hdtv.xvid-2hd.avi')
14
+
15
+ parsed = Lexicon::parse(@item)
16
+ assert_equal 'dollhouse', parsed[:title]
17
+ assert_equal 2, parsed[:series]
18
+ assert_equal 8, parsed[:episode]
19
+ assert parsed[:high_def]
20
+ end
21
+
22
+ should 'parse Ghost.Adventures.S03E07.HDTV.XviD-CRiMSON' do
23
+ @item.stubs(:title).returns('Ghost.Adventures.S03E07.HDTV.XviD-CRiMSON')
24
+
25
+ parsed = Lexicon::parse(@item)
26
+ assert_equal 'Ghost Adventures', parsed[:title]
27
+ assert_equal 3, parsed[:series]
28
+ assert_equal 7, parsed[:episode]
29
+ assert parsed[:high_def]
30
+ end
31
+
32
+ should 'parse National.Geographic.Samurai.Sword.720p.HDTV.x264-DiCH' do
33
+ @item.stubs(:title).returns('National.Geographic.Samurai.Sword.720p.HDTV.x264-DiCH')
34
+
35
+ parsed = Lexicon::parse(@item)
36
+ assert_equal 'National Geographic Samurai Sword', parsed[:title]
37
+ assert_equal nil, parsed[:series]
38
+ assert_equal nil, parsed[:episode]
39
+ assert parsed[:high_def]
40
+ end
41
+
42
+ should 'parse Hardcore TV (1994, HBO series)' do
43
+ @item.stubs(:title).returns('Hardcore TV (1994, HBO series)')
44
+
45
+ parsed = Lexicon::parse(@item)
46
+ assert_equal 'Hardcore TV (1994, HBO series)', parsed[:title]
47
+ assert_equal nil, parsed[:series]
48
+ assert_equal nil, parsed[:episode]
49
+ assert !parsed[:high_def]
50
+ end
51
+
52
+ should 'parse Being.Erica.S02.INTERNAL.HDTV.XviD-BitMeTV' do
53
+ @item.stubs(:title).returns('Being.Erica.S02.INTERNAL.HDTV.XviD-BitMeTV')
54
+
55
+ parsed = Lexicon::parse(@item)
56
+ assert_equal 'Being Erica', parsed[:title]
57
+ assert_equal 2, parsed[:series]
58
+ assert_equal nil, parsed[:episode]
59
+ assert parsed[:high_def]
60
+ end
61
+
62
+ should 'parse National.Geographic.Inside.The.Pentagon.Xvid.DVDRip.avi' do
63
+ @item.stubs(:title).returns('National.Geographic.Inside.The.Pentagon.Xvid.DVDRip.avi')
64
+
65
+ parsed = Lexicon::parse(@item)
66
+ assert_equal 'National Geographic Inside The Pentagon', parsed[:title]
67
+ assert_equal nil, parsed[:series]
68
+ assert_equal nil, parsed[:episode]
69
+ assert !parsed[:high_def]
70
+ end
71
+
72
+ should 'parse i.want.to.work.for.diddy.206.hdtv.xvid-sys.avi' do
73
+ @item.stubs(:title).returns('i.want.to.work.for.diddy.206.hdtv.xvid-sys.avi')
74
+
75
+ parsed = Lexicon::parse(@item)
76
+ assert_equal 'i want to work for diddy', parsed[:title]
77
+ assert_equal 2, parsed[:series]
78
+ assert_equal 6, parsed[:episode]
79
+ assert parsed[:high_def]
80
+ end
81
+
82
+ should 'parse Dollhouse S02E08 [reencode] [internal] [2hd] [iPod].mp4' do
83
+ @item.stubs(:title).returns('Dollhouse S02E08 [reencode] [internal] [2hd] [iPod].mp4')
84
+
85
+ parsed = Lexicon::parse(@item)
86
+ assert_equal 'Dollhouse', parsed[:title]
87
+ assert_equal 2, parsed[:series]
88
+ assert_equal 8, parsed[:episode]
89
+ assert !parsed[:high_def]
90
+ end
91
+
92
+ should 'parse The.Jeff.Dunham.Show.S01.E06.HDTV.XviD-RNR.avi' do
93
+ @item.stubs(:title).returns('The.Jeff.Dunham.Show.S01.E06.HDTV.XviD-RNR.avi')
94
+
95
+ parsed = Lexicon::parse(@item)
96
+ assert_equal 'The Jeff Dunham Show', parsed[:title]
97
+ assert_equal 1, parsed[:series]
98
+ assert_equal 6, parsed[:episode]
99
+ assert parsed[:high_def]
100
+ end
101
+
102
+ should 'parse Mouth To Mouth.1x04.Devine.REPACK.WS PDTV XviD-FoV' do
103
+ @item.stubs(:title).returns('Mouth To Mouth.1x04.Devine.REPACK.WS PDTV XviD-FoV')
104
+
105
+ parsed = Lexicon::parse(@item)
106
+ assert_equal 'Mouth To Mouth', parsed[:title]
107
+ assert_equal 1, parsed[:series]
108
+ assert_equal 4, parsed[:episode]
109
+ assert !parsed[:high_def]
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+ require 'base64'
3
+
4
+ class DslTest < Test::Unit::TestCase
5
+ include TestHelper
6
+
7
+ context '' do
8
+ setup do
9
+ @fake_torrent_file = "This really isn't a torrent file"
10
+ @transmission_client = mock
11
+ Transmission::Client.stubs(:new).returns(@transmission_client)
12
+ FakeWeb.register_uri(:get, "http://www.mytorrentsite.com/tv_show.torrent", :body => @fake_torrent_file)
13
+ end
14
+
15
+ context 'download' do
16
+ should 'pass a base64 encoded version of the supplied data and the download directory' do
17
+ @transmission_client.stubs(:add_torrent).with('metainfo' => Base64.encode64(@fake_torrent_file), 'download-dir' => '/Downloads')
18
+ Torrent.download('http://www.mytorrentsite.com/tv_show.torrent', :download_dir => '/Downloads')
19
+ end
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: overdrive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Myles Eftos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-20 00:00:00 +08:00
13
+ default_executable: overdrive
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: daemons
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: transmission-client
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: SyslogLogger
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: An RSS frontend for transmissions. It defines a basic DSL that allows you decide what you do once the files have been downloaded
46
+ email: myles@madpilot.com.au
47
+ executables:
48
+ - overdrive
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.markdown
53
+ files:
54
+ - README.markdown
55
+ - Rakefile
56
+ - VERSION
57
+ - bin/overdrive
58
+ - lib/dsl.rb
59
+ - lib/feed.rb
60
+ - lib/lexicon.rb
61
+ - lib/options.rb
62
+ - lib/torrent.rb
63
+ - test/fixtures/test.rss
64
+ - test/recipe.rb
65
+ - test/test_helper.rb
66
+ - test/unit/dsl.rb
67
+ - test/unit/feed.rb
68
+ - test/unit/lexicon.rb
69
+ - test/unit/torrent.rb
70
+ has_rdoc: true
71
+ homepage: http://github.com/madpilot/overdrive
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --charset=UTF-8
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ requirements: []
92
+
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.5
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: An RSS frontend for transmission
98
+ test_files:
99
+ - test/test_helper.rb
100
+ - test/unit/feed.rb
101
+ - test/unit/lexicon.rb
102
+ - test/unit/dsl.rb
103
+ - test/unit/torrent.rb