]> git.lizzy.rs Git - luairc.git/blob - src/irc/dcc.lua
document the dcc module
[luairc.git] / src / irc / dcc.lua
1 ---
2 -- Implementation of the DCC protocol
3 -- initialization {{{
4 local base =      _G
5 local irc =       require 'irc'
6 local irc_debug = require 'irc.debug'
7 local misc =      require 'irc.misc'
8 local socket =    require 'socket'
9 local coroutine = require 'coroutine'
10 local io =        require 'io'
11 local string =    require 'string'
12 -- }}}
13
14 ---
15 -- This module implements the DCC protocol. File transfers (DCC SEND) are
16 -- handled, but DCC CHAT is not, as of yet.
17 module 'irc.dcc'
18
19 -- defaults {{{
20 FIRST_PORT = 1028
21 LAST_PORT = 5000
22 -- }}}
23
24 -- private functions {{{
25 -- send_file {{{
26 --
27 -- Sends a file to a remote user, after that user has accepted our DCC SEND
28 -- invitation
29 -- @param sock        Socket to send the file on
30 -- @param file        Lua file object corresponding to the file we want to send
31 -- @param size        Size of the file to send
32 -- @param packet_size Size of the packets to send the file in
33 local function send_file(sock, file, size, packet_size)
34     local bytes = 0
35     while true do
36         local packet = file:read(packet_size)
37         if not packet then break end
38         bytes = bytes + packet:len()
39         local index = 1
40         while true do
41             sock:send(packet, index)
42             local new_bytes = misc.int_to_str(sock:receive(4))
43             if new_bytes ~= bytes then
44                 index = packet_size - bytes + new_bytes + 1
45             else
46                 break
47             end
48         end
49         if bytes >= size then break end
50         coroutine.yield(true)
51     end
52     file:close()
53     sock:close()
54     irc._unregister_socket(sock, 'w')
55     return true
56 end
57 -- }}}
58
59 -- handle_connect {{{
60 --
61 -- Handle the connection attempt by a remote user to get our file. Basically
62 -- just swaps out the server socket we were listening on for a client socket
63 -- that we can send data on
64 -- @param ssock Server socket that the remote user connected to
65 -- @param file  Lua file object corresponding to the file we want to send
66 -- @param size  Size of the file to send
67 -- @param packet_size Size of the packets to send the file in
68 local function handle_connect(ssock, file, size, packet_size)
69     packet_size = packet_size or 1024
70     local sock = ssock:accept()
71     sock:settimeout(0.1)
72     ssock:close()
73     irc._unregister_socket(ssock, 'r')
74     irc._register_socket(sock, 'w',
75                          coroutine.wrap(function(sock)
76                              return send_file(sock, file, size, packet_size)
77                          end))
78     return true
79 end
80 -- }}}
81
82 -- accept_file {{{
83 --
84 -- Accepts a file from a remote user which has offered it to us.
85 -- @param sock        Socket to receive the file on
86 -- @param file        Lua file object corresponding to the file we want to save
87 -- @param size        Size of the file we are receiving
88 -- @param packet_size Size of the packets to receive the file in
89 local function accept_file(sock, file, size, packet_size)
90     local bytes = 0
91     while true do
92         local packet, err, partial_packet = sock:receive(packet_size)
93         if not packet and err == "timeout" then packet = partial_packet end
94         if not packet then break end
95         if packet:len() == 0 then break end
96         bytes = bytes + packet:len()
97         sock:send(misc.str_to_int(bytes))
98         file:write(packet)
99         coroutine.yield(true)
100     end
101     file:close()
102     sock:close()
103     irc._unregister_socket(sock, 'r')
104     return true
105 end
106 -- }}}
107 -- }}}
108
109 -- public functions {{{
110 -- send {{{
111 ---
112 -- Offers a file to a remote user.
113 -- @param nick     User to offer the file to
114 -- @param filename Filename to offer
115 -- @param port     Port to accept connections on (optional, defaults to
116 --                 choosing an available port between FIRST_PORT and LAST_PORT
117 --                 above)
118 function send(nick, filename, port)
119     port = port or FIRST_PORT
120     local sock = base.assert(socket.tcp())
121     repeat
122         err, msg = sock:bind('*', port)
123         port = port + 1
124     until msg ~= "address already in use" and port <= LAST_PORT + 1
125     base.assert(err, msg)
126     base.assert(sock:listen(1))
127     local ip = misc.ip_str_to_int(irc.get_ip())
128     local file = base.assert(io.open(filename))
129     local size = file:seek("end")
130     file:seek("set")
131     irc._register_socket(sock, 'r',
132                          coroutine.wrap(function(sock)
133                              return handle_connect(sock, file, size)
134                          end))
135     filename = misc.basename(filename)
136     if filename:find(" ") then filename = '"' .. filename .. '"' end
137     irc.send("PRIVMSG", nick, {"DCC SEND " .. filename .. " " ..
138              ip .. " " .. port - 1 .. " " .. size})
139 end
140 -- }}}
141
142 -- accept {{{
143 --
144 -- Accepts a file offer from a remote user. Called when the on_dcc callback
145 -- retuns true.
146 -- @param filename    Name to save the file as
147 -- @param address     IP address of the remote user
148 -- @param port        Port to connect to at the remote user
149 -- @param size        Size of the file that the remote user is offering
150 -- @param packet_size Size of the packets the remote user will be sending
151 function accept(filename, address, port, size, packet_size)
152     packet_size = packet_size or 1024
153     local sock = base.assert(socket.tcp())
154     base.assert(sock:connect(misc.ip_int_to_str(address), port))
155     sock:settimeout(0.1)
156     local file = base.assert(io.open(misc.get_unique_filename(filename), "w"))
157     irc._register_socket(sock, 'r',
158                          coroutine.wrap(function(sock)
159                              return accept_file(sock, file, size, packet_size)
160                          end))
161 end
162 -- }}}
163 -- }}}