nzbgetpp 0.1.1rc0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +20 -0
- data/README.md +103 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/bin/nzbgetpp +82 -0
- data/lib/nzbgetpp/log.rb +90 -0
- data/lib/nzbgetpp/nzb.rb +26 -0
- data/lib/nzbgetpp/par.rb +38 -0
- data/lib/nzbgetpp/shellout.rb +27 -0
- data/lib/nzbgetpp/version.rb +3 -0
- data/lib/nzbgetpp.rb +244 -0
- data/nzbgetpp.gemspec +25 -0
- data/spec/log_spec.rb +22 -0
- data/spec/nzb_spec.rb +17 -0
- data/spec/nzbgetpp_spec.rb +106 -0
- data/spec/par_spec.rb +29 -0
- data/spec/shellout_spec.rb +19 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/log.rb +16 -0
- data/spec/support/sample-dst/example job/_brokenlog.txt +0 -0
- data/spec/support/sample-dst/example job/example.rar +0 -0
- data/spec/support/sample-dst/example job/survivor +0 -0
- data/spec/unrar_spec.rb +47 -0
- data/support/config.rb +98 -0
- metadata +111 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nzbgetpp (0.1.1rc0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.2)
|
10
|
+
rake (0.9.2.2)
|
11
|
+
rcov (0.9.9)
|
12
|
+
rspec (2.5.0)
|
13
|
+
rspec-core (~> 2.5.0)
|
14
|
+
rspec-expectations (~> 2.5.0)
|
15
|
+
rspec-mocks (~> 2.5.0)
|
16
|
+
rspec-core (2.5.1)
|
17
|
+
rspec-expectations (2.5.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.5.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
nzbgetpp!
|
26
|
+
rake
|
27
|
+
rcov
|
28
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Marc Bowes
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# nzbgetpp #
|
2
|
+
|
3
|
+
This is a postprocessing script for
|
4
|
+
[nzbget](http://nzbget.sf.net). That is, after nzbget is done
|
5
|
+
downloading things, this is a script that can be called to tidy up
|
6
|
+
your download. Currently, we support:
|
7
|
+
|
8
|
+
* unrarring files
|
9
|
+
* removing unneeded files
|
10
|
+
* categories
|
11
|
+
* callbacks
|
12
|
+
* logging
|
13
|
+
* tests
|
14
|
+
|
15
|
+
NzbGetPP has a pre-defined callback for
|
16
|
+
[dewey](http://github.com/timsjoberg/dewey), which will automatically
|
17
|
+
place TV shows in the right place (and can be configured to rename
|
18
|
+
things).
|
19
|
+
|
20
|
+
Other callbacks can include things like telling some other client to
|
21
|
+
download the now-available file (more on this coming Real Soon Now) or
|
22
|
+
updating a database collection.
|
23
|
+
|
24
|
+
## Design ##
|
25
|
+
|
26
|
+
One of the major painpoints I've found with writing nzbget
|
27
|
+
postprocessing scripts is they tend to be monolithic (bash) scripts
|
28
|
+
and become really hard to reason about and extend. Testing them is
|
29
|
+
also painful. This project has been designed to make it real easy to
|
30
|
+
reason about it (OO design), see what is happening (logging) and test
|
31
|
+
it works without actually downloading things (rspec).
|
32
|
+
|
33
|
+
## Installing ##
|
34
|
+
|
35
|
+
Install the project via a gem or git/hub:
|
36
|
+
|
37
|
+
`gem install nzbgetpp`
|
38
|
+
|
39
|
+
.. or if that doesn't work, or you want the Git version:
|
40
|
+
|
41
|
+
NZBGETPP_INSTALL_PATH=~/src/nzbgetpp
|
42
|
+
git clone git://github.com/marcbowes/nzbgetpp $NZBGETPP_INSTALL_PATH
|
43
|
+
# or:
|
44
|
+
# curl https://github.com/marcbowes/nzbgetpp/tarball/master > $NZBGETPP_INSTALL_PATH
|
45
|
+
pushd $NZBGETPP_INSTALL_PATH
|
46
|
+
sudo rake install
|
47
|
+
|
48
|
+
## Configuring ##
|
49
|
+
|
50
|
+
We look in two places for a `config.rb` file: either the default one
|
51
|
+
we ship in `support/`, or in `$HOME/.nzbgetpp/config.rb`. This is a
|
52
|
+
Ruby script that essentially lets you write code into
|
53
|
+
`lib/nzbgetpp.rb`. We provide convenience `configure do` and
|
54
|
+
`install_callback(name) do` methods, both of which have examples in
|
55
|
+
the factory edition of the config.
|
56
|
+
|
57
|
+
The shipped version should "Just Work (TM)" in that it tries to pick
|
58
|
+
the various binaries out of your environment. We make the assumption
|
59
|
+
that you want to store stuff in `$HOME/download/complete`, but you can
|
60
|
+
change this by setting `storage_directory` in the configure block.
|
61
|
+
|
62
|
+
The only possibly mysterious value is the `scratch_directory`. This is
|
63
|
+
the directory we use to work on the nzb. It means that we don't step
|
64
|
+
on anyone's toes as it is neither the initial destination, nor the
|
65
|
+
final. That is, if you have some other script running (e.g. scanning
|
66
|
+
for new files), this prevents race conditions (we do a `mv` at the
|
67
|
+
end, which is atomic on the filesystem). That said, please make sure
|
68
|
+
that `storage_directory` is on the same drive as `scratch_directory`,
|
69
|
+
else you will incur a penalty when moving the files at the end.
|
70
|
+
|
71
|
+
## Alternatives ##
|
72
|
+
|
73
|
+
Have a look at the
|
74
|
+
[nzbget page on post-processing scripts](http://nzbget.sourceforge.net/Postprocessing_scripts). At the time of writing, the two options are:
|
75
|
+
|
76
|
+
* [PPWeb](http://dalrun.com/Linux/Software/Nzbget/PPWeb/) is a Perl
|
77
|
+
based Web solution for managing nzbget and comes with post-processing scripts.
|
78
|
+
* [Oversight](http://code.google.com/p/oversight/wiki/UnpackingScriptsOnly)
|
79
|
+
is a full system but they provide their unpacking scripts in
|
80
|
+
isolation. I was using this for a while and it works OK, but I found
|
81
|
+
it slow and tricky to extend.
|
82
|
+
|
83
|
+
## Contributing to nzbgetpp ##
|
84
|
+
|
85
|
+
* Check out the latest master to make sure the feature hasn't been
|
86
|
+
implemented or the bug hasn't been fixed yet
|
87
|
+
* Check out the issue tracker to make sure someone already hasn't
|
88
|
+
requested it and/or contributed it
|
89
|
+
* Fork the project
|
90
|
+
* Start a feature/bugfix branch
|
91
|
+
* Commit and push until you are happy with your contribution
|
92
|
+
* Make sure to add tests for it. This is important so I don't break it
|
93
|
+
in a future version unintentionally.
|
94
|
+
* Please try not to mess with the Rakefile, version, or history. If
|
95
|
+
you want to have your own version, or is otherwise necessary, that
|
96
|
+
is fine, but please isolate to its own commit so I can cherry-pick
|
97
|
+
around it.
|
98
|
+
|
99
|
+
## Copyright ##
|
100
|
+
|
101
|
+
Copyright (c) 2011 Marc Bowes. I don't care what you do with it. There
|
102
|
+
are no guarentees.
|
103
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.rspec_opts = %w(-fs --color)
|
8
|
+
# If you're pedantic ..
|
9
|
+
# t.ruby_opts = %w(-w)
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
13
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
14
|
+
spec.rcov = true
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/nzbgetpp
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Reference: http://nzbget.svn.sourceforge.net/viewvc/nzbget/trunk/postprocess-example.sh?revision=359&content-type=text%2Fplain
|
4
|
+
#
|
5
|
+
# NZBGet passes following arguments to postprocess-programm as environment
|
6
|
+
# variables:
|
7
|
+
# NZBPP_DIRECTORY - path to destination dir for downloaded files;
|
8
|
+
# NZBPP_NZBFILENAME - name of processed nzb-file;
|
9
|
+
# NZBPP_PARFILENAME - name of par-file or empty string (if no collections were
|
10
|
+
# found);
|
11
|
+
# NZBPP_PARSTATUS - result of par-check:
|
12
|
+
# 0 = not checked: par-check disabled or nzb-file does
|
13
|
+
# not contain any par-files;
|
14
|
+
# 1 = checked and failed to repair;
|
15
|
+
# 2 = checked and successfully repaired;
|
16
|
+
# 3 = checked and can be repaired but repair is disabled;
|
17
|
+
# NZBPP_NZBCOMPLETED - state of nzb-job:
|
18
|
+
# 0 = there are more collections in this nzb-file queued;
|
19
|
+
# 1 = this was the last collection in nzb-file;
|
20
|
+
# NZBPP_PARFAILED - indication of failed par-jobs for current nzb-file:
|
21
|
+
# 0 = no failed par-jobs;
|
22
|
+
# 1 = current par-job or any of the previous par-jobs for
|
23
|
+
# the same nzb-files failed;
|
24
|
+
# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string).
|
25
|
+
#
|
26
|
+
|
27
|
+
# ARGV looks something like this
|
28
|
+
# [
|
29
|
+
# "/path/to/dst/name-of-folder", # 0. NZBPP_DIRECTORY
|
30
|
+
# "/path/to/nzb/name-of-nzb", # 1. NZBPP_NZBFILENAME
|
31
|
+
# "/path/to/dst/name-of-folder/name-of-par2", # 2. NZBPP_PARFILENAME
|
32
|
+
# "2", # 3. NZBPP_PARSTATUS
|
33
|
+
# "1", # 4. NZBPP_NZBCOMPLETED
|
34
|
+
# "0", # 5. NZBPP_PARFAILED
|
35
|
+
# "" # 6. NZBPP_CATEGORY
|
36
|
+
# ]
|
37
|
+
|
38
|
+
# Return value: nzbget processes the exit code returned by the script:
|
39
|
+
# 91 - request nzbget to do par-check/repair for current collection in the
|
40
|
+
# current nzb-file;
|
41
|
+
# 92 - request nzbget to do par-check/repair for all collections in the
|
42
|
+
# current nzb-file;
|
43
|
+
# 93 - post-process successful (status = SUCCESS);
|
44
|
+
# 94 - post-process failed (status = FAILURE);
|
45
|
+
# 95 - post-process skipped (status = NONE);
|
46
|
+
# All other return codes are interpreted as "status unknown".
|
47
|
+
RC_POSTPROCESS_PARCHECK_CURRENT = 91
|
48
|
+
RC_POSTPROCESS_PARCHECK_ALL = 92
|
49
|
+
RC_POSTPROCESS_SUCCESS = 93
|
50
|
+
RC_POSTPROCESS_ERROR = 94
|
51
|
+
RC_POSTPROCESS_NONE = 95
|
52
|
+
|
53
|
+
begin
|
54
|
+
require 'nzbgetpp'
|
55
|
+
|
56
|
+
pp = NzbGetPP::PostProcessor.new(ARGV[0],
|
57
|
+
NzbGetPP::Nzb.new(ARGV[1],
|
58
|
+
ARGV[4]),
|
59
|
+
NzbGetPP::Par.new(ARGV[2],
|
60
|
+
ARGV[3],
|
61
|
+
ARGV[5]),
|
62
|
+
ARGV[6])
|
63
|
+
pp.postprocess!
|
64
|
+
|
65
|
+
Kernel.exit! RC_POSTPROCESS_SUCCESS
|
66
|
+
rescue NzbGetPP::PostProcessor::InflationError
|
67
|
+
Kernel.exit! RC_POSTPROCESS_PARCHECK_ALL
|
68
|
+
rescue Exception => e
|
69
|
+
STDERR.puts("[ERROR] (#{e.class.name}) #{e.message}")
|
70
|
+
e.backtrace.each do |line|
|
71
|
+
STDERR.puts("[DETAIL] #{line}")
|
72
|
+
end
|
73
|
+
|
74
|
+
File.open("/tmp/nzbgetpp.stderr", "a") do |f|
|
75
|
+
f.puts("An exception occurred running nzbgetpp at " + Time.now.to_s)
|
76
|
+
f.puts([[e.class.name, e.message].join(": "),
|
77
|
+
e.backtrace.join("\n")].join("\n"))
|
78
|
+
f.puts()
|
79
|
+
end
|
80
|
+
|
81
|
+
Kernel.exit! RC_POSTPROCESS_ERROR
|
82
|
+
end
|
data/lib/nzbgetpp/log.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module NzbGetPP
|
2
|
+
class Log
|
3
|
+
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
LEVELS = {
|
7
|
+
:debug => 0,
|
8
|
+
:detail => 1,
|
9
|
+
:info => 2,
|
10
|
+
:warning => 3,
|
11
|
+
:error => 4,
|
12
|
+
:fatal => 5,
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_accessor :log_fn
|
16
|
+
attr_reader :level
|
17
|
+
|
18
|
+
def initialize(level = :debug,
|
19
|
+
log_fn = nil)
|
20
|
+
self.level = level
|
21
|
+
self.log_fn = log_fn
|
22
|
+
open_log_fd()
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_log_fd
|
26
|
+
@log_fd.close() if @log_fd and not @log_fd.closed?
|
27
|
+
if self.log_fn
|
28
|
+
@log_fd = File.open(self.log_fn, "a")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def close_log_fd
|
33
|
+
if @log_fd and not @log_fd.closed?
|
34
|
+
@log_fd.flush()
|
35
|
+
@log_fd.close()
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
LEVELS.each_key do |level|
|
40
|
+
define_method(level) do |message|
|
41
|
+
write_to_log(level, message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def level=(level)
|
46
|
+
@level = level
|
47
|
+
@_enum_level = LEVELS[level]
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_to_log(level, message)
|
51
|
+
if LEVELS[level] >= @_enum_level
|
52
|
+
do_write_to_log(level, message)
|
53
|
+
else
|
54
|
+
# Toss it
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def do_write_to_log(level, message)
|
59
|
+
puts("[%s] %s" % [level.to_s.upcase, message])
|
60
|
+
|
61
|
+
if @log_fd and not @log_fd.closed?
|
62
|
+
log_for_humans(Time.now.iso8601(), level, message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Map the log level (LOG_LEVEL_*) to an array containing
|
67
|
+
# [human_name:String, colour:String(ANSI escape sequence)]
|
68
|
+
ANSI_RED = "\033[0;31m"
|
69
|
+
ANSI_RED_INVERTED = "\033[7;31m"
|
70
|
+
ANSI_BROWN = "\033[0;33m"
|
71
|
+
ANSI_MAGENTA = "\033[0;35m"
|
72
|
+
ANSI_GREEN = "\033[0;32m"
|
73
|
+
ANSI_BOLD_WHITE = "\033[0;37m"
|
74
|
+
ANSI_NORMAL = "\033[0m"
|
75
|
+
HUMAN_LOG_LEVELS = {
|
76
|
+
:fatal => ["FATAL", ANSI_RED_INVERTED],
|
77
|
+
:error => ["ERROR", ANSI_RED],
|
78
|
+
:warning => ["WARN", ANSI_MAGENTA],
|
79
|
+
:info => ["INFO", ANSI_GREEN],
|
80
|
+
:detail => ["DEBUG", ANSI_NORMAL],
|
81
|
+
:debug => ["DEBUG", ANSI_BROWN],
|
82
|
+
}
|
83
|
+
def log_for_humans(iso8601_timestr, log_level, log_msg)
|
84
|
+
level_str, level_colour = HUMAN_LOG_LEVELS[log_level]
|
85
|
+
@log_fd.puts("#{ANSI_BOLD_WHITE}#{iso8601_timestr}#{ANSI_NORMAL} "\
|
86
|
+
"#{level_colour}#{"%5.5s" % level_str}#{ANSI_NORMAL} #{log_msg}")
|
87
|
+
@log_fd.flush()
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/nzbgetpp/nzb.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module NzbGetPP
|
2
|
+
class Nzb
|
3
|
+
|
4
|
+
COMPLETED = {
|
5
|
+
"0" => false,
|
6
|
+
"1" => true,
|
7
|
+
}
|
8
|
+
|
9
|
+
attr_reader :filename
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(filename, completed)
|
13
|
+
@filename = filename
|
14
|
+
self.completed = completed
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def completed=(nzbpp_nzbcompleted)
|
19
|
+
@completed = COMPLETED[nzbpp_nzbcompleted]
|
20
|
+
end
|
21
|
+
|
22
|
+
def completed?
|
23
|
+
!!@completed
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/nzbgetpp/par.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module NzbGetPP
|
2
|
+
class Par
|
3
|
+
|
4
|
+
STATUSES = {
|
5
|
+
"0" => :not_checked,
|
6
|
+
"1" => :cannot_repair,
|
7
|
+
"2" => :repaired,
|
8
|
+
"3" => :repairable,
|
9
|
+
}
|
10
|
+
|
11
|
+
FAILED = {
|
12
|
+
"0" => false,
|
13
|
+
"1" => true,
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_reader :filename, :status
|
17
|
+
|
18
|
+
|
19
|
+
def initialize(filename, status, failed)
|
20
|
+
@filename = filename
|
21
|
+
self.status = status
|
22
|
+
self.failed = failed
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def status=(nzbpp_parstatus)
|
27
|
+
@status = STATUSES[nzbpp_parstatus]
|
28
|
+
end
|
29
|
+
|
30
|
+
def failed=(nzbpp_parfailed)
|
31
|
+
@failed = FAILED[nzbpp_parfailed]
|
32
|
+
end
|
33
|
+
|
34
|
+
def failed?
|
35
|
+
!!@failed
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module NzbGetPP
|
2
|
+
module Shellout
|
3
|
+
# FIXME: probably want to implement timeouts here..
|
4
|
+
def shellout(cmd)
|
5
|
+
stdout_rd, stdout_wr = IO.pipe
|
6
|
+
stderr_rd, stderr_wr = IO.pipe
|
7
|
+
child_pid, child_status = nil
|
8
|
+
child_pid = Kernel.fork
|
9
|
+
|
10
|
+
if child_pid
|
11
|
+
stdout_wr.close
|
12
|
+
stderr_wr.close
|
13
|
+
return [child_pid, stdout_wr, stderr_wr]
|
14
|
+
else
|
15
|
+
Process.setsid
|
16
|
+
STDIN.close
|
17
|
+
STDOUT.reopen(stdout_wr)
|
18
|
+
STDERR.reopen(stderr_wr)
|
19
|
+
3.upto(256) { |fd| IO.new(fd).close rescue nil }
|
20
|
+
|
21
|
+
Kernel.exec(cmd)
|
22
|
+
# Never reached.
|
23
|
+
Kernel.exit!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/nzbgetpp.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
module NzbGetPP
|
2
|
+
|
3
|
+
require "nzbgetpp/version"
|
4
|
+
|
5
|
+
class PostProcessor
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
require 'pathname'
|
9
|
+
require 'ostruct'
|
10
|
+
require 'nzbgetpp/log'
|
11
|
+
require 'nzbgetpp/nzb'
|
12
|
+
require 'nzbgetpp/par'
|
13
|
+
require 'nzbgetpp/shellout'
|
14
|
+
|
15
|
+
include Shellout
|
16
|
+
|
17
|
+
class Error < RuntimeError; end
|
18
|
+
class ConfigError < Error; end
|
19
|
+
class InflationError < Error; end
|
20
|
+
|
21
|
+
|
22
|
+
attr_accessor :category
|
23
|
+
attr_accessor :config
|
24
|
+
attr_accessor :log
|
25
|
+
attr_accessor :nzb
|
26
|
+
attr_accessor :par
|
27
|
+
attr_reader :path
|
28
|
+
|
29
|
+
def initialize(path,
|
30
|
+
nzb,
|
31
|
+
par,
|
32
|
+
category)
|
33
|
+
self.path = path
|
34
|
+
self.nzb = nzb
|
35
|
+
self.par = par
|
36
|
+
self.category = category
|
37
|
+
|
38
|
+
clear_callbacks()
|
39
|
+
load_config()
|
40
|
+
self.log = mk_log(config.log_level, config.log_fn)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def load_config()
|
45
|
+
@config = OpenStruct.new()
|
46
|
+
config_fn = figure_config_fn()
|
47
|
+
ruby = File.read(config_fn)
|
48
|
+
Kernel.eval(ruby, binding(), config_fn, 0)
|
49
|
+
@config
|
50
|
+
end
|
51
|
+
|
52
|
+
def configure
|
53
|
+
yield(@config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def install_callback(name, &block)
|
57
|
+
@callbacks.push([name, block])
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_callbacks
|
61
|
+
@callbacks = Array.new()
|
62
|
+
end
|
63
|
+
|
64
|
+
def run_callbacks
|
65
|
+
log.info("Running callbacks")
|
66
|
+
log.detail("Callbacks #=> #{@callbacks.inspect()}")
|
67
|
+
|
68
|
+
@callbacks.each do |name, block|
|
69
|
+
log.debug("Running callback #{name}")
|
70
|
+
block.call()
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def postprocess!
|
75
|
+
unless nzb.completed?
|
76
|
+
log.info("Called prematurely - this nzb ain't ready for me")
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
|
80
|
+
unless Dir[path.join("*.rar").to_s].empty?
|
81
|
+
inflate_rar_files
|
82
|
+
else
|
83
|
+
log.info("Nothing to inflate")
|
84
|
+
end
|
85
|
+
|
86
|
+
remove_unneeded_files(path)
|
87
|
+
merge_directory_tree(path, scratch_directory)
|
88
|
+
remove_download_directory(path)
|
89
|
+
remove_unneeded_files(scratch_directory)
|
90
|
+
|
91
|
+
log.info("Moving to final resting place")
|
92
|
+
storage_dir = storage_directory.dirname
|
93
|
+
unless storage_dir.exist?
|
94
|
+
log.debug("Storage dir #{storage_dir} will be created")
|
95
|
+
FileUtils.mkdir_p(storage_dir.to_s)
|
96
|
+
end
|
97
|
+
FileUtils.mv(scratch_directory, storage_directory)
|
98
|
+
run_callbacks()
|
99
|
+
|
100
|
+
log.info("Postprocessing complete")
|
101
|
+
log.close_log_fd()
|
102
|
+
return true
|
103
|
+
rescue PostProcessor::Error => e
|
104
|
+
log.error("#{e.class.name}: #{e.message}")
|
105
|
+
e.backtrace.each do |line|
|
106
|
+
log.detail(line)
|
107
|
+
end
|
108
|
+
raise(e)
|
109
|
+
end
|
110
|
+
|
111
|
+
# We shellout to unrar as a demonstration of our lack of faith for
|
112
|
+
# Ruby-based unraring to handle large files efficiently, fast or
|
113
|
+
# even at all.
|
114
|
+
def inflate_rar_files
|
115
|
+
log.info("Inflating rar files")
|
116
|
+
|
117
|
+
unrar_cmd = mk_unrar_cmd
|
118
|
+
log.debug(unrar_cmd)
|
119
|
+
FileUtils.mkdir_p(scratch_directory)
|
120
|
+
|
121
|
+
child_pid, stdout, stderr = shellout(unrar_cmd)
|
122
|
+
|
123
|
+
# Will raise Errno::ECHILD if the pid doesn't exist
|
124
|
+
until Process.wait(child_pid, Process::WNOHANG)
|
125
|
+
readable, _ = IO.select([stdout, stderr], [], [], 1)
|
126
|
+
readable.each do |io|
|
127
|
+
line = io.readline
|
128
|
+
case io
|
129
|
+
when stdout
|
130
|
+
log.detail(line)
|
131
|
+
else
|
132
|
+
log.error(line)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if $?.success?
|
138
|
+
log.info("Inflation succeeded")
|
139
|
+
else
|
140
|
+
raise(InflationError, child_status.inspect)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def merge_directory_tree(source, destination)
|
145
|
+
Dir.glob(File.join(source, "**/*")).each do |f|
|
146
|
+
# Skip directories. If they're empty, we don't want to create
|
147
|
+
# them. If they have content, we'll create them via the
|
148
|
+
# mkdir_p().
|
149
|
+
next if File.directory?(f)
|
150
|
+
|
151
|
+
rel_dir = File.dirname(f).gsub(/^#{Regexp.escape(source.to_s)}\/?/, "")
|
152
|
+
target_dir = File.join(destination,
|
153
|
+
rel_dir)
|
154
|
+
|
155
|
+
unless File.exist?(target_dir)
|
156
|
+
log.debug("Making %s" % target_dir)
|
157
|
+
FileUtils.mkdir_p(target_dir)
|
158
|
+
end
|
159
|
+
|
160
|
+
log.debug("Will move #{f} to #{target_dir}")
|
161
|
+
FileUtils.mv(f, target_dir)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def remove_download_directory(where)
|
166
|
+
FileUtils.rm_r(where)
|
167
|
+
end
|
168
|
+
|
169
|
+
def remove_unneeded_files(where)
|
170
|
+
log.info("Cleaning up unnecessary files in %s" %
|
171
|
+
where.to_s)
|
172
|
+
|
173
|
+
config.discard_files.each do |glob|
|
174
|
+
log.debug("Will remove %s #=> %s" %
|
175
|
+
[
|
176
|
+
where.join(glob),
|
177
|
+
Dir.glob(where.join(glob)).inspect()
|
178
|
+
])
|
179
|
+
Dir.glob(where.join(glob)).each do |file|
|
180
|
+
FileUtils.rm_f(file)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
Dir.glob(where.join("*")) do |glob|
|
185
|
+
remove_unneeded_files(Pathname.new(glob)) if File.directory? glob
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def path=(path)
|
190
|
+
@path = Pathname.new(File.expand_path(path))
|
191
|
+
raise ArgumentError unless @path.exist?
|
192
|
+
rescue => e
|
193
|
+
raise(ArgumentError,
|
194
|
+
"#{path.inspect} is not a valid download destination")
|
195
|
+
end
|
196
|
+
|
197
|
+
def mk_log(log_level, log_fn)
|
198
|
+
Log.new(log_level, log_fn)
|
199
|
+
end
|
200
|
+
|
201
|
+
def mk_unrar_cmd
|
202
|
+
[
|
203
|
+
config.unrar_bin,
|
204
|
+
config.unrar_flags.join(" "),
|
205
|
+
"\"#{unrar_targets.to_s}\"",
|
206
|
+
"\"#{scratch_directory.to_s}\"",
|
207
|
+
].join(" ")
|
208
|
+
end
|
209
|
+
|
210
|
+
def unrar_targets
|
211
|
+
@unrar_targets ||= Pathname.new(File.join(path,
|
212
|
+
config.unrar_targets)).expand_path
|
213
|
+
end
|
214
|
+
|
215
|
+
def scratch_directory
|
216
|
+
@scratch_directory ||= Pathname.new(File.join(*[
|
217
|
+
config.scratch_directory,
|
218
|
+
self.category,
|
219
|
+
path.basename,
|
220
|
+
].compact)).expand_path
|
221
|
+
end
|
222
|
+
|
223
|
+
def storage_directory
|
224
|
+
@storage_directory ||= Pathname.new(File.join(*[
|
225
|
+
config.storage_directory,
|
226
|
+
self.category,
|
227
|
+
path.basename,
|
228
|
+
].compact)).expand_path
|
229
|
+
end
|
230
|
+
|
231
|
+
def figure_config_fn
|
232
|
+
fn = "config.rb"
|
233
|
+
|
234
|
+
custom = File.join(ENV["HOME"], ".nzbgetpp", fn)
|
235
|
+
return custom if File.exist? custom
|
236
|
+
|
237
|
+
default = File.expand_path(File.join(File.dirname(__FILE__), "../support", fn))
|
238
|
+
return default if File.exist? default
|
239
|
+
|
240
|
+
raise RuntimeError,
|
241
|
+
"Unable to find config in #{[custom, default].inspect}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
data/nzbgetpp.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "nzbgetpp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "nzbgetpp"
|
7
|
+
s.version = NzbGetPP::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Marc Bowes"]
|
10
|
+
s.email = ["marcbowes+nzbgetpp@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{NzbGet Postprocessor}
|
13
|
+
s.description = %q{A postprocessing script for NzbGet, written in Ruby.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "nzbgetpp"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
23
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
24
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
25
|
+
end
|
data/spec/log_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'nzbgetpp/log'
|
4
|
+
|
5
|
+
describe NzbGetPP::Log do
|
6
|
+
before do
|
7
|
+
@log = NzbGetPP::MockLog.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should write to the log when the level is permitting" do
|
11
|
+
@log.debug("foo")
|
12
|
+
@log.store.should == [
|
13
|
+
[:debug, "foo"]
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should toss unprivileged messages" do
|
18
|
+
@log.level = :error
|
19
|
+
@log.debug("foo")
|
20
|
+
@log.store.should be_empty
|
21
|
+
end
|
22
|
+
end
|
data/spec/nzb_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'nzbgetpp/nzb'
|
4
|
+
|
5
|
+
describe NzbGetPP::Nzb do
|
6
|
+
before do
|
7
|
+
@nzb = NzbGetPP::Nzb.new("name.nzb", "1")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should correctly report completed?" do
|
11
|
+
@nzb.completed?.should be_true
|
12
|
+
@nzb.completed = "0"
|
13
|
+
@nzb.completed?.should be_false
|
14
|
+
@nzb.completed = "1"
|
15
|
+
@nzb.completed?.should be_true
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
describe NzbGetPP do
|
6
|
+
before do
|
7
|
+
@workdir = "/tmp/nzbgetpp-spec-#{$$}"
|
8
|
+
FileUtils.mkdir_p(@workdir)
|
9
|
+
|
10
|
+
FileUtils.cp_r(File.join(File.dirname(__FILE__),
|
11
|
+
"support/sample-dst"),
|
12
|
+
File.join(@workdir,
|
13
|
+
"dst"))
|
14
|
+
|
15
|
+
@pp = NzbGetPP::PostProcessor.new(File.join(@workdir, "dst/example job"),
|
16
|
+
NzbGetPP::Nzb.new("example.nzb", 1),
|
17
|
+
NzbGetPP::Par.new("example.par2", 2, 0),
|
18
|
+
nil)
|
19
|
+
@pp.config.scratch_directory = File.join(@workdir, "scratch")
|
20
|
+
|
21
|
+
# Comment me out if you want logging for debug purposes
|
22
|
+
# @pp.log.log_fn = "/tmp/nzbgetpp.log"
|
23
|
+
# @pp.log.open_log_fd()
|
24
|
+
@pp.log = NzbGetPP::MockLog.new()
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should correctly determine unrar targets" do
|
28
|
+
@pp.unrar_targets.to_s.should == File.join(@workdir, "dst/example job/*.rar")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should correctly determine scratch directory" do
|
32
|
+
@pp.scratch_directory.to_s.should == File.join(@pp.config.scratch_directory, "example job")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should correctly determine scratch directory with categories" do
|
36
|
+
@pp.category = "category"
|
37
|
+
@pp.scratch_directory.to_s.should == File.join(@pp.config.scratch_directory, "category/example job")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should correctly determine storage directory with categories" do
|
41
|
+
@pp.category = "category"
|
42
|
+
@pp.storage_directory.to_s.should == File.join(@pp.config.storage_directory, "category/example job")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Disabled: requires unrar to be installed
|
46
|
+
#
|
47
|
+
# it "should inflate rars" do
|
48
|
+
# @pp.inflate_rar_files
|
49
|
+
# @pp.scratch_directory.join("example.file").exist?.should be_true
|
50
|
+
# end
|
51
|
+
|
52
|
+
it "should move survivors to the scratch dir" do
|
53
|
+
@pp.merge_directory_tree(@pp.path.to_s, @pp.scratch_directory)
|
54
|
+
@pp.scratch_directory.join("survivor").exist?.should be_true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should remove unneeded files" do
|
58
|
+
# Make some junk
|
59
|
+
junk_dir = Pathname.new(File.join(@workdir, "junk"))
|
60
|
+
FileUtils.mkdir_p(junk_dir)
|
61
|
+
junk_files = %w[
|
62
|
+
junk.rar
|
63
|
+
junk.r00
|
64
|
+
junk.r01
|
65
|
+
junk.s00
|
66
|
+
junk.s01
|
67
|
+
junk.nzb
|
68
|
+
junk.1
|
69
|
+
junk.sfv
|
70
|
+
junk.srr
|
71
|
+
junk.sample.file
|
72
|
+
_brokenlog.txt
|
73
|
+
survivor
|
74
|
+
].each do |junk_file|
|
75
|
+
FileUtils.touch(junk_dir.join(junk_file))
|
76
|
+
end
|
77
|
+
Dir[junk_dir.join("*").to_s].size.should == junk_files.size
|
78
|
+
|
79
|
+
@pp.remove_unneeded_files(junk_dir)
|
80
|
+
|
81
|
+
# Check we completed our list
|
82
|
+
@pp.config.discard_files.each do |glob|
|
83
|
+
Dir[junk_dir.join(glob).to_s].should be_empty
|
84
|
+
end
|
85
|
+
|
86
|
+
# Safety net
|
87
|
+
survivor = junk_dir.join("survivor")
|
88
|
+
survivor.exist?.should be_true
|
89
|
+
FileUtils.rm(survivor.to_s)
|
90
|
+
Dir[junk_dir.join("*").to_s].should be_empty
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should run callbacks" do
|
94
|
+
@pp.clear_callbacks
|
95
|
+
touched = false
|
96
|
+
@pp.install_callback(:toucher) do
|
97
|
+
touched = true
|
98
|
+
end
|
99
|
+
@pp.run_callbacks
|
100
|
+
touched.should == true
|
101
|
+
end
|
102
|
+
|
103
|
+
after do
|
104
|
+
FileUtils.rm_r(@workdir)
|
105
|
+
end
|
106
|
+
end
|
data/spec/par_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'nzbgetpp/par'
|
4
|
+
|
5
|
+
describe NzbGetPP::Par do
|
6
|
+
before do
|
7
|
+
@par = NzbGetPP::Par.new("name.par2", "2", "0")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should correctly report status" do
|
11
|
+
@par.status.should == :repaired
|
12
|
+
@par.status = "0"
|
13
|
+
@par.status.should == :not_checked
|
14
|
+
@par.status = "1"
|
15
|
+
@par.status.should == :cannot_repair
|
16
|
+
@par.status = "2"
|
17
|
+
@par.status.should == :repaired
|
18
|
+
@par.status = "3"
|
19
|
+
@par.status.should == :repairable
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should correctly report failed?" do
|
23
|
+
@par.failed?.should be_false
|
24
|
+
@par.failed = "1"
|
25
|
+
@par.failed?.should be_true
|
26
|
+
@par.failed = "0"
|
27
|
+
@par.failed?.should be_false
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'nzbgetpp/shellout'
|
4
|
+
|
5
|
+
include NzbGetPP::Shellout
|
6
|
+
|
7
|
+
describe NzbGetPP::Shellout do
|
8
|
+
it "should return 0 for true" do
|
9
|
+
pid, _ = shellout("/bin/true")
|
10
|
+
Process.wait(pid)
|
11
|
+
$?.success?.should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return 1 for false" do
|
15
|
+
pid, _ = shellout("/bin/false")
|
16
|
+
Process.wait(pid)
|
17
|
+
$?.success?.should be_false
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'nzbgetpp'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
data/spec/support/log.rb
ADDED
File without changes
|
Binary file
|
File without changes
|
data/spec/unrar_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require 'nzbgetpp/shellout'
|
4
|
+
|
5
|
+
include NzbGetPP::Shellout
|
6
|
+
|
7
|
+
describe NzbGetPP do
|
8
|
+
it "should parse * as a * not as an expanded list of arguments" do
|
9
|
+
sample_prog_fpath = File.join("/tmp/nzbgetpp-spec", Process.pid.to_s, "arg_writer.sh")
|
10
|
+
dir = File.dirname(sample_prog_fpath)
|
11
|
+
FileUtils.mkdir_p(dir)
|
12
|
+
File.open(sample_prog_fpath, "w") do |f|
|
13
|
+
f.puts <<SCRIPT
|
14
|
+
#!/usr bin/env/ruby
|
15
|
+
|
16
|
+
argc_fpath = File.expand_path("argc", File.dirname(__FILE__))
|
17
|
+
argv_fpath = File.expand_path("argv", File.dirname(__FILE__))
|
18
|
+
File.open(argc_fpath, "w") do |f|
|
19
|
+
f.puts(ARGV.size)
|
20
|
+
end
|
21
|
+
File.open(argv_fpath, "w") do |f|
|
22
|
+
f.puts(ARGV.inspect)
|
23
|
+
end
|
24
|
+
SCRIPT
|
25
|
+
end
|
26
|
+
FileUtils.touch(File.join(dir, "foo"))
|
27
|
+
FileUtils.touch(File.join(dir, "bar"))
|
28
|
+
|
29
|
+
arg = File.join(dir, "*").inspect
|
30
|
+
cmd = [
|
31
|
+
"ruby",
|
32
|
+
sample_prog_fpath,
|
33
|
+
arg,
|
34
|
+
].join(" ")
|
35
|
+
child_pid, _ = shellout(cmd)
|
36
|
+
Process.wait(child_pid)
|
37
|
+
$?.success?.should be_true
|
38
|
+
|
39
|
+
File.read(File.join(dir, "argc")).strip.
|
40
|
+
should == "1"
|
41
|
+
|
42
|
+
File.read(File.join(dir, "argv")).strip.
|
43
|
+
should == "[#{arg}]"
|
44
|
+
|
45
|
+
FileUtils.rm_r(dir)
|
46
|
+
end
|
47
|
+
end
|
data/support/config.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
def which(bin)
|
2
|
+
candidates = ENV["PATH"].split(":").map do |dir|
|
3
|
+
File.expand_path(bin, dir)
|
4
|
+
end
|
5
|
+
|
6
|
+
candidates.detect do |candidate|
|
7
|
+
File.exist?(candidate)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
configure do |c|
|
12
|
+
c.unrar_targets = "*.rar"
|
13
|
+
c.discard_files = [
|
14
|
+
"*.rar",
|
15
|
+
"*.r[0-9][0-9]",
|
16
|
+
"*.s[0-9][0-9]",
|
17
|
+
"*.nzb",
|
18
|
+
"*.par2",
|
19
|
+
"*.1",
|
20
|
+
"*.sfv",
|
21
|
+
"*.srr",
|
22
|
+
"*sample*",
|
23
|
+
"_brokenlog.txt",
|
24
|
+
]
|
25
|
+
c.scratch_directory = File.join(ENV["HOME"], "download/scratch")
|
26
|
+
c.storage_directory = File.join(ENV["HOME"], "download/complete")
|
27
|
+
c.unrar_bin = which("unrar")
|
28
|
+
c.unrar_flags = [
|
29
|
+
"x",
|
30
|
+
"-y",
|
31
|
+
"-p-",
|
32
|
+
"-o+",
|
33
|
+
]
|
34
|
+
|
35
|
+
c.log_level = :debug
|
36
|
+
log_dir = File.join(ENV["HOME"], ".nzbgetpp")
|
37
|
+
c.log_fn = File.join(log_dir, "log") if File.exist?(log_dir)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Dewey support
|
42
|
+
#
|
43
|
+
configure do |c|
|
44
|
+
c.tv_dir = File.join(config.storage_directory, "archive/tv.series")
|
45
|
+
c.dewey_bin = which("dewey")
|
46
|
+
end
|
47
|
+
|
48
|
+
def move_tv_shows_to_the_archive
|
49
|
+
if category.nil? ||
|
50
|
+
category !~ /tv/i
|
51
|
+
# Not TV, so move along
|
52
|
+
log.detail("This isn't a TV show, so dewey won't be invoked")
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
if config.dewey_bin.nil? ||
|
57
|
+
config.dewey_bin.empty? ||
|
58
|
+
!File.exist?(config.dewey_bin)
|
59
|
+
# Dewey not available, so we won't archive this tv show
|
60
|
+
log.warning("This is a TV show but dewey isn't available :-(")
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
# Support for appending extra information into your tv-dir. For
|
65
|
+
# example, this allows us to set the category to 'tv/720p' and call
|
66
|
+
# dewey with '--tv-dir=archive/tv.series/720p'.
|
67
|
+
tv_dir = config.tv_dir
|
68
|
+
if (m = category.match(/tv\/(\w+)/i))
|
69
|
+
tv_dir = File.join(tv_dir, m[1])
|
70
|
+
end
|
71
|
+
|
72
|
+
dewey_exec = [
|
73
|
+
config.dewey_bin,
|
74
|
+
[
|
75
|
+
["--tv-dir", tv_dir.inspect()],
|
76
|
+
].map { |args| args.join("=") },
|
77
|
+
storage_directory.to_s.inspect(),
|
78
|
+
].join(" ")
|
79
|
+
log.detail("Will exec(): " + dewey_exec.inspect())
|
80
|
+
|
81
|
+
rd, wr = IO.pipe()
|
82
|
+
rc = shellout(dewey_exec, nil, wr, wr)
|
83
|
+
wr.close() unless wr.closed?
|
84
|
+
rd.each_line do |line|
|
85
|
+
log.detail(line)
|
86
|
+
end
|
87
|
+
rd.close() unless rd.closed?
|
88
|
+
|
89
|
+
if rc
|
90
|
+
log.info("Dewey seems happy :-)")
|
91
|
+
else
|
92
|
+
log.error("Dewey failed with a rc #{rc.inspect()} :-(")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
install_callback(:dewey) do
|
97
|
+
move_tv_shows_to_the_archive()
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nzbgetpp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1rc0
|
5
|
+
prerelease: 5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marc Bowes
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &18907960 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *18907960
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &18905760 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *18905760
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rcov
|
38
|
+
requirement: &18904720 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *18904720
|
47
|
+
description: A postprocessing script for NzbGet, written in Ruby.
|
48
|
+
email:
|
49
|
+
- marcbowes+nzbgetpp@gmail.com
|
50
|
+
executables:
|
51
|
+
- nzbgetpp
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- .rspec
|
58
|
+
- Gemfile
|
59
|
+
- Gemfile.lock
|
60
|
+
- LICENSE.txt
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- VERSION
|
64
|
+
- bin/nzbgetpp
|
65
|
+
- lib/nzbgetpp.rb
|
66
|
+
- lib/nzbgetpp/log.rb
|
67
|
+
- lib/nzbgetpp/nzb.rb
|
68
|
+
- lib/nzbgetpp/par.rb
|
69
|
+
- lib/nzbgetpp/shellout.rb
|
70
|
+
- lib/nzbgetpp/version.rb
|
71
|
+
- nzbgetpp.gemspec
|
72
|
+
- spec/log_spec.rb
|
73
|
+
- spec/nzb_spec.rb
|
74
|
+
- spec/nzbgetpp_spec.rb
|
75
|
+
- spec/par_spec.rb
|
76
|
+
- spec/shellout_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
- spec/support/log.rb
|
79
|
+
- spec/support/sample-dst/example job/_brokenlog.txt
|
80
|
+
- spec/support/sample-dst/example job/example.rar
|
81
|
+
- spec/support/sample-dst/example job/survivor
|
82
|
+
- spec/unrar_spec.rb
|
83
|
+
- support/config.rb
|
84
|
+
homepage: ''
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
hash: 3546351088323564806
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>'
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.3.1
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project: nzbgetpp
|
107
|
+
rubygems_version: 1.8.11
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: NzbGet Postprocessor
|
111
|
+
test_files: []
|