transmission-rss 0.1.3 → 0.1.5
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.rdoc +1 -2
- data/bin/transmission-rss +92 -88
- data/lib/transmission-rss.rb +4 -4
- data/lib/transmission-rss/aggregator.rb +111 -109
- data/lib/transmission-rss/client.rb +70 -70
- data/lib/transmission-rss/config-editor.rb +6 -6
- data/lib/transmission-rss/config-editor/listbox.rb +67 -67
- data/lib/transmission-rss/config-editor/main.rb +316 -316
- data/lib/transmission-rss/config.rb +24 -24
- data/lib/transmission-rss/hash.rb +10 -10
- data/lib/transmission-rss/log.rb +21 -41
- metadata +9 -9
data/README.rdoc
CHANGED
data/bin/transmission-rss
CHANGED
@@ -1,63 +1,56 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'getoptlong'
|
4
|
+
require 'etc'
|
5
5
|
|
6
|
-
$:.unshift(
|
7
|
-
require
|
6
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
7
|
+
require 'transmission-rss'
|
8
8
|
|
9
9
|
include TransmissionRSS
|
10
10
|
|
11
11
|
# Default config file path.
|
12
|
-
|
12
|
+
config_file = '/etc/transmission-rss.conf'
|
13
13
|
|
14
14
|
# Do not edit config file in Gtk GUI by default.
|
15
|
-
|
15
|
+
edit_config = false
|
16
16
|
|
17
17
|
# Do not fork by default.
|
18
18
|
dofork = false
|
19
19
|
|
20
|
-
# Default not verbose.
|
21
|
-
verbose = false
|
22
|
-
|
23
20
|
# Shows a summary of the command line options.
|
24
|
-
def
|
25
|
-
|
21
|
+
def usage_message( config_file )
|
22
|
+
$stderr << "#{File.basename($0)} [options]
|
26
23
|
Adds torrents from rss feeds to transmission web frontend.
|
27
24
|
|
28
|
-
-c <file> Custom config file path. Default: #{
|
25
|
+
-c <file> Custom config file path. Default: #{config_file}
|
29
26
|
-e Edit config file with Gtk GUI.
|
30
27
|
-f Fork into background after startup.
|
31
28
|
-h This help.
|
32
|
-
-v Verbose mode.
|
33
29
|
|
34
30
|
"
|
35
|
-
|
31
|
+
exit(1)
|
36
32
|
end
|
37
33
|
|
38
34
|
# Define command-line options.
|
39
35
|
options = GetoptLong.new(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
[ '-v', GetoptLong::NO_ARGUMENT ]
|
36
|
+
[ '-c', GetoptLong::REQUIRED_ARGUMENT ],
|
37
|
+
[ '-e', GetoptLong::NO_ARGUMENT ],
|
38
|
+
[ '-f', GetoptLong::NO_ARGUMENT ],
|
39
|
+
[ '-h', GetoptLong::NO_ARGUMENT ]
|
45
40
|
)
|
46
41
|
|
47
42
|
# Parse given options.
|
48
43
|
options.each do |option, argument|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
verbose = true
|
60
|
-
end
|
44
|
+
case(option)
|
45
|
+
when '-c'
|
46
|
+
config_file = argument
|
47
|
+
when '-e'
|
48
|
+
edit_config = true
|
49
|
+
when '-f'
|
50
|
+
dofork = true
|
51
|
+
when '-h'
|
52
|
+
usage_message(config_file)
|
53
|
+
end
|
61
54
|
end
|
62
55
|
|
63
56
|
# Seems to be necessary when called from gem installation.
|
@@ -65,96 +58,107 @@ end
|
|
65
58
|
config = TransmissionRSS::Config.instance
|
66
59
|
|
67
60
|
# Default configuration.
|
68
|
-
config.load(
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
}
|
79
|
-
|
80
|
-
# Initialize a log instance
|
61
|
+
config.load({
|
62
|
+
'feeds' => [],
|
63
|
+
'update_interval' => 600,
|
64
|
+
'add_paused' => false,
|
65
|
+
'server' => {
|
66
|
+
'host' => 'localhost',
|
67
|
+
'port' => 9091
|
68
|
+
},
|
69
|
+
'log_target' => $stderr,
|
70
|
+
'privileges' => {}
|
71
|
+
})
|
72
|
+
|
73
|
+
# Initialize a log instance and configure it.
|
81
74
|
log = Log.instance
|
82
|
-
log.verbose = verbose
|
83
75
|
log.target = config.log_target
|
84
|
-
|
76
|
+
log.level = Logger::DEBUG
|
77
|
+
log.formatter = proc do |sev, time, prog, msg|
|
78
|
+
"#{time.to_i}(#{sev.downcase}) #{msg}\n"
|
79
|
+
end
|
85
80
|
|
86
81
|
# Load config file (default or given by argument).
|
87
82
|
begin
|
88
|
-
|
83
|
+
config.load(config_file)
|
84
|
+
log.target = config.log_target
|
89
85
|
rescue Errno::ENOENT
|
90
|
-
|
86
|
+
log.error(config_file + ' not found')
|
91
87
|
end
|
92
|
-
log.
|
88
|
+
log.debug(config)
|
93
89
|
|
94
90
|
# Drop privileges, if section is given in config file.
|
95
|
-
if(
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
91
|
+
if(not config.privileges.empty?)
|
92
|
+
Process::Sys.setgid(
|
93
|
+
Etc.getgrnam(config.privileges.group).gid
|
94
|
+
)
|
95
|
+
|
96
|
+
Process::Sys.setuid(
|
97
|
+
Etc.getpwnam(config.privileges.user).uid
|
98
|
+
)
|
99
|
+
|
100
|
+
log.debug(
|
101
|
+
'dropped privileges ' +
|
102
|
+
config.privileges.user +
|
103
|
+
':' +
|
104
|
+
config.privileges.group
|
105
|
+
)
|
110
106
|
else
|
111
|
-
|
107
|
+
log.debug('no privilege dropping')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Warn if no feeds are given.
|
111
|
+
if(config.feeds.empty?)
|
112
|
+
log.warn('no feeds given')
|
112
113
|
end
|
113
114
|
|
114
115
|
# Start GUI if config edit option is given.
|
115
|
-
if(
|
116
|
-
|
116
|
+
if(edit_config)
|
117
|
+
require 'transmission-rss/config-editor'
|
117
118
|
|
118
|
-
|
119
|
+
Gtk.init
|
119
120
|
|
120
|
-
|
121
|
-
|
121
|
+
ConfigEditor.new(config_file, config)
|
122
|
+
Gtk.main
|
122
123
|
|
123
|
-
|
124
|
+
exit(0)
|
124
125
|
end
|
125
126
|
|
126
127
|
# Connect reload of config file to SIGHUP.
|
127
|
-
trap(
|
128
|
-
|
129
|
-
|
128
|
+
trap('HUP') do
|
129
|
+
config.load(config_file)
|
130
|
+
log.info('got hup', config)
|
130
131
|
end
|
131
132
|
|
132
133
|
# Initialize feed aggregator.
|
133
134
|
aggregator = Aggregator.new
|
134
135
|
|
135
136
|
# Initialize communication to transmission.
|
136
|
-
client = Client.new(
|
137
|
+
client = Client.new(config.server.host, config.server.port)
|
137
138
|
|
138
139
|
# Add feeds from config file to +Aggregator+ class.
|
139
|
-
aggregator.feeds.concat(
|
140
|
+
aggregator.feeds.concat(config.feeds)
|
140
141
|
|
141
142
|
# Callback for a new item on one of the feeds.
|
142
143
|
aggregator.on_new_item do |torrentFile|
|
143
|
-
|
144
|
-
|
145
|
-
|
144
|
+
Thread.start do
|
145
|
+
client.addTorrent(torrentFile, config.add_paused)
|
146
|
+
end
|
146
147
|
end
|
147
148
|
|
148
149
|
# Start the aggregation process.
|
149
150
|
begin
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
151
|
+
if(dofork)
|
152
|
+
pid = fork do
|
153
|
+
aggregator.run(config.update_interval)
|
154
|
+
end
|
155
|
+
|
156
|
+
puts('forked ' + pid.to_s)
|
157
|
+
else
|
158
|
+
aggregator.run(config.update_interval)
|
159
|
+
end
|
159
160
|
rescue Interrupt
|
161
|
+
log.info('interrupt caught')
|
160
162
|
end
|
163
|
+
|
164
|
+
log.close
|
data/lib/transmission-rss.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
$:.unshift(
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
2
|
|
3
3
|
module TransmissionRSS
|
4
|
-
VERSION = '0.1.
|
4
|
+
VERSION = '0.1.5'
|
5
5
|
end
|
6
6
|
|
7
7
|
dir = 'transmission-rss'
|
@@ -14,6 +14,6 @@ blacklist.map! do |name|
|
|
14
14
|
$:.first + '/' + dir + '/' + name + '.rb'
|
15
15
|
end
|
16
16
|
|
17
|
-
(
|
18
|
-
require
|
17
|
+
(Dir.glob($:.first + '/' + dir + '/*.rb') - blacklist).each do |lib|
|
18
|
+
require lib
|
19
19
|
end
|
@@ -1,114 +1,116 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'etc'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'rss'
|
5
5
|
|
6
6
|
# Class for aggregating torrent files through RSS feeds.
|
7
7
|
class TransmissionRSS::Aggregator
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
8
|
+
attr_accessor :feeds
|
9
|
+
|
10
|
+
def initialize(feeds = [])
|
11
|
+
@feeds = feeds
|
12
|
+
@seen = []
|
13
|
+
|
14
|
+
# Initialize log instance.
|
15
|
+
@log = Log.instance
|
16
|
+
|
17
|
+
# Declare callback for new items.
|
18
|
+
callback(:on_new_item)
|
19
|
+
|
20
|
+
# Generate path for seen torrents store file.
|
21
|
+
@seenfile = File.join(
|
22
|
+
Etc.getpwuid.dir,
|
23
|
+
'/.config/transmission/seen-torrents.conf'
|
24
|
+
)
|
25
|
+
|
26
|
+
# Make directories in path if they are not existing.
|
27
|
+
FileUtils.mkdir_p(File.dirname(@seenfile))
|
28
|
+
|
29
|
+
# Touch seen torrents store file.
|
30
|
+
if(not File.exists?(@seenfile))
|
31
|
+
FileUtils.touch(@seenfile)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Open file, read torrent URLs and add to +@seen+.
|
35
|
+
open(@seenfile).readlines.each do |line|
|
36
|
+
@seen.push(line.chomp)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Log number of +@seen+ URIs.
|
40
|
+
@log.debug(@seen.size.to_s + ' uris from seenfile')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get file enclosures from all feeds items and call on_new_item callback
|
44
|
+
# with torrent file URL as argument.
|
45
|
+
def run(interval = 600)
|
46
|
+
@log.debug('aggregator start')
|
47
|
+
|
48
|
+
while(true)
|
49
|
+
feeds.each do |url|
|
50
|
+
@log.debug('aggregate ' + url)
|
51
|
+
|
52
|
+
begin
|
53
|
+
content = open(url).readlines.join("\n")
|
54
|
+
items = RSS::Parser.parse(content, false).items
|
55
|
+
rescue
|
56
|
+
@log.debug('retrieval error')
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
items.each do |item|
|
61
|
+
link = item.link
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
63
|
+
# Item contains no link.
|
64
|
+
if(link.nil?)
|
65
|
+
next
|
66
|
+
end
|
67
|
+
|
68
|
+
# Link is not a String directly.
|
69
|
+
if(link.class != String)
|
70
|
+
link = link.href
|
71
|
+
end
|
72
|
+
|
73
|
+
# The link is not in +@seen+ Array.
|
74
|
+
if(not seen?(link))
|
75
|
+
on_new_item(link)
|
76
|
+
@log.debug('on_new_item event ' + link)
|
77
|
+
|
78
|
+
add_seen(link)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
sleep(interval)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# To add a link into the list of seen links.
|
88
|
+
def add_seen(link)
|
89
|
+
@seen.push(link)
|
90
|
+
|
91
|
+
File.open(@seenfile, 'w') do |file|
|
92
|
+
file.write(@seen.join("\n"))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# To test if a link is in the list of seen links.
|
97
|
+
def seen?(link)
|
98
|
+
@seen.include?(link)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Method to define callback methods.
|
102
|
+
def callback(*names)
|
103
|
+
names.each do |name|
|
104
|
+
eval <<-EOF
|
105
|
+
@#{name} = false
|
106
|
+
def #{name}(*args, &block)
|
107
|
+
if(block)
|
108
|
+
@#{name} = block
|
109
|
+
elsif(@#{name})
|
110
|
+
@#{name}.call(*args)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
EOF
|
114
|
+
end
|
115
|
+
end
|
114
116
|
end
|