sanguinews 0.60 → 0.61
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.
- checksums.yaml +4 -4
- data/lib/sanguinews/config.rb +152 -0
- data/lib/sanguinews/file_to_upload.rb +3 -6
- data/lib/sanguinews/nntp_msg.rb +5 -9
- data/lib/sanguinews/version.rb +1 -1
- data/lib/sanguinews.rb +58 -185
- data/sample.conf +37 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40aa32aca9a225e90465338f0e81ab91e3a6d252
|
|
4
|
+
data.tar.gz: d54b92ab842f357493d401bbe2f125896e019c8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6502eba0c482db080ab3a0a3eedd47ae3523a5772e908d68fe6d21041844cae67fcd02cb631cb6501b6b6ee18448c1837ec5a45c01cfca4eb4e04f02828829f
|
|
7
|
+
data.tar.gz: d501c3176bf7f6e2ca3197e1234c74652e770574e6d668f4881daea9b5e0009b44dc832eca6ca9a91ece5f0af069bf01edb807d8a6f78578908b8f9f069df722
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Config - class designed specifically for sanguinews
|
|
3
|
+
# Copyright (c) 2013-2014, Tadeus Dobrovolskij
|
|
4
|
+
# This library is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License along
|
|
15
|
+
# with this library; if not, write to the Free Software Foundation, Inc.,
|
|
16
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
17
|
+
#########################################################################
|
|
18
|
+
module Sanguinews
|
|
19
|
+
class Config
|
|
20
|
+
attr_reader :config, :data
|
|
21
|
+
|
|
22
|
+
%w(username password server port from connections
|
|
23
|
+
article_size reconnect_delay groups prefix ssl
|
|
24
|
+
xna nzb header_check verbose debug filemode files directory).each do |meth|
|
|
25
|
+
define_method(meth) { @data[meth.to_sym] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_config(config)
|
|
29
|
+
config = ParseConfig.new(config)
|
|
30
|
+
config.get_params().each do |key|
|
|
31
|
+
value = config[key]
|
|
32
|
+
value = true if value == 'yes'
|
|
33
|
+
value = false if value == 'no'
|
|
34
|
+
value = value.to_i if %w(connections article_size reconnect_delay).include? key
|
|
35
|
+
@data[key.to_sym] ||= value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def mode
|
|
40
|
+
self.ssl ? :tls : :original
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_options!(args)
|
|
44
|
+
# version and legal info presented to user
|
|
45
|
+
banner = []
|
|
46
|
+
banner << ""
|
|
47
|
+
banner << "sanguinews v#{Sanguinews::VERSION}. Copyright (c) 2013-2014 Tadeus Dobrovolskij."
|
|
48
|
+
banner << "Comes with ABSOLUTELY NO WARRANTY. Distributed under GPL v2 license(http://www.gnu.org/licenses/gpl-2.0.txt)."
|
|
49
|
+
banner << "sanguinews is a simple nntp(usenet) binary poster. It supports multithreading and SSL. More info in README."
|
|
50
|
+
banner << ""
|
|
51
|
+
# option parser
|
|
52
|
+
|
|
53
|
+
opt_parser = OptionParser.new do |opt|
|
|
54
|
+
opt.banner = "Usage: sanguinews [OPTIONS] [DIRECTORY] | -f FILE1..[FILEX]"
|
|
55
|
+
opt.separator ""
|
|
56
|
+
opt.separator "Options"
|
|
57
|
+
|
|
58
|
+
opt.on("-c", "--config CONFIG", "use different config file") do |cfg|
|
|
59
|
+
@config = cfg
|
|
60
|
+
end
|
|
61
|
+
opt.on("-C", "--check", "check headers while uploading; slow but reliable") do
|
|
62
|
+
@data[:header_check] = true
|
|
63
|
+
end
|
|
64
|
+
opt.on("-f", "--file FILE", "upload FILE, treat all additional parameters as files") do |file|
|
|
65
|
+
@data[:filemode] = true
|
|
66
|
+
@data[:files] << file
|
|
67
|
+
end
|
|
68
|
+
opt.on("-g", "--groups GROUP_LIST", "use these groups(comma separated) for upload") do |group_list|
|
|
69
|
+
@data[:groups] = group_list
|
|
70
|
+
end
|
|
71
|
+
opt.on("-h", "--help", "help") do
|
|
72
|
+
banner.each do |msg|
|
|
73
|
+
puts msg
|
|
74
|
+
end
|
|
75
|
+
puts opt_parser
|
|
76
|
+
puts
|
|
77
|
+
exit
|
|
78
|
+
end
|
|
79
|
+
opt.on("-p", "--password PASSWORD", "use PASSWORD as your password(overwrites config file)") do |password|
|
|
80
|
+
@data[:password] = password
|
|
81
|
+
end
|
|
82
|
+
opt.on("-u", "--user USERNAME", "use USERNAME as your username(overwrites config file)") do |username|
|
|
83
|
+
@data[:username] = username
|
|
84
|
+
end
|
|
85
|
+
opt.on("-v", "--verbose", "be verbose?") do
|
|
86
|
+
@data[:verbose] = true
|
|
87
|
+
end
|
|
88
|
+
opt.on("-V", "--version", "print version information and then exit") do
|
|
89
|
+
puts Sanguinews::VERSION
|
|
90
|
+
exit
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
opt_parser.parse!(args)
|
|
96
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
|
97
|
+
puts opt_parser
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# in file mode treat every additional parameter as a file
|
|
102
|
+
if !args.empty? && @data[:filemode]
|
|
103
|
+
args.each do |file|
|
|
104
|
+
@data[:files] << file.to_s
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# exit when no file list is provided
|
|
109
|
+
if @data[:files].empty?
|
|
110
|
+
if @data[:filemode] || args.empty?
|
|
111
|
+
puts "You need to specify something to upload!"
|
|
112
|
+
puts opt_parser
|
|
113
|
+
exit 1
|
|
114
|
+
else
|
|
115
|
+
args[0].end_with?('/') ? @data[:directory] = args[0] : @data[:directory] = args[0] + '/'
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def config_gen
|
|
122
|
+
puts "It looks like you are launching sanguinews for the first time."
|
|
123
|
+
`cp #{File.expand_path(File.dirname(__FILE__)) + '/../../sample.conf'} ~/.sanguinews.conf`
|
|
124
|
+
puts "Sample config was copied to your home directory."
|
|
125
|
+
puts "Please edit #{File.expand_path('~/.sanguinews.conf')} in your favourite text editor."
|
|
126
|
+
puts "Relaunch the application when ready."
|
|
127
|
+
exit
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def initialize(args)
|
|
131
|
+
@data = {}
|
|
132
|
+
@data[:filemode] = false
|
|
133
|
+
@data[:files] = []
|
|
134
|
+
|
|
135
|
+
parse_options!(args)
|
|
136
|
+
|
|
137
|
+
# Parse options in config file
|
|
138
|
+
if @data[:config] && File.exist?(File.expand_path(@data[:config]))
|
|
139
|
+
config = @data[:config]
|
|
140
|
+
else
|
|
141
|
+
config = File.expand_path("~/.sanguinews.conf")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if File.exist?(config)
|
|
145
|
+
parse_config(config)
|
|
146
|
+
else
|
|
147
|
+
config_gen
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -65,11 +65,8 @@ module Sanguinews
|
|
|
65
65
|
until self.eof?
|
|
66
66
|
f = self.read(@@max_mem)
|
|
67
67
|
crc32 = Zlib.crc32(f, 0)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
else
|
|
71
|
-
fcrc32 = Zlib.crc32_combine(fcrc32, crc32, f.size)
|
|
72
|
-
end
|
|
68
|
+
fcrc32 &&= Zlib.crc32_combine(fcrc32, crc32, f.size)
|
|
69
|
+
fcrc32 ||= crc32
|
|
73
70
|
end
|
|
74
71
|
self.rewind
|
|
75
72
|
fcrc32.to_s(16)
|
|
@@ -99,7 +96,7 @@ module Sanguinews
|
|
|
99
96
|
end
|
|
100
97
|
|
|
101
98
|
def max_mem
|
|
102
|
-
|
|
99
|
+
@@max_mem ||= begin
|
|
103
100
|
memory = Vmstat.memory
|
|
104
101
|
@@max_mem = (memory[:free] * memory[:pagesize] * 0.1).floor
|
|
105
102
|
end
|
data/lib/sanguinews/nntp_msg.rb
CHANGED
|
@@ -22,17 +22,14 @@ module Sanguinews
|
|
|
22
22
|
class NntpMsg
|
|
23
23
|
attr_accessor :message, :from, :groups, :subject, :poster, :date, :xna
|
|
24
24
|
|
|
25
|
-
def initialize(from, groups, subject, message='',
|
|
25
|
+
def initialize(from, groups, subject, message='', **opts)
|
|
26
26
|
@from = from
|
|
27
27
|
@groups = groups
|
|
28
28
|
@subject = subject
|
|
29
29
|
@message = message
|
|
30
|
-
if date
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@date = date
|
|
34
|
-
end
|
|
35
|
-
@poster = poster if !poster.nil?
|
|
30
|
+
@date = opts[:date] if opts[:date]
|
|
31
|
+
@date ||= DateTime.now().strftime('%a, %d %b %Y %T %z')
|
|
32
|
+
@poster = opts[:poster] if opts[:poster]
|
|
36
33
|
end
|
|
37
34
|
|
|
38
35
|
def create_header
|
|
@@ -40,7 +37,7 @@ module Sanguinews
|
|
|
40
37
|
sio.puts "From: #{@from}"
|
|
41
38
|
sio.puts "Newsgroups: #{@groups}"
|
|
42
39
|
sio.puts "Subject: #{@subject}"
|
|
43
|
-
sio.puts "X-Newsposter: #{@poster}"
|
|
40
|
+
sio.puts "X-Newsposter: #{@poster}" if @poster
|
|
44
41
|
sio.puts "X-No-Archive: yes" if @xna
|
|
45
42
|
sio.puts "Date: #{@date}"
|
|
46
43
|
sio.puts
|
|
@@ -72,7 +69,6 @@ module Sanguinews
|
|
|
72
69
|
def return_self
|
|
73
70
|
header = self.create_header
|
|
74
71
|
header << @message
|
|
75
|
-
return header
|
|
76
72
|
end
|
|
77
73
|
|
|
78
74
|
def size
|
data/lib/sanguinews/version.rb
CHANGED
data/lib/sanguinews.rb
CHANGED
|
@@ -31,19 +31,20 @@ require_relative 'sanguinews/nntp'
|
|
|
31
31
|
require_relative 'sanguinews/nntp_msg'
|
|
32
32
|
require_relative 'sanguinews/file_to_upload'
|
|
33
33
|
require_relative 'sanguinews/yencoded'
|
|
34
|
+
require_relative 'sanguinews/config'
|
|
34
35
|
require_relative 'sanguinews/version'
|
|
35
36
|
|
|
36
37
|
module Sanguinews
|
|
37
38
|
module_function
|
|
38
39
|
# Method returns yenc encoded string and crc32 value
|
|
39
40
|
def yencode(file, length, queue)
|
|
40
|
-
|
|
41
|
+
chunk = 1
|
|
41
42
|
until file.eof?
|
|
42
43
|
bindata = file.read(length)
|
|
43
44
|
# We can't take all memory, so we wait
|
|
44
45
|
queue.synchronize do
|
|
45
46
|
@cond.wait_while do
|
|
46
|
-
queue.length > @
|
|
47
|
+
queue.length > @config.connections * 3
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
data = {}
|
|
@@ -52,12 +53,12 @@ module Sanguinews
|
|
|
52
53
|
data[:yenc] = Yencoded::Data.yenc(bindata, len)
|
|
53
54
|
data[:crc32] = Zlib.crc32(bindata, 0).to_s(16)
|
|
54
55
|
data[:length] = len
|
|
55
|
-
data[:chunk] =
|
|
56
|
+
data[:chunk] = chunk
|
|
56
57
|
data[:file] = file
|
|
57
58
|
final_data[0] = form_message(data)
|
|
58
59
|
final_data[1] = file
|
|
59
60
|
queue.push(final_data)
|
|
60
|
-
|
|
61
|
+
chunk += 1
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
|
|
@@ -72,168 +73,68 @@ module Sanguinews
|
|
|
72
73
|
chunks = file.chunks
|
|
73
74
|
basename = file.name
|
|
74
75
|
# usenet works with ASCII
|
|
75
|
-
subject="#{@prefix}#{file.dir_prefix}\"#{basename}\" yEnc (#{chunk}/#{chunks})"
|
|
76
|
-
msg = NntpMsg.new(@from, @groups, subject)
|
|
76
|
+
subject="#{@config.prefix}#{file.dir_prefix}\"#{basename}\" yEnc (#{chunk}/#{chunks})"
|
|
77
|
+
msg = NntpMsg.new(@config.from, @config.groups, subject)
|
|
77
78
|
msg.poster = "sanguinews v#{Sanguinews::VERSION} (ruby #{RUBY_VERSION}) - https://github.com/tdobrovolskij/sanguinews"
|
|
78
|
-
msg.xna = @xna
|
|
79
|
+
msg.xna = @config.xna
|
|
79
80
|
msg.message = message.force_encoding('ASCII-8BIT')
|
|
80
81
|
msg.yenc_body(chunk, chunks, crc32, pcrc32, length, fsize, basename)
|
|
81
82
|
msg = msg.return_self
|
|
82
83
|
{ message: msg, filename: basename, chunk: chunk, length: length }
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
def connect(
|
|
86
|
+
def connect(conn_nr)
|
|
86
87
|
begin
|
|
87
|
-
nntp = Net::NNTP.start(
|
|
88
|
+
nntp = Net::NNTP.start(
|
|
89
|
+
@config.server, @config.port, @config.username, @config.password, @config.mode)
|
|
88
90
|
rescue
|
|
89
|
-
@s.log([$!, $@], stderr: true) if @debug
|
|
90
|
-
if @verbose
|
|
91
|
+
@s.log([$!, $@], stderr: true) if @config.debug
|
|
92
|
+
if @config.verbose
|
|
91
93
|
parse_error($!.to_s)
|
|
92
|
-
@s.log("Connection nr. #{
|
|
94
|
+
@s.log("Connection nr. #{conn_nr} has failed. Reconnecting...\n", stderr: true)
|
|
93
95
|
end
|
|
94
|
-
sleep @
|
|
96
|
+
sleep @config.reconnect_delay
|
|
95
97
|
retry
|
|
96
98
|
end
|
|
97
99
|
return nntp
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
def
|
|
101
|
-
config = ParseConfig.new(config)
|
|
102
|
-
config.get_params()
|
|
103
|
-
@username = config['username']
|
|
104
|
-
@password = config['password']
|
|
105
|
-
@from = config['from']
|
|
106
|
-
@server = config['server']
|
|
107
|
-
@port = config['port']
|
|
108
|
-
@threads = config['connections'].to_i
|
|
109
|
-
@length = config['article_size'].to_i
|
|
110
|
-
@delay = config['reconnect_delay'].to_i
|
|
111
|
-
@groups = config['groups']
|
|
112
|
-
@prefix = config['prefix']
|
|
113
|
-
ssl = config['ssl']
|
|
114
|
-
if ssl == 'yes'
|
|
115
|
-
@mode = :tls
|
|
116
|
-
else
|
|
117
|
-
@mode = :original
|
|
118
|
-
end
|
|
119
|
-
config['xna'] == 'yes' ? @xna = true : @xna = false
|
|
120
|
-
config['nzb'] == 'yes' ? @nzb = true : @nzb = false
|
|
121
|
-
config['header_check'] == 'yes' ? @header_check = true : @header_check = false
|
|
122
|
-
config['debug'] == 'yes' ? @debug = true : @debug = false
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def get_msgid(response)
|
|
102
|
+
def get_msgid(responses)
|
|
126
103
|
msgid = ''
|
|
127
|
-
|
|
128
|
-
msgid =
|
|
104
|
+
responses.each do |response|
|
|
105
|
+
msgid = response.sub(/>.*/, '').tr("<", '') if response.end_with?('Article posted')
|
|
129
106
|
end
|
|
130
107
|
return msgid
|
|
131
108
|
end
|
|
132
109
|
|
|
133
|
-
def parse_options(args)
|
|
134
|
-
# version and legal info presented to user
|
|
135
|
-
banner = []
|
|
136
|
-
banner << ""
|
|
137
|
-
banner << "sanguinews v#{Sanguinews::VERSION}. Copyright (c) 2013-2014 Tadeus Dobrovolskij."
|
|
138
|
-
banner << "Comes with ABSOLUTELY NO WARRANTY. Distributed under GPL v2 license(http://www.gnu.org/licenses/gpl-2.0.txt)."
|
|
139
|
-
banner << "sanguinews is a simple nntp(usenet) binary poster. It supports multithreading and SSL. More info in README."
|
|
140
|
-
banner << ""
|
|
141
|
-
# option parser
|
|
142
|
-
options = {}
|
|
143
|
-
options[:filemode] = false
|
|
144
|
-
options[:files] = []
|
|
145
|
-
|
|
146
|
-
opt_parser = OptionParser.new do |opt|
|
|
147
|
-
opt.banner = "Usage: #{$0} [OPTIONS] [DIRECTORY] | -f FILE1..[FILEX]"
|
|
148
|
-
opt.separator ""
|
|
149
|
-
opt.separator "Options"
|
|
150
|
-
|
|
151
|
-
opt.on("-c", "--config CONFIG", "use different config file") do |cfg|
|
|
152
|
-
options[:config] = cfg
|
|
153
|
-
end
|
|
154
|
-
opt.on("-C", "--check", "check headers while uploading; slow but reliable") do
|
|
155
|
-
options[:header_check] = true
|
|
156
|
-
end
|
|
157
|
-
opt.on("-f", "--file FILE", "upload FILE, treat all additional parameters as files") do |file|
|
|
158
|
-
options[:filemode] = true
|
|
159
|
-
options[:files] << file
|
|
160
|
-
end
|
|
161
|
-
opt.on("-g", "--groups GROUP_LIST", "use these groups(comma separated) for upload") do |group_list|
|
|
162
|
-
options[:groups] = group_list
|
|
163
|
-
end
|
|
164
|
-
opt.on("-h", "--help", "help") do
|
|
165
|
-
banner.each do |msg|
|
|
166
|
-
puts msg
|
|
167
|
-
end
|
|
168
|
-
puts opt_parser
|
|
169
|
-
puts
|
|
170
|
-
exit
|
|
171
|
-
end
|
|
172
|
-
opt.on("-p", "--password PASSWORD", "use PASSWORD as your password(overwrites config file)") do |password|
|
|
173
|
-
options[:password] = password
|
|
174
|
-
end
|
|
175
|
-
opt.on("-u", "--user USERNAME", "use USERNAME as your username(overwrites config file)") do |username|
|
|
176
|
-
options[:username] = username
|
|
177
|
-
end
|
|
178
|
-
opt.on("-v", "--verbose", "be verbose?") do
|
|
179
|
-
options[:verbose] = true
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
begin
|
|
184
|
-
opt_parser.parse!(args)
|
|
185
|
-
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
|
186
|
-
puts opt_parser
|
|
187
|
-
exit 1
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
options[:directory] = args[0] unless options[:filemode]
|
|
191
|
-
|
|
192
|
-
# in file mode treat every additional parameter as a file
|
|
193
|
-
if !args.empty? && options[:filemode]
|
|
194
|
-
args.each do |file|
|
|
195
|
-
options[:files] << file.to_s
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# exit when no file list is provided
|
|
200
|
-
if options[:directory].nil? && options[:files].empty?
|
|
201
|
-
puts "You need to specify something to upload!"
|
|
202
|
-
puts opt_parser
|
|
203
|
-
exit 1
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
return options
|
|
207
|
-
end
|
|
208
|
-
|
|
209
110
|
def parse_error(msg, **info)
|
|
210
|
-
if info[:file]
|
|
211
|
-
fileinfo = ''
|
|
212
|
-
else
|
|
111
|
+
if info[:file] && info[:chunk]
|
|
213
112
|
fileinfo = '(' + info[:file] + ' / Chunk: ' + info[:chunk].to_s + ')'
|
|
113
|
+
else
|
|
114
|
+
fileinfo = ''
|
|
214
115
|
end
|
|
215
116
|
|
|
216
117
|
case
|
|
217
118
|
when /\A411/ === msg
|
|
218
|
-
@s.log("Invalid newsgroup specified
|
|
119
|
+
@s.log("Invalid newsgroup specified.\n", stderr: true)
|
|
219
120
|
when /\A430/ === msg
|
|
220
|
-
@s.log("No such article. Maybe server is lagging...#{fileinfo}", stderr: true)
|
|
121
|
+
@s.log("No such article. Maybe server is lagging...#{fileinfo}\n", stderr: true)
|
|
221
122
|
when /\A(4\d{2}\s)?437/ === msg
|
|
222
|
-
@s.log("Article rejected by server. Maybe it's too big.#{fileinfo}", stderr: true)
|
|
123
|
+
@s.log("Article rejected by server. Maybe it's too big.#{fileinfo}\n", stderr: true)
|
|
223
124
|
when /\A440/ === msg
|
|
224
|
-
@s.log("Posting not allowed
|
|
125
|
+
@s.log("Posting not allowed.\n", stderr: true)
|
|
225
126
|
when /\A441/ === msg
|
|
226
|
-
@s.log("Posting failed for some reason.#{fileinfo}", stderr: true)
|
|
127
|
+
@s.log("Posting failed for some reason.#{fileinfo}\n", stderr: true)
|
|
227
128
|
when /\A450/ === msg
|
|
228
|
-
@s.log("Not authorized
|
|
129
|
+
@s.log("Not authorized.\n", stderr: true)
|
|
229
130
|
when /\A452/ === msg
|
|
230
|
-
@s.log("Wrong username and/or password
|
|
131
|
+
@s.log("Wrong username and/or password.\n", stderr: true)
|
|
231
132
|
when /\A500/ === msg
|
|
232
|
-
@s.log("Command not recognized
|
|
133
|
+
@s.log("Command not recognized.\n", stderr: true)
|
|
233
134
|
when /\A501/ === msg
|
|
234
|
-
@s.log("Command syntax error
|
|
135
|
+
@s.log("Command syntax error.\n", stderr: true)
|
|
235
136
|
when /\A502/ === msg
|
|
236
|
-
@s.log("Access denied
|
|
137
|
+
@s.log("Access denied.\n", stderr: true)
|
|
237
138
|
end
|
|
238
139
|
end
|
|
239
140
|
|
|
@@ -261,80 +162,52 @@ module Sanguinews
|
|
|
261
162
|
end
|
|
262
163
|
|
|
263
164
|
@s.start
|
|
264
|
-
|
|
165
|
+
check_delay = 1
|
|
265
166
|
begin
|
|
266
167
|
response = nntp.post msg
|
|
267
168
|
msgid = get_msgid(response)
|
|
268
|
-
if @header_check
|
|
269
|
-
sleep
|
|
169
|
+
if @config.header_check
|
|
170
|
+
sleep check_delay
|
|
270
171
|
nntp.stat("<#{msgid}>")
|
|
271
172
|
end
|
|
272
173
|
rescue
|
|
273
|
-
@s.log([$!, $@], stderr: true) if @debug
|
|
274
|
-
if @verbose
|
|
174
|
+
@s.log([$!, $@], stderr: true) if @config.debug
|
|
175
|
+
if @config.verbose
|
|
275
176
|
parse_error($!.to_s, file: basename, chunk: chunk)
|
|
276
177
|
@s.log("Upload of chunk #{chunk} from file #{basename} unsuccessful. Retrying...\n", stderr: true)
|
|
277
178
|
end
|
|
278
|
-
sleep @
|
|
279
|
-
|
|
179
|
+
sleep @config.reconnect_delay
|
|
180
|
+
check_delay += 4
|
|
280
181
|
retry
|
|
281
182
|
end
|
|
282
183
|
|
|
283
|
-
if @verbose
|
|
184
|
+
if @config.verbose
|
|
284
185
|
@s.log("Uploaded chunk Nr:#{chunk}\n", stderr: true)
|
|
285
186
|
end
|
|
286
187
|
|
|
287
188
|
@s.done(length)
|
|
288
189
|
@s.uploaded += full_size
|
|
289
|
-
if @nzb
|
|
190
|
+
if @config.nzb
|
|
290
191
|
file.write_segment_info(length, chunk, msgid)
|
|
291
192
|
end
|
|
292
193
|
nntp_pool.push(nntp)
|
|
293
194
|
end
|
|
294
195
|
|
|
295
196
|
def run!
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
config = File.expand_path(config)
|
|
299
|
-
# variable to store if config was parsed
|
|
300
|
-
saw_config = false
|
|
301
|
-
if File.exist?(config)
|
|
302
|
-
saw_config = true
|
|
303
|
-
parse_config(config)
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
options = parse_options(ARGV)
|
|
307
|
-
|
|
308
|
-
optconfig = options[:config]
|
|
309
|
-
optconfig = '' if optconfig.nil?
|
|
310
|
-
if !File.exist?(optconfig) && !saw_config
|
|
311
|
-
puts "No config information specified. Aborting..."
|
|
312
|
-
exit
|
|
313
|
-
end
|
|
314
|
-
parse_config(optconfig) if File.exist?(optconfig)
|
|
315
|
-
|
|
316
|
-
options[:verbose] ? @verbose = true : @verbose = false
|
|
317
|
-
@header_check = true unless options[:header_check].nil?
|
|
318
|
-
filemode = options[:filemode]
|
|
319
|
-
|
|
320
|
-
@username = options[:username] unless options[:username].nil?
|
|
321
|
-
@password = options[:password] unless options[:password].nil?
|
|
322
|
-
@groups = options[:groups] unless options[:groups].nil?
|
|
323
|
-
directory = options[:directory] unless filemode
|
|
324
|
-
files = options[:files]
|
|
197
|
+
@config = Config.new(ARGV)
|
|
198
|
+
files = @config.files
|
|
325
199
|
|
|
326
200
|
# skip hidden files
|
|
327
|
-
if
|
|
328
|
-
directory
|
|
329
|
-
Dir.foreach(directory) do |item|
|
|
201
|
+
if !@config.filemode
|
|
202
|
+
Dir.foreach(@config.directory) do |item|
|
|
330
203
|
next if item.start_with?('.')
|
|
331
|
-
files << directory+item
|
|
204
|
+
files << @config.directory+item
|
|
332
205
|
end
|
|
333
206
|
end
|
|
334
207
|
|
|
335
208
|
# "max" is needed only in dirmode
|
|
336
209
|
max = files.length
|
|
337
|
-
|
|
210
|
+
current_chunk = 1
|
|
338
211
|
|
|
339
212
|
unprocessed = 0
|
|
340
213
|
info_lock=Mutex.new
|
|
@@ -347,13 +220,13 @@ module Sanguinews
|
|
|
347
220
|
|
|
348
221
|
pool = Queue.new
|
|
349
222
|
Thread.new {
|
|
350
|
-
@
|
|
351
|
-
nntp = connect(
|
|
223
|
+
@config.connections.times do |conn_nr|
|
|
224
|
+
nntp = connect(conn_nr)
|
|
352
225
|
pool.push(nntp)
|
|
353
226
|
end
|
|
354
227
|
}
|
|
355
228
|
|
|
356
|
-
|
|
229
|
+
thread_pool = Pool.new(@config.connections)
|
|
357
230
|
informed = {}
|
|
358
231
|
|
|
359
232
|
files.each do |file|
|
|
@@ -361,9 +234,9 @@ module Sanguinews
|
|
|
361
234
|
|
|
362
235
|
informed[file.to_sym] = false
|
|
363
236
|
file = FileToUpload.new(
|
|
364
|
-
name: file, chunk_length: @
|
|
365
|
-
|
|
366
|
-
|
|
237
|
+
name: file, chunk_length: @config.article_size, prefix: @config.prefix,
|
|
238
|
+
current: current_chunk, last: max, filemode: @config.filemode,
|
|
239
|
+
from: @config.from, groups: @config.groups, nzb: @config.nzb
|
|
367
240
|
)
|
|
368
241
|
@s.to_upload += file.size
|
|
369
242
|
|
|
@@ -372,28 +245,28 @@ module Sanguinews
|
|
|
372
245
|
end
|
|
373
246
|
|
|
374
247
|
files_to_process << file
|
|
375
|
-
|
|
248
|
+
current_chunk += 1
|
|
376
249
|
end
|
|
377
250
|
|
|
378
251
|
# let's give a little bit higher priority for file processing thread
|
|
379
|
-
@
|
|
252
|
+
@file_proc_thread = Thread.new {
|
|
380
253
|
files_to_process.each do |file|
|
|
381
254
|
@s.log("Calculating CRC32 value for #{file.name}\n", stderr: true) if @verbose
|
|
382
255
|
file.file_crc32
|
|
383
256
|
@s.log("Encoding #{file.name}\n")
|
|
384
|
-
yencode(file, @
|
|
257
|
+
yencode(file, @config.article_size, messages)
|
|
385
258
|
end
|
|
386
259
|
}
|
|
387
|
-
@
|
|
260
|
+
@file_proc_thread.priority += 2
|
|
388
261
|
|
|
389
262
|
until unprocessed == 0
|
|
390
|
-
|
|
263
|
+
thread_pool.schedule do
|
|
391
264
|
process_and_upload(messages, pool, info_lock, informed)
|
|
392
265
|
end
|
|
393
266
|
unprocessed -= 1
|
|
394
267
|
end
|
|
395
268
|
|
|
396
|
-
|
|
269
|
+
thread_pool.shutdown
|
|
397
270
|
|
|
398
271
|
until pool.empty?
|
|
399
272
|
nntp = pool.pop
|
data/sample.conf
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Sample config file
|
|
2
|
+
# Groups to post to
|
|
3
|
+
# Separate multiple groups with comma
|
|
4
|
+
groups = alt.binaries.test
|
|
5
|
+
# Your identity
|
|
6
|
+
from = witty_nickname <whatever@example.com>
|
|
7
|
+
|
|
8
|
+
# Username to use for authentication
|
|
9
|
+
username = your_username
|
|
10
|
+
# Password
|
|
11
|
+
password = your_password
|
|
12
|
+
# server to use
|
|
13
|
+
server = your_server
|
|
14
|
+
# Use SSL connection?
|
|
15
|
+
ssl = yes
|
|
16
|
+
# port
|
|
17
|
+
port = 563
|
|
18
|
+
# number of connections
|
|
19
|
+
connections = 10
|
|
20
|
+
# article size in bytes
|
|
21
|
+
article_size = 768000
|
|
22
|
+
# Wait this many seconds before trying to reconnect after unsuccessful upload
|
|
23
|
+
reconnect_delay = 5
|
|
24
|
+
# Subject prefix to use
|
|
25
|
+
prefix = "[sanguinews] - "
|
|
26
|
+
# Enable nzb creation
|
|
27
|
+
nzb = yes
|
|
28
|
+
# Use header checking? Upload will be slow but reliable.
|
|
29
|
+
header_check = no
|
|
30
|
+
|
|
31
|
+
# Debug information
|
|
32
|
+
debug = no
|
|
33
|
+
#######################
|
|
34
|
+
# headers
|
|
35
|
+
#######################
|
|
36
|
+
# X-No-Archive
|
|
37
|
+
xna = no
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sanguinews
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.61'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tadeus Dobrovolskij
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2014-10-
|
|
11
|
+
date: 2014-10-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: speedometer
|
|
@@ -121,11 +121,13 @@ files:
|
|
|
121
121
|
- ext/yencoded/yencoded.c
|
|
122
122
|
- ext/yencoded/yencoded.h
|
|
123
123
|
- lib/sanguinews.rb
|
|
124
|
+
- lib/sanguinews/config.rb
|
|
124
125
|
- lib/sanguinews/file_to_upload.rb
|
|
125
126
|
- lib/sanguinews/nntp.rb
|
|
126
127
|
- lib/sanguinews/nntp_msg.rb
|
|
127
128
|
- lib/sanguinews/thread-pool.rb
|
|
128
129
|
- lib/sanguinews/version.rb
|
|
130
|
+
- sample.conf
|
|
129
131
|
homepage: http://www.tad-do.net
|
|
130
132
|
licenses:
|
|
131
133
|
- GPLv2
|