overdrive 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/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