]> git.lizzy.rs Git - luairc.git/blobdiff - src/irc/dcc.lua
add debug output to dcc transfers
[luairc.git] / src / irc / dcc.lua
index f227d4b15736c7918a460d7ce6f27a53e738dc86..be8892b02ac85f80a671674104058746baeef64a 100644 (file)
@@ -1,6 +1,10 @@
+---
+-- Implementation of the DCC protocol
 -- initialization {{{
 local base =      _G
 local irc =       require 'irc'
+local ctcp =      require 'irc.ctcp'
+local c =         ctcp._ctcp_quote
 local irc_debug = require 'irc.debug'
 local misc =      require 'irc.misc'
 local socket =    require 'socket'
@@ -9,6 +13,9 @@ local io =        require 'io'
 local string =    require 'string'
 -- }}}
 
+---
+-- This module implements the DCC protocol. File transfers (DCC SEND) are
+-- handled, but DCC CHAT is not, as of yet.
 module 'irc.dcc'
 
 -- defaults {{{
@@ -17,8 +24,23 @@ LAST_PORT = 5000
 -- }}}
 
 -- private functions {{{
+-- debug_dcc {{{
+--
+-- Prints a debug message about DCC events similar to irc.debug.warn, etc.
+-- @param msg Debug message
+local function debug_dcc(msg)
+    irc_debug._message("DCC", msg, "\027[0;32m")
+end
+-- }}}
+
 -- send_file {{{
-local function send_file(sock, file, size, packet_size)
+--
+-- Sends a file to a remote user, after that user has accepted our DCC SEND
+-- invitation
+-- @param sock        Socket to send the file on
+-- @param file        Lua file object corresponding to the file we want to send
+-- @param packet_size Size of the packets to send the file in
+local function send_file(sock, file, packet_size)
     local bytes = 0
     while true do
         local packet = file:read(packet_size)
@@ -26,17 +48,30 @@ local function send_file(sock, file, size, packet_size)
         bytes = bytes + packet:len()
         local index = 1
         while true do
+            local skip = false
             sock:send(packet, index)
-            local new_bytes = misc.int_to_str(sock:receive(4))
-            if new_bytes ~= bytes then
-                index = packet_size - bytes + new_bytes + 1
+            local new_bytes, err = sock:receive(4)
+            if not new_bytes then
+                if err == "timeout" then
+                    skip = true
+                else
+                    irc_debug._warn(err)
+                    break
+                end
             else
-                break
+                new_bytes = misc._int_to_str(new_bytes)
+            end
+            if not skip then
+                if new_bytes ~= bytes then
+                    index = packet_size - bytes + new_bytes + 1
+                else
+                    break
+                end
             end
         end
-        if bytes >= size then break end
         coroutine.yield(true)
     end
+    debug_dcc("File completely sent")
     file:close()
     sock:close()
     irc._unregister_socket(sock, 'w')
@@ -45,22 +80,35 @@ end
 -- }}}
 
 -- handle_connect {{{
-local function handle_connect(ssock, file, size, packet_size)
+--
+-- Handle the connection attempt by a remote user to get our file. Basically
+-- just swaps out the server socket we were listening on for a client socket
+-- that we can send data on
+-- @param ssock Server socket that the remote user connected to
+-- @param file  Lua file object corresponding to the file we want to send
+-- @param packet_size Size of the packets to send the file in
+local function handle_connect(ssock, file, packet_size)
+    debug_dcc("Offer accepted, beginning to send")
     packet_size = packet_size or 1024
     local sock = ssock:accept()
     sock:settimeout(0.1)
     ssock:close()
     irc._unregister_socket(ssock, 'r')
     irc._register_socket(sock, 'w',
-                         coroutine.wrap(function(sock)
-                             return send_file(sock, file, size, packet_size)
+                         coroutine.wrap(function(s)
+                             return send_file(s, file, packet_size)
                          end))
     return true
 end
 -- }}}
 
 -- accept_file {{{
-local function accept_file(sock, file, size, packet_size)
+--
+-- Accepts a file from a remote user which has offered it to us.
+-- @param sock        Socket to receive the file on
+-- @param file        Lua file object corresponding to the file we want to save
+-- @param packet_size Size of the packets to receive the file in
+local function accept_file(sock, file, packet_size)
     local bytes = 0
     while true do
         local packet, err, partial_packet = sock:receive(packet_size)
@@ -68,10 +116,11 @@ local function accept_file(sock, file, size, packet_size)
         if not packet then break end
         if packet:len() == 0 then break end
         bytes = bytes + packet:len()
-        sock:send(misc.str_to_int(bytes))
+        sock:send(misc._str_to_int(bytes))
         file:write(packet)
         coroutine.yield(true)
     end
+    debug_dcc("File completely received")
     file:close()
     sock:close()
     irc._unregister_socket(sock, 'r')
@@ -80,43 +129,63 @@ end
 -- }}}
 -- }}}
 
+-- internal functions {{{
+-- _accept {{{
+--
+-- Accepts a file offer from a remote user. Called when the on_dcc callback
+-- retuns true.
+-- @param filename    Name to save the file as
+-- @param address     IP address of the remote user in low level int form
+-- @param port        Port to connect to at the remote user
+-- @param packet_size Size of the packets the remote user will be sending
+function _accept(filename, address, port, packet_size)
+    debug_dcc("Accepting a DCC SEND request from " ..
+              misc._ip_int_to_str(address) .. ":" .. port)
+    packet_size = packet_size or 1024
+    local sock = base.assert(socket.tcp())
+    base.assert(sock:connect(misc._ip_int_to_str(address), port))
+    sock:settimeout(0.1)
+    local file = base.assert(io.open(misc._get_unique_filename(filename), "w"))
+    irc._register_socket(sock, 'r',
+                         coroutine.wrap(function(s)
+                             return accept_file(s, file, packet_size)
+                         end))
+end
+-- }}}
+-- }}}
+
 -- public functions {{{
 -- send {{{
+---
+-- Offers a file to a remote user.
+-- @param nick     User to offer the file to
+-- @param filename Filename to offer
+-- @param port     Port to accept connections on (optional, defaults to
+--                 choosing an available port between FIRST_PORT and LAST_PORT
+--                 above)
 function send(nick, filename, port)
     port = port or FIRST_PORT
-    local sock = base.assert(socket.tcp())
+    local sock
     repeat
+        sock = base.assert(socket.tcp())
         err, msg = sock:bind('*', port)
         port = port + 1
     until msg ~= "address already in use" and port <= LAST_PORT + 1
     base.assert(err, msg)
     base.assert(sock:listen(1))
-    local ip = misc.ip_str_to_int(irc.get_ip())
+    local ip = misc._ip_str_to_int(irc.get_ip())
     local file = base.assert(io.open(filename))
     local size = file:seek("end")
     file:seek("set")
     irc._register_socket(sock, 'r',
-                         coroutine.wrap(function(sock)
-                             return handle_connect(sock, file, size)
+                         coroutine.wrap(function(s)
+                             return handle_connect(s, file)
                          end))
-    filename = misc.basename(filename)
+    filename = misc._basename(filename)
     if filename:find(" ") then filename = '"' .. filename .. '"' end
-    irc.send("PRIVMSG", nick, {"DCC SEND " .. filename .. " " ..
-             ip .. " " .. port - 1 .. " " .. size})
-end
--- }}}
-
--- accept {{{
-function accept(filename, address, port, size, packet_size)
-    packet_size = packet_size or 1024
-    local sock = base.assert(socket.tcp())
-    base.assert(sock:connect(misc.ip_int_to_str(address), port))
-    sock:settimeout(0.1)
-    local file = base.assert(io.open(misc.get_unique_filename(filename), "w"))
-    irc._register_socket(sock, 'r',
-                         coroutine.wrap(function(sock)
-                             return accept_file(sock, file, size, packet_size)
-                         end))
+    debug_dcc("Offering " .. filename .. " to " .. nick .. " from " ..
+              irc.get_ip() .. ":" .. port - 1)
+    irc.send("PRIVMSG", nick, c("DCC", "SEND", filename, ip, port - 1, size))
 end
 -- }}}
 -- }}}