fargo 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/lib/fargo/client.rb +161 -0
- data/lib/fargo/connection/base.rb +133 -0
- data/lib/fargo/connection/download.rb +302 -0
- data/lib/fargo/connection/hub.rb +120 -0
- data/lib/fargo/connection/search.rb +19 -0
- data/lib/fargo/connection/upload.rb +85 -0
- data/lib/fargo/parser.rb +121 -0
- data/lib/fargo/publisher.rb +26 -0
- data/lib/fargo/search.rb +61 -0
- data/lib/fargo/search_result.rb +31 -0
- data/lib/fargo/server.rb +52 -0
- data/lib/fargo/supports/chat.rb +35 -0
- data/lib/fargo/supports/downloads.rb +261 -0
- data/lib/fargo/supports/file_list.rb +84 -0
- data/lib/fargo/supports/nick_list.rb +71 -0
- data/lib/fargo/supports/persistence.rb +52 -0
- data/lib/fargo/supports/searches.rb +64 -0
- data/lib/fargo/supports/timeout.rb +19 -0
- data/lib/fargo/supports/uploads.rb +16 -0
- data/lib/fargo/utils.rb +35 -0
- data/lib/fargo/version.rb +3 -0
- data/lib/fargo.rb +48 -0
- metadata +127 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Connection
|
3
|
+
class Search < Base
|
4
|
+
|
5
|
+
# maybe do something special here at some point?
|
6
|
+
# this is currently just receiving the search result packets over UDP
|
7
|
+
# and fowarding them to the client who will handle them. This doesn't
|
8
|
+
# explicitly disconnect because I'm not sure if multiple results
|
9
|
+
# are sent. This connection will close itself because there will
|
10
|
+
# be a read error I think...
|
11
|
+
def receive data
|
12
|
+
message = parse_message data
|
13
|
+
|
14
|
+
@client.publish message[:type], message
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# TODO: actually get this class to work in some fashion
|
2
|
+
module Fargo
|
3
|
+
module Connection
|
4
|
+
class Upload < Base
|
5
|
+
|
6
|
+
include Fargo::Utils
|
7
|
+
include Fargo::Parser
|
8
|
+
|
9
|
+
def post_listen
|
10
|
+
@lock, @pk = generate_lock
|
11
|
+
write "$MyNick #{self[:nick]}|$Lock #{@lock} Pk=#{@pk}"
|
12
|
+
@handshake_step = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def supports
|
16
|
+
"$Supports BZList TTHL TTHF" # ???
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive data
|
20
|
+
message = parse_message data
|
21
|
+
publish message[:type], message
|
22
|
+
case message[:type]
|
23
|
+
when :mynick
|
24
|
+
if @handshake_step == 0
|
25
|
+
@remote_nick = message[:nick]
|
26
|
+
@handshake_step = 1
|
27
|
+
else
|
28
|
+
disconnect
|
29
|
+
end
|
30
|
+
when :lock
|
31
|
+
if @handshake_step == 1
|
32
|
+
@remote_lock = message[:lock]
|
33
|
+
@handshake_step = 2
|
34
|
+
else
|
35
|
+
disconnect
|
36
|
+
end
|
37
|
+
when :supports
|
38
|
+
if @handshake_step == 2
|
39
|
+
@remote_extensions = message[:extensions]
|
40
|
+
@handshake_step = 3
|
41
|
+
else
|
42
|
+
disconnect
|
43
|
+
end
|
44
|
+
when :direction
|
45
|
+
if @handshake_step == 3 && message[:direction] == 'download'
|
46
|
+
@handshake_step = 4
|
47
|
+
@client_num = message[:number]
|
48
|
+
else
|
49
|
+
disconnect
|
50
|
+
end
|
51
|
+
when :key
|
52
|
+
if @handshake_step == 4 && generate_key(@lock) == message[:key]
|
53
|
+
write supports
|
54
|
+
write "$Direction Download #{@my_num = rand 10000}"
|
55
|
+
write "$Key #{generate_key @remote_lock}"
|
56
|
+
@handshake_step = 5
|
57
|
+
else
|
58
|
+
disconnect
|
59
|
+
end
|
60
|
+
when :get
|
61
|
+
if @handshake_step == 5
|
62
|
+
@filepath = message[:path]
|
63
|
+
@offset = message[:offset]
|
64
|
+
write "$FileLength #{file_length}"
|
65
|
+
@handshake_step = 5
|
66
|
+
else
|
67
|
+
disconnect
|
68
|
+
end
|
69
|
+
when :send
|
70
|
+
write_chunk if @handshake_step == 5
|
71
|
+
|
72
|
+
else
|
73
|
+
# Fargo.logger.warn "Ignoring `#{data}'\n"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_chunk
|
78
|
+
end
|
79
|
+
|
80
|
+
def file_length
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/fargo/parser.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Parser
|
3
|
+
|
4
|
+
#
|
5
|
+
# See <http://www.teamfair.info/DC-Protocol.htm> for more information
|
6
|
+
#
|
7
|
+
@@commandmatch = /\$(.*)$/
|
8
|
+
@@messagematch = /^<(.*?)> (.*)$/
|
9
|
+
|
10
|
+
# TODO: Supports, UserIP, ops command
|
11
|
+
# Client - hub commands
|
12
|
+
@@validatedenied = /^ValidateDenide/
|
13
|
+
@@getpass = /^GetPass$/
|
14
|
+
@@badpass = /^BadPass$/
|
15
|
+
@@lock = /^Lock (.*) Pk=.*?$/
|
16
|
+
@@userip = /^UserIP (.*)$/
|
17
|
+
@@hubname = /^HubName (.*)$/
|
18
|
+
@@hubfull = /^HubIsFull$/
|
19
|
+
@@hubtopic = /^HubTopic (.*)$/
|
20
|
+
@@hello = /^Hello (.*)$/
|
21
|
+
@@myinfo = /^MyINFO \$ALL (.*?) (.*?)\$ \$(.*?).\$(.*?)\$(.*?)\$/
|
22
|
+
@@myinfo2 = /^MyINFO \$ALL (.*?) (.*?)\$$/
|
23
|
+
@@to = /^To: (.*?) From: (.*?) \$<.*?> (.*)$/
|
24
|
+
@@hubto = /^To: (.*?) From: Hub \$(.*)$/
|
25
|
+
@@ctm = /^ConnectToMe (.*?) (.*?):(.*?)$/
|
26
|
+
@@nicklist = /^NickList (.*?)$/
|
27
|
+
@@psr = /^SR (.*?) (.*?)\005(.*?) (.*?)\/(.*?)\005(.*?) \((.*?):(.*?)\)$/
|
28
|
+
@@psearch = /^Search Hub:(.*) (.)\?(.)\?(.*)\?(.)\?(.*)$/
|
29
|
+
@@search = /^Search (.*):(.*) (.)\?(.)\?(.*)\?(.)\?(.*)$/
|
30
|
+
@@oplist = /^OpList (.*?)$/
|
31
|
+
@@botlist = /^BotList (.*?)$/
|
32
|
+
@@quit = /^Quit (.*)$/
|
33
|
+
@@sr = /^SR (.*?) (.*?)\005(.*?) (.*?)\/(.*?)\005(.*?) (.*?):(.*?)$/
|
34
|
+
@@rctm = /^RevConnectToMe (.*?) (.*?)$/
|
35
|
+
|
36
|
+
# Client to client commands
|
37
|
+
@@mynick = /^MyNick (.*)$/
|
38
|
+
@@key = /^Key (.*)$/
|
39
|
+
@@direction = /^Direction (Download|Upload) (\d+)$/
|
40
|
+
@@get = /^Get (.*)\$(\d+)$/
|
41
|
+
@@send = /^Send$/
|
42
|
+
@@filelength = /^FileLength (.*?)$/
|
43
|
+
@@getlistlen = /^GetListLen$/
|
44
|
+
@@maxedout = /^MaxedOut$/
|
45
|
+
@@supports = /^Supports (.*)$/
|
46
|
+
@@error = /^Error (.*)$/
|
47
|
+
@@ugetblock = /^UGetBlock (.*?) (.*?) (.*)$/
|
48
|
+
@@adcsnd = /^ADCSND (.*?) (.*?) (.*?) (.*?)$/
|
49
|
+
@@adcsnd_zl1 = /^ADCSND (.*?) (.*?) (.*?) (.*?) ZL1$/
|
50
|
+
|
51
|
+
def parse_message text
|
52
|
+
case text
|
53
|
+
when @@commandmatch then parse_command_message $1
|
54
|
+
when @@messagematch then {:type => :chat, :from => $1, :text => $2}
|
55
|
+
when '' then {:type => :garbage}
|
56
|
+
else {:type => :mystery, :text => text}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_command_message text
|
61
|
+
case text
|
62
|
+
when @@validatedenied then {:type => :denide}
|
63
|
+
when @@getpass then {:type => :getpass}
|
64
|
+
when @@badpass then {:type => :badpass}
|
65
|
+
when @@lock then {:type => :lock, :lock => $1}
|
66
|
+
when @@hubname then {:type => :hubname, :name => $1}
|
67
|
+
when @@hubname then {:type => :hubfull}
|
68
|
+
when @@hubtopic then {:type => :hubtopic, :topic => $1}
|
69
|
+
when @@hello then {:type => :hello, :who => $1}
|
70
|
+
when @@myinfo then {:type => :myinfo, :nick => $1, :interest => $2, :speed => $3,
|
71
|
+
:email => $4, :sharesize => $5.to_i}
|
72
|
+
when @@myinfo2 then {:type => :myinfo, :nick=> $1, :interest => $2, :sharesize=> 0}
|
73
|
+
when @@to then {:type => :privmsg, :to => $1, :from => $2, :text => $3}
|
74
|
+
when @@hubto then {:type => :privmsg, :to => $1, :from => "Hub", :text => $2}
|
75
|
+
when @@ctm then {:type => :connect_to_me, :nick => $1, :address => $2,
|
76
|
+
:port => $3.to_i}
|
77
|
+
when @@nicklist then {:type => :nick_list, :nicks => $1.split(/\$\$/)}
|
78
|
+
when @@psr then {:type => :search_result, :nick => $1, :file => $2,
|
79
|
+
:size => $3.to_i, :open_slots => $4.to_i, :slots => $5.to_i,
|
80
|
+
:hub => $6, :address => $7,
|
81
|
+
:port => $8.to_i}
|
82
|
+
when @@psearch then {:type => :search, :searcher => $1, :restrict_size => $2,
|
83
|
+
:min_size => $3.to_i, :size => $4.to_i, :filetype => $5,
|
84
|
+
:pattern => $6}
|
85
|
+
when @@search then {:type => :search, :address => $1, :port => $2.to_i,
|
86
|
+
:restrict_size => $3,
|
87
|
+
:min_size => $4.to_i, :size => $5.to_i, :filetype => $6,
|
88
|
+
:pattern => $7}
|
89
|
+
when @@oplist then {:type => :op_list, :nicks => $1.split(/\$\$/)}
|
90
|
+
when @@oplist then {:type => :bot_list, :nicks => $1.split(/\$\$/)}
|
91
|
+
when @@quit then {:type => :quit, :who => $1}
|
92
|
+
when @@sr then {:type => :search_result, :nick => $2, :file => $3,
|
93
|
+
:size => $4.to_i, :open_slots => $5.to_i, :slots => $6.to_i,
|
94
|
+
:hubname => $7}
|
95
|
+
when @@rctm then {:type => :revconnect, :who => $1}
|
96
|
+
|
97
|
+
when @@mynick then {:type => :mynick, :nick => $1}
|
98
|
+
when @@key then {:type => :key, :key => $1}
|
99
|
+
when @@direction then {:type => :direction, :direction => $1.downcase, :number => $3.to_i}
|
100
|
+
when @@get then {:type => :get, :path => $1, :offset => $2.to_i - 1}
|
101
|
+
when @@send then {:type => :send}
|
102
|
+
when @@filelength then {:type => :file_length, :size => $1.to_i}
|
103
|
+
when @@getlistlen then {:type => :getlistlen}
|
104
|
+
when @@maxedout then {:type => :noslots}
|
105
|
+
when @@supports then {:type => :supports, :extensions => $1.split(' ')}
|
106
|
+
when @@error then {:type => :error, :message => $1}
|
107
|
+
when @@adcsnd_zl1 then {:type => :adcsnd, :kind => $1, :tth => $2, :offset => $3.to_i,
|
108
|
+
:size => $4.to_i, :zlib => true}
|
109
|
+
when @@adcsnd then {:type => :adcsnd, :kind => $1, :tth => $2, :offset => $3.to_i,
|
110
|
+
:size => $4.to_i}
|
111
|
+
when @@ugetblock then {:type => :ugetblock, :start => $1.to_i, :finish => $2.to_i,
|
112
|
+
:path => $3}
|
113
|
+
when @@userip then
|
114
|
+
h = {:type => :userip, :users => {}}
|
115
|
+
$1.split("$$").map{ |s| h[:users][s.split(' ')[0]] = s.split(' ')[1]}
|
116
|
+
h
|
117
|
+
else {:type => :mystery, :text => text}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Publisher
|
3
|
+
|
4
|
+
attr_reader :subscribers
|
5
|
+
|
6
|
+
def subscribe &subscriber
|
7
|
+
raise RuntimeError.new("Need a subscription block!") if subscriber.nil?
|
8
|
+
Fargo.logger.debug "#{self}: subscribing #{subscriber}"
|
9
|
+
(@subscribers ||= []) << subscriber
|
10
|
+
end
|
11
|
+
|
12
|
+
def subscribed_to?
|
13
|
+
@subscribers && @subscribers.size > 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def unsubscribe &subscriber
|
17
|
+
raise RuntimeError.new("Need a subscription block!") if subscriber.nil?
|
18
|
+
Fargo.logger.debug "#{self}: unsubscribing #{subscriber}"
|
19
|
+
(@subscribers ||= []).delete subscriber
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish message_type, hash = {}
|
23
|
+
@subscribers.each { |subscriber| subscriber.call message_type, hash } if @subscribers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/fargo/search.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Fargo
|
2
|
+
class Search
|
3
|
+
|
4
|
+
ANY = 1
|
5
|
+
AUDIO = 2
|
6
|
+
COMPRESSED = 3
|
7
|
+
DOCUMENT = 4
|
8
|
+
EXECUTABLE = 5
|
9
|
+
VIDEO = 7
|
10
|
+
FOLDER = 8
|
11
|
+
|
12
|
+
attr_accessor :size_restricted, :is_minimum_size, :size, :filetype, :pattern
|
13
|
+
|
14
|
+
def initialize opts = {}
|
15
|
+
self.size_restricted = opts[:size_restricted]
|
16
|
+
self.is_minimum_size = opts[:is_minimum_size]
|
17
|
+
self.size = opts[:size]
|
18
|
+
self.filetype = opts[:filetype] || ANY
|
19
|
+
if opts[:pattern]
|
20
|
+
self.pattern = opts[:pattern]
|
21
|
+
elsif opts[:query]
|
22
|
+
self.query = opts[:query]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def query= query
|
27
|
+
@pattern = query.split(' ').join('$')
|
28
|
+
end
|
29
|
+
|
30
|
+
def queries
|
31
|
+
pattern.split("$")
|
32
|
+
end
|
33
|
+
|
34
|
+
def query
|
35
|
+
pattern.gsub('$', ' ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def matches_result? map
|
39
|
+
file = map[:file].downcase
|
40
|
+
matches_query = queries.inject(true) { |last, word| last && file.index(word.downcase) }
|
41
|
+
if size_restricted == 'T'
|
42
|
+
if is_minimum_size
|
43
|
+
matches_query && map[:size] > size
|
44
|
+
else
|
45
|
+
matches_query && map[:size] < size
|
46
|
+
end
|
47
|
+
else
|
48
|
+
matches_query
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
if size_restricted
|
54
|
+
"#{size_restricted ? 'T' : 'F' }?#{!size_restricted || is_minimum_size ? 'T' : 'F'}?#{size || 0}?#{filetype}?#{pattern}"
|
55
|
+
else
|
56
|
+
"F?T?#{size || 0}?#{filetype}?#{pattern}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fargo
|
2
|
+
class SearchResult
|
3
|
+
|
4
|
+
# Needs :file, :filesize, :client, :target (if passive)
|
5
|
+
def initialize file, filesize, client, target = nil
|
6
|
+
@file, @filesize, @client, @target = file, filesize, client, target
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
file = @file.gusb '/', "\\"
|
11
|
+
if File.directory? @file
|
12
|
+
s = file
|
13
|
+
else
|
14
|
+
s = "#{file}\005#{@filesize}"
|
15
|
+
end
|
16
|
+
|
17
|
+
s << sprintf(" %d/%d\005%s (%s:%d)", @client.open_slots,
|
18
|
+
@client.slots,
|
19
|
+
@client.hub.hubname,
|
20
|
+
@client.hub.config.ip,
|
21
|
+
@client.hub.config.port)
|
22
|
+
s << "\005#{@target}" if @client.config.passive
|
23
|
+
end
|
24
|
+
|
25
|
+
def active_send nick, ip, port
|
26
|
+
socket = UDPSocket.new
|
27
|
+
socket.send "$SR #{nick} #{to_s}", 0, ip, port
|
28
|
+
socket.close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/fargo/server.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Fargo
|
2
|
+
class Server
|
3
|
+
|
4
|
+
include Fargo::Publisher
|
5
|
+
|
6
|
+
def initialize options = {}
|
7
|
+
@options = options
|
8
|
+
@options[:address] = '0.0.0.0'
|
9
|
+
@peers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def connected?
|
13
|
+
!@server.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect
|
17
|
+
return if connected?
|
18
|
+
|
19
|
+
Fargo.logger.info "#{self}: Starting server on #{@options[:address]}:#{@options[:port]}"
|
20
|
+
|
21
|
+
@server = TCPServer.new @options[:address], @options[:port]
|
22
|
+
|
23
|
+
@active_thread = Thread.start { loop {
|
24
|
+
|
25
|
+
connection = @options[:connection].new @options.merge(:first => false)
|
26
|
+
|
27
|
+
connection_type = self.class.name.split("::").last.downcase
|
28
|
+
disconnect_symbol = :"#{connection_type}_disconnected"
|
29
|
+
|
30
|
+
connection.subscribe{ |type, hash|
|
31
|
+
@peers.delete connection if type == disconnect_symbol
|
32
|
+
}
|
33
|
+
|
34
|
+
connection.socket = @server.accept
|
35
|
+
connection.listen
|
36
|
+
@peers << connection
|
37
|
+
} }
|
38
|
+
end
|
39
|
+
|
40
|
+
def disconnect
|
41
|
+
Fargo.logger.info "#{self}: disconnecting..."
|
42
|
+
@active_thread.exit if @active_thread
|
43
|
+
|
44
|
+
@server.close if @server rescue nil
|
45
|
+
@server = nil
|
46
|
+
|
47
|
+
@peers.each{ |p| p.disconnect }
|
48
|
+
@peers.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Supports
|
3
|
+
module Chat
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
set_callback :setup, :after, :subscribe_to_chats
|
8
|
+
end
|
9
|
+
|
10
|
+
def messages
|
11
|
+
@public_chats
|
12
|
+
end
|
13
|
+
|
14
|
+
def messages_with nick
|
15
|
+
@chats[nick] if @chats
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribe_to_chats
|
19
|
+
@public_chats = []
|
20
|
+
@chats = Hash.new{ |h, k| h[k] = [] }
|
21
|
+
|
22
|
+
subscribe do |type, map|
|
23
|
+
if type == :chat
|
24
|
+
@public_chats << map
|
25
|
+
elsif type == :privmsg
|
26
|
+
@chats[map[:from]] << map
|
27
|
+
elsif type == :hub_disconnected
|
28
|
+
@chats.clear
|
29
|
+
@public_chats.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Supports
|
3
|
+
module Downloads
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class Download < Struct.new(:nick, :file, :tth, :size, :offset)
|
7
|
+
attr_accessor :percent, :status
|
8
|
+
|
9
|
+
def file_list?
|
10
|
+
file == 'files.xml.bz2'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :current_downloads, :finished_downloads, :queued_downloads,
|
15
|
+
:failed_downloads, :open_download_slots, :trying, :timed_out,
|
16
|
+
:download_slots
|
17
|
+
|
18
|
+
included do
|
19
|
+
set_callback :setup, :after, :initialize_queues
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_failed_downloads
|
23
|
+
failed_downloads.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear_finished_downloads
|
27
|
+
finished_downloads.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
def download nick, file, tth=nil, size=-1, offset=0
|
31
|
+
raise ConnectionException.new 'Not connected yet!' unless hub
|
32
|
+
raise 'File cannot be nil!' if file.nil?
|
33
|
+
|
34
|
+
unless nicks.include? nick
|
35
|
+
raise ConnectionException.new "User #{nick} does not exist!"
|
36
|
+
end
|
37
|
+
|
38
|
+
download = Download.new nick, file, tth, size, offset
|
39
|
+
download.percent = 0
|
40
|
+
download.status = 'idle'
|
41
|
+
|
42
|
+
# Append it to the queue of things to download. This will be processed
|
43
|
+
# elsewhere
|
44
|
+
@to_download << download
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def retry_download nick, file
|
49
|
+
dl = (@failed_downloads[nick] ||= []).detect{ |h| h.file == file }
|
50
|
+
|
51
|
+
if dl.nil?
|
52
|
+
Fargo.logger.warn "#{file} isn't a failed download for: #{nick}!"
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
@failed_downloads[nick].delete dl
|
57
|
+
download dl.nick, dl.file, dl.tth, dl.size
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_download nick, file
|
61
|
+
# We need to synchronize this access, so append these arguments to a
|
62
|
+
# queue to be processed later
|
63
|
+
@to_remove << [nick, file]
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def lock_next_download! user, connection
|
68
|
+
@downloading_lock.synchronize {
|
69
|
+
return get_next_download_with_lock! user, connection
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# If a connection timed out, retry all queued downloads for that user
|
74
|
+
def try_again nick
|
75
|
+
return false unless @timed_out.include? nick
|
76
|
+
|
77
|
+
@timed_out.delete nick
|
78
|
+
downloads = @failed_downloads[nick].dup
|
79
|
+
@failed_downloads[nick].clear
|
80
|
+
downloads.each{ |d| download nick, d.file, d.tth, d.size }
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Finds the next queued up download and begins downloading it.
|
88
|
+
def start_download
|
89
|
+
return false if open_download_slots == 0 || @current_downloads.size + @trying.size > download_slots
|
90
|
+
|
91
|
+
arr = nil
|
92
|
+
|
93
|
+
@downloading_lock.synchronize {
|
94
|
+
# Find the first nick and download list
|
95
|
+
arr = @queued_downloads.to_a.detect{ |nick, downloads|
|
96
|
+
downloads.size > 0 &&
|
97
|
+
!@current_downloads.has_key?(nick) &&
|
98
|
+
!@trying.include?(nick) &&
|
99
|
+
!@timed_out.include?(nick) &&
|
100
|
+
has_slot?(nick)
|
101
|
+
}
|
102
|
+
|
103
|
+
return false if arr.nil? || arr.size == 0
|
104
|
+
dl_nick = arr[0]
|
105
|
+
connection = connection_for dl_nick
|
106
|
+
|
107
|
+
# If we already have an open connection to this user, tell that
|
108
|
+
# connection to download the file. Otherwise, request a connection
|
109
|
+
# which will handle downloading when the connection is complete.
|
110
|
+
if connection
|
111
|
+
Fargo.logger.debug "Requesting previous connection downloads: #{arr[1].first}"
|
112
|
+
download = get_next_download_with_lock! dl_nick, connection
|
113
|
+
connection.download = download
|
114
|
+
connection.begin_download!
|
115
|
+
else
|
116
|
+
Fargo.logger.debug "Requesting connection with: #{dl_nick} for downloading"
|
117
|
+
@trying << dl_nick
|
118
|
+
connect_with dl_nick
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
arr
|
123
|
+
end
|
124
|
+
|
125
|
+
# This method should only be called when synchronized by the mutex
|
126
|
+
def get_next_download_with_lock! user, connection
|
127
|
+
raise 'No open slots!' if @open_download_slots <= 0
|
128
|
+
raise "Already downloading from #{user}!" if @current_downloads[user]
|
129
|
+
|
130
|
+
if @queued_downloads[user].nil? || @queued_downloads[user].size == 0
|
131
|
+
return nil
|
132
|
+
end
|
133
|
+
|
134
|
+
download = @queued_downloads[user].shift
|
135
|
+
@current_downloads[user] = download
|
136
|
+
@trying.delete user
|
137
|
+
|
138
|
+
Fargo.logger.debug "#{self}: Locking download: #{download}"
|
139
|
+
|
140
|
+
block = Proc.new{ |type, map|
|
141
|
+
Fargo.logger.debug "#{connection}: received: #{type.inspect} - #{map.inspect}"
|
142
|
+
|
143
|
+
if type == :download_progress
|
144
|
+
download.percent = map[:percent]
|
145
|
+
elsif type == :download_started
|
146
|
+
download.status = 'downloading'
|
147
|
+
elsif type == :download_finished
|
148
|
+
connection.unsubscribe &block
|
149
|
+
download.percent = 1
|
150
|
+
download.status = 'finished'
|
151
|
+
download_finished! user, false
|
152
|
+
elsif type == :download_failed || type == :download_disconnected
|
153
|
+
connection.unsubscribe &block
|
154
|
+
download.status = 'failed'
|
155
|
+
download_finished! user, true
|
156
|
+
end
|
157
|
+
}
|
158
|
+
|
159
|
+
connection.subscribe &block
|
160
|
+
|
161
|
+
download
|
162
|
+
end
|
163
|
+
|
164
|
+
def download_finished! user, failed
|
165
|
+
download = nil
|
166
|
+
@downloading_lock.synchronize{
|
167
|
+
download = @current_downloads.delete user
|
168
|
+
@open_download_slots += 1
|
169
|
+
}
|
170
|
+
|
171
|
+
if failed
|
172
|
+
(@failed_downloads[user] ||= []) << download
|
173
|
+
else
|
174
|
+
(@finished_downloads[user] ||= []) << download
|
175
|
+
end
|
176
|
+
|
177
|
+
start_download # Start another download if possible
|
178
|
+
end
|
179
|
+
|
180
|
+
def connection_failed_with! nick
|
181
|
+
@trying.delete nick
|
182
|
+
@timed_out << nick
|
183
|
+
|
184
|
+
@downloading_lock.synchronize {
|
185
|
+
@queued_downloads[nick].each{ |d| d.status = 'timeout' }
|
186
|
+
@failed_downloads[nick] ||= []
|
187
|
+
@failed_downloads[nick] = @failed_downloads[nick] | @queued_downloads[nick]
|
188
|
+
@queued_downloads[nick].clear
|
189
|
+
}
|
190
|
+
|
191
|
+
start_download # This one failed, try the next one
|
192
|
+
end
|
193
|
+
|
194
|
+
def initialize_queues
|
195
|
+
@download_slots ||= 4
|
196
|
+
|
197
|
+
FileUtils.mkdir_p config.download_dir, :mode => 0755
|
198
|
+
|
199
|
+
@downloading_lock = Mutex.new
|
200
|
+
|
201
|
+
# Don't use Hash.new{} because this can't be dumped by Marshal
|
202
|
+
@queued_downloads = {}
|
203
|
+
@current_downloads = {}
|
204
|
+
@failed_downloads = {}
|
205
|
+
@finished_downloads = {}
|
206
|
+
@trying = []
|
207
|
+
@timed_out = []
|
208
|
+
|
209
|
+
@open_download_slots = download_slots
|
210
|
+
|
211
|
+
subscribe { |type, hash|
|
212
|
+
if type == :connection_timeout
|
213
|
+
connection_failed_with! hash[:nick] if @trying.include?(hash[:nick])
|
214
|
+
elsif type == :hub_disconnected
|
215
|
+
exit_download_queue_threads
|
216
|
+
elsif type == :hub_connection_opened
|
217
|
+
start_download_queue_threads
|
218
|
+
end
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
def exit_download_queue_threads
|
223
|
+
@download_starter_thread.exit
|
224
|
+
@download_removal_thread.exit
|
225
|
+
end
|
226
|
+
|
227
|
+
# Both of these need access to the synchronization lock, so we use
|
228
|
+
# separate threads to do these processes.
|
229
|
+
def start_download_queue_threads
|
230
|
+
@to_download = Queue.new
|
231
|
+
@download_starter_thread = Thread.start {
|
232
|
+
loop {
|
233
|
+
download = @to_download.pop
|
234
|
+
|
235
|
+
if @timed_out.include? download.nick
|
236
|
+
download.status = 'timeout'
|
237
|
+
(@failed_downloads[download.nick] ||= []) << download
|
238
|
+
else
|
239
|
+
(@queued_downloads[download.nick] ||= []) << download
|
240
|
+
start_download
|
241
|
+
end
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
@to_remove = Queue.new
|
246
|
+
@download_removal_thread = Thread.start {
|
247
|
+
loop {
|
248
|
+
user, file = @to_remove.pop
|
249
|
+
|
250
|
+
@downloading_lock.synchronize {
|
251
|
+
@queued_downloads[user] ||= []
|
252
|
+
download = @queued_downloads[user].detect{ |h| h.file == file }
|
253
|
+
@queued_downloads[user].delete download unless download.nil?
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
end # Downloads
|
260
|
+
end # Supports
|
261
|
+
end # Fargo
|