This repository has been archived by the owner on Apr 13, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 180
/
send.rb
115 lines (107 loc) · 3.54 KB
/
send.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
require "socket"
require "ipaddr"
require "timeout"
module Cinch
module DCC
module Outgoing
# DCC SEND is a protocol for transferring files, usually found
# in IRC. While the handshake, i.e. the details of the file
# transfer, are transferred over IRC, the actual file transfer
# happens directly between two clients. As such it doesn't put
# stress on the IRC server.
#
# Cinch allows sending files by either using
# {Cinch::User#dcc_send}, which takes care of all parameters as
# well as setting up resume support, or by creating instances of
# this class directly. The latter will only be useful to people
# working on the Cinch code itself.
#
# {Cinch::User#dcc_send} expects an object to send as well as
# optionaly a file name, which is sent to the receiver as a
# suggestion where to save the file. If no file name is
# provided, the method will use the object's `#path` method to
# determine it.
#
# Any object that implements {DCC::DCCableObject} can be sent,
# but sending files will probably be the most common case.
#
# If you're behind a NAT it is necessary to explicitly set the
# external IP using the {file:docs/bot_options.md#dccownip dcc.own_ip
# option}.
#
# @example Sending a file to a user
# match "send me something"
# def execute(m)
# m.user.dcc_send(open("/tmp/cookies"))
# end
class Send
# @param [Hash] opts
# @option opts [User] receiver
# @option opts [String] filename
# @option opts [File] io
# @option opts [String] own_ip
def initialize(opts = {})
@receiver, @filename, @io, @own_ip = opts.values_at(:receiver, :filename, :io, :own_ip)
end
# Start the server
#
# @return [void]
def start_server
@socket = TCPServer.new(0)
@socket.listen(1)
end
# Send the handshake to the user.
#
# @return [void]
def send_handshake
handshake = "\001DCC SEND %s %d %d %d\001" % [@filename, IPAddr.new(@own_ip).to_i, port, @io.size]
@receiver.send(handshake)
end
# Listen for an incoming connection.
#
# This starts listening for an incoming connection to the server
# started by {#start_server}. After a client successfully
# connected, the server socket will be closed and the file
# transferred to the client.
#
# @raise [Timeout::Error] Raised if the receiver did not connect
# within 30 seconds
# @return [void]
# @note This method blocks.
def listen
begin
fd = nil
Timeout.timeout(30) do
fd, _ = @socket.accept
send_data(fd)
fd.close
end
ensure
@socket.close
end
end
# Seek to `pos` in the data.
#
# @param [Integer] pos
# @return [void]
# @api private
def seek(pos)
@io.seek(pos)
end
# @return [Fixnum] The port used for the socket
def port
@port ||= @socket.addr[1]
end
private
def send_data(fd)
@io.advise(:sequential)
while chunk = @io.read(8096)
rs, ws = IO.select([fd], [fd])
rs.first.recv unless rs.empty?
ws.first.write(chunk) unless ws.empty?
end
end
end
end
end
end