transmission-rss 0.1.14 → 0.1.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/bin/transmission-rss +13 -8
- data/lib/transmission-rss.rb +1 -1
- data/lib/transmission-rss/aggregator.rb +80 -88
- data/lib/transmission-rss/callback.rb +19 -0
- data/lib/transmission-rss/client.rb +55 -51
- data/lib/transmission-rss/config.rb +18 -16
- data/lib/transmission-rss/log.rb +17 -15
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4799fb595553fef0cf552adf93216b4ce6c7d265
|
4
|
+
data.tar.gz: 64c6d7ffb5dca02e58195b27bd0185ef321773e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 452d464189a7d4df88827296741410572f5550aa96c8ed684e8cda79f37296dddc01c01757e074a6c31fb85fbba2d04e5783f2a06e9aa66a9c7f5e4f157e40ce
|
7
|
+
data.tar.gz: e3ed1575e0b955b4be00ecd0f724b351036d06ea6026af5b390781598f4c17e09e59f42073ea05079559edd713ab9d32c6c7ca790d468265933a0c25c31e97f2
|
data/README.md
CHANGED
data/bin/transmission-rss
CHANGED
@@ -19,13 +19,14 @@ pid_file = false
|
|
19
19
|
|
20
20
|
# Shows a summary of the command line options.
|
21
21
|
def usage_message(config_file)
|
22
|
-
$stderr << "#{File.basename $0} [
|
22
|
+
$stderr << "#{File.basename $0} [options]
|
23
23
|
Adds torrents from rss feeds to transmission web frontend.
|
24
24
|
|
25
25
|
-c <file> Custom config file path. Default: #{config_file}
|
26
26
|
-f Fork into background after startup.
|
27
|
-
-p <file> Write PID to file.
|
28
27
|
-h This help.
|
28
|
+
-p <file> Write PID to file.
|
29
|
+
-v Show program version and exit.
|
29
30
|
|
30
31
|
"
|
31
32
|
exit(1)
|
@@ -33,10 +34,11 @@ end
|
|
33
34
|
|
34
35
|
# Define command-line options.
|
35
36
|
options = GetoptLong.new \
|
36
|
-
[
|
37
|
-
[
|
38
|
-
[
|
39
|
-
[
|
37
|
+
['-c', GetoptLong::REQUIRED_ARGUMENT],
|
38
|
+
['-f', GetoptLong::NO_ARGUMENT],
|
39
|
+
['-h', GetoptLong::NO_ARGUMENT],
|
40
|
+
['-p', GetoptLong::REQUIRED_ARGUMENT],
|
41
|
+
['-v', GetoptLong::NO_ARGUMENT]
|
40
42
|
|
41
43
|
# Parse given options.
|
42
44
|
options.each do |option, argument|
|
@@ -45,10 +47,13 @@ options.each do |option, argument|
|
|
45
47
|
config_file = argument
|
46
48
|
when '-f'
|
47
49
|
dofork = true
|
48
|
-
when '-p'
|
49
|
-
pid_file = argument
|
50
50
|
when '-h'
|
51
51
|
usage_message config_file
|
52
|
+
when '-p'
|
53
|
+
pid_file = argument
|
54
|
+
when '-v'
|
55
|
+
puts TransmissionRSS::VERSION
|
56
|
+
exit
|
52
57
|
end
|
53
58
|
end
|
54
59
|
|
data/lib/transmission-rss.rb
CHANGED
@@ -1,115 +1,107 @@
|
|
1
1
|
require 'etc'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'open-uri'
|
4
|
+
require 'open_uri_redirections'
|
4
5
|
require 'rss'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
libdir = File.dirname(__FILE__)
|
8
|
+
require File.join(libdir, 'log')
|
9
|
+
require File.join(libdir, 'callback')
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
module TransmissionRSS
|
12
|
+
# Class for aggregating torrent files through RSS feeds.
|
13
|
+
class Aggregator
|
14
|
+
extend Callback
|
15
|
+
callback :on_new_item # Declare callback for new items.
|
13
16
|
|
14
|
-
|
15
|
-
@log = Log.instance
|
17
|
+
attr_accessor :feeds
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
def initialize(feeds = [])
|
20
|
+
@feeds = feeds
|
21
|
+
@seen = []
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
Etc.getpwuid.dir,
|
23
|
-
'/.config/transmission/seen-torrents.conf'
|
23
|
+
# Initialize log instance.
|
24
|
+
@log = Log.instance
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
# Generate path for seen torrents store file.
|
27
|
+
@seenfile = File.join \
|
28
|
+
Etc.getpwuid.dir,
|
29
|
+
'/.config/transmission/seen-torrents.conf'
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
FileUtils.touch @seenfile
|
31
|
-
end
|
31
|
+
# Make directories in path if they are not existing.
|
32
|
+
FileUtils.mkdir_p File.dirname(@seenfile)
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
# Touch seen torrents store file.
|
35
|
+
unless File.exists? @seenfile
|
36
|
+
FileUtils.touch @seenfile
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
# Open file, read torrent URLs and add to +@seen+.
|
40
|
+
open(@seenfile).readlines.each do |line|
|
41
|
+
@seen.push line.chomp
|
42
|
+
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
44
|
+
# Log number of +@seen+ URIs.
|
45
|
+
@log.debug @seen.size.to_s + ' uris from seenfile'
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get file enclosures from all feeds items and call on_new_item callback
|
49
|
+
# with torrent file URL as argument.
|
50
|
+
def run(interval = 600)
|
51
|
+
@log.debug 'aggregator start'
|
52
|
+
|
53
|
+
while true
|
54
|
+
feeds.each do |url|
|
55
|
+
url = URI.encode(url)
|
56
|
+
@log.debug 'aggregate ' + url
|
57
|
+
|
58
|
+
begin
|
59
|
+
content = open(url, allow_redirections: :safe).read
|
60
|
+
items = RSS::Parser.parse(content, false).items
|
61
|
+
rescue Exception => e
|
62
|
+
@log.debug "retrieval error (#{e.message})"
|
63
|
+
next
|
64
|
+
end
|
58
65
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
|
76
|
-
|
66
|
+
items.each do |item|
|
67
|
+
link = item.enclosure.url rescue item.link
|
68
|
+
|
69
|
+
# Item contains no link.
|
70
|
+
next if link.nil?
|
71
|
+
|
72
|
+
# Link is not a String directly.
|
73
|
+
link = link.href if link.class != String
|
74
|
+
|
75
|
+
# The link is not in +@seen+ Array.
|
76
|
+
unless seen? link
|
77
|
+
@log.debug 'on_new_item event ' + link
|
78
|
+
begin
|
79
|
+
on_new_item link
|
80
|
+
rescue Errno::ECONNREFUSED
|
81
|
+
# @log.debug 'not added to seenfile'
|
82
|
+
else
|
83
|
+
add_seen link
|
84
|
+
end
|
77
85
|
end
|
78
86
|
end
|
79
87
|
end
|
80
|
-
end
|
81
88
|
|
82
|
-
|
89
|
+
sleep interval
|
90
|
+
end
|
83
91
|
end
|
84
|
-
end
|
85
92
|
|
86
|
-
|
87
|
-
|
88
|
-
|
93
|
+
# To add a link into the list of seen links.
|
94
|
+
def add_seen(link)
|
95
|
+
@seen.push link
|
89
96
|
|
90
|
-
|
91
|
-
|
97
|
+
File.open(@seenfile, 'w') do |file|
|
98
|
+
file.write @seen.join("\n")
|
99
|
+
end
|
92
100
|
end
|
93
|
-
end
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
# Method to define callback methods.
|
101
|
-
def callback(*names)
|
102
|
-
names.each do |name|
|
103
|
-
eval <<-EOF
|
104
|
-
@#{name} = false
|
105
|
-
def #{name}(*args, &block)
|
106
|
-
if block
|
107
|
-
@#{name} = block
|
108
|
-
elsif @#{name}
|
109
|
-
@#{name}.call *args
|
110
|
-
end
|
111
|
-
end
|
112
|
-
EOF
|
102
|
+
# To test if a link is in the list of seen links.
|
103
|
+
def seen?(link)
|
104
|
+
@seen.include? link
|
113
105
|
end
|
114
106
|
end
|
115
107
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module TransmissionRSS
|
2
|
+
module Callback
|
3
|
+
# Define callback method.
|
4
|
+
def callback(*names)
|
5
|
+
names.each do |name|
|
6
|
+
self.class_eval do
|
7
|
+
define_method name, ->(*args, &block) do
|
8
|
+
@callbacks ||= {}
|
9
|
+
if block
|
10
|
+
@callbacks[name] = block
|
11
|
+
elsif @callbacks[name]
|
12
|
+
@callbacks[name].call *args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,72 +2,76 @@ require 'net/http'
|
|
2
2
|
require 'json'
|
3
3
|
require 'base64'
|
4
4
|
|
5
|
-
|
6
|
-
class TransmissionRSS::Client
|
7
|
-
def initialize(host = 'localhost', port = 9091, timeout: 5)
|
8
|
-
@host, @port, @timeout = host, port, timeout
|
9
|
-
@log = Log.instance
|
10
|
-
end
|
11
|
-
|
12
|
-
# POST json packed torrent add command.
|
13
|
-
def add_torrent(file, type, paused = false)
|
14
|
-
hash = {
|
15
|
-
'method' => 'torrent-add',
|
16
|
-
'arguments' => {
|
17
|
-
'paused' => paused
|
18
|
-
}
|
19
|
-
}
|
5
|
+
require File.join(File.dirname(__FILE__), 'log')
|
20
6
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
raise ArgumentError.new 'type has to be :url or :file.'
|
7
|
+
module TransmissionRSS
|
8
|
+
# Class for communication with transmission utilizing the RPC web interface.
|
9
|
+
class Client
|
10
|
+
def initialize(host = 'localhost', port = 9091, timeout: 5)
|
11
|
+
@host, @port, @timeout = host, port, timeout
|
12
|
+
@log = TransmissionRSS::Log.instance
|
28
13
|
end
|
29
14
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
'
|
34
|
-
'
|
15
|
+
# POST json packed torrent add command.
|
16
|
+
def add_torrent(file, type, paused = false)
|
17
|
+
hash = {
|
18
|
+
'method' => 'torrent-add',
|
19
|
+
'arguments' => {
|
20
|
+
'paused' => paused
|
21
|
+
}
|
35
22
|
}
|
36
23
|
|
37
|
-
|
24
|
+
case type
|
25
|
+
when :url
|
26
|
+
hash.arguments.filename = file
|
27
|
+
when :file
|
28
|
+
hash.arguments.metainfo = Base64.encode64(File.read(file))
|
29
|
+
else
|
30
|
+
raise ArgumentError.new 'type has to be :url or :file.'
|
31
|
+
end
|
38
32
|
|
39
|
-
|
33
|
+
post = Net::HTTP::Post.new \
|
34
|
+
'/transmission/rpc',
|
35
|
+
initheader = {
|
36
|
+
'Content-Type' => 'application/json',
|
37
|
+
'X-Transmission-Session-Id' => get_session_id
|
38
|
+
}
|
40
39
|
|
41
|
-
|
40
|
+
post.body = hash.to_json
|
42
41
|
|
43
|
-
|
44
|
-
end
|
42
|
+
response = request post
|
45
43
|
|
46
|
-
|
47
|
-
def get_session_id
|
48
|
-
get = Net::HTTP::Get.new '/transmission/rpc'
|
49
|
-
response = request get
|
44
|
+
result = JSON.parse(response.body).result
|
50
45
|
|
51
|
-
|
46
|
+
@log.debug 'add_torrent result: ' + result
|
47
|
+
end
|
52
48
|
|
53
|
-
|
49
|
+
# Get transmission session id.
|
50
|
+
def get_session_id
|
51
|
+
get = Net::HTTP::Get.new '/transmission/rpc'
|
52
|
+
response = request get
|
54
53
|
|
55
|
-
|
56
|
-
|
54
|
+
id = response.header['x-transmission-session-id']
|
55
|
+
|
56
|
+
@log.debug 'got session id ' + id
|
57
|
+
|
58
|
+
id
|
59
|
+
end
|
57
60
|
|
58
|
-
|
61
|
+
private
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
63
|
+
def request(data)
|
64
|
+
Timeout::timeout(@timeout) do
|
65
|
+
Net::HTTP.new(@host, @port).start do |http|
|
66
|
+
http.request data
|
67
|
+
end
|
64
68
|
end
|
69
|
+
rescue Errno::ECONNREFUSED
|
70
|
+
@log.debug 'connection refused'
|
71
|
+
raise
|
72
|
+
rescue Timeout::Error
|
73
|
+
@log.debug 'connection timeout'
|
74
|
+
raise
|
65
75
|
end
|
66
|
-
rescue Errno::ECONNREFUSED
|
67
|
-
@log.debug 'connection refused'
|
68
|
-
raise
|
69
|
-
rescue Timeout::Error
|
70
|
-
@log.debug 'connection timeout'
|
71
|
-
raise
|
72
76
|
end
|
73
77
|
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'yaml'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module TransmissionRSS
|
5
|
+
# Class handles configuration parameters.
|
6
|
+
class Config < Hash
|
7
|
+
# This is a singleton class.
|
8
|
+
include Singleton
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
# Merges a Hash or YAML file (containing a Hash) with itself.
|
11
|
+
def load(config)
|
12
|
+
case config.class.to_s
|
13
|
+
when 'Hash'
|
14
|
+
self.merge! config
|
15
|
+
when 'String'
|
16
|
+
self.merge_yaml! config
|
17
|
+
else
|
18
|
+
raise ArgumentError.new 'Could not load config.'
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
22
|
+
# Merge Config Hash with Hash from YAML file.
|
23
|
+
def merge_yaml!(path)
|
24
|
+
self.merge! YAML.load_file(path)
|
18
25
|
end
|
19
26
|
end
|
20
|
-
|
21
|
-
# Merge Config Hash with Hash from YAML file.
|
22
|
-
def merge_yaml!(path)
|
23
|
-
self.merge! YAML.load_file(path)
|
24
|
-
end
|
25
27
|
end
|
data/lib/transmission-rss/log.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'singleton'
|
3
3
|
|
4
|
-
|
5
|
-
class
|
6
|
-
|
4
|
+
module TransmissionRSS
|
5
|
+
# Encapsulates Logger as a singleton class.
|
6
|
+
class Log
|
7
|
+
include Singleton
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
# Change log target (IO or path to a file as String).
|
10
|
+
def target=(target)
|
11
|
+
old_logger = @logger
|
12
|
+
@logger = Logger.new target
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
if old_logger
|
15
|
+
@logger.level = old_logger.level
|
16
|
+
@logger.formatter = old_logger.formatter
|
17
|
+
end
|
16
18
|
end
|
17
|
-
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# If this class misses a method, call it on the encapsulated Logger class.
|
21
|
+
def method_missing(sym, *args)
|
22
|
+
@logger ||= Logger.new $stderr
|
23
|
+
@logger.send sym, *args
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transmission-rss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- henning mueller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
-
dependencies:
|
11
|
+
date: 2014-04-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: open_uri_redirections
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.4
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.4
|
13
33
|
description: |-
|
14
34
|
transmission-rss is basically a workaround for
|
15
35
|
transmission's lack of the ability to monitor RSS feeds and
|
@@ -26,6 +46,7 @@ files:
|
|
26
46
|
- bin/transmission-rss
|
27
47
|
- lib/transmission-rss.rb
|
28
48
|
- lib/transmission-rss/aggregator.rb
|
49
|
+
- lib/transmission-rss/callback.rb
|
29
50
|
- lib/transmission-rss/client.rb
|
30
51
|
- lib/transmission-rss/config.rb
|
31
52
|
- lib/transmission-rss/hash.rb
|