summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/asyncio.nim43
-rw-r--r--lib/pure/ftpclient.nim18
-rwxr-xr-xlib/pure/sockets.nim22
3 files changed, 65 insertions, 18 deletions
diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim
index 0f5197792..133d8c24e 100644
--- a/lib/pure/asyncio.nim
+++ b/lib/pure/asyncio.nim
@@ -126,6 +126,7 @@ type
     handleTask*: proc (s: PAsyncSocket) {.closure.}
 
     lineBuffer: TaintedString ## Temporary storage for ``recvLine``
+    sendBuffer: string ## Temporary storage for ``send``
     sslNeedAccept: bool
     proto: TProtocol
     deleg: PDelegate
@@ -155,6 +156,7 @@ proc newAsyncSocket(): PAsyncSocket =
   result.handleTask = (proc (s: PAsyncSocket) = nil)
 
   result.lineBuffer = "".TaintedString
+  result.sendBuffer = ""
 
 proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, 
                   protocol: TProtocol = IPPROTO_TCP, 
@@ -225,10 +227,22 @@ proc asyncSockHandleWrite(h: PObject) =
     else:
       PAsyncSocket(h).deleg.mode = fmReadWrite
   else:
-    if PAsyncSocket(h).handleWrite != nil:
-      PAsyncSocket(h).handleWrite(PAsyncSocket(h))
+    if PAsyncSocket(h).sendBuffer != "":
+      let sock = PAsyncSocket(h)
+      let bytesSent = sock.socket.sendAsync(sock.sendBuffer)
+      assert bytesSent > 0
+      if bytesSent != sock.sendBuffer.len:
+        sock.sendBuffer = sock.sendBuffer[bytesSent .. -1]
+      elif bytesSent == sock.sendBuffer.len:
+        sock.sendBuffer = ""
+      
+      if PAsyncSocket(h).handleWrite != nil:
+        PAsyncSocket(h).handleWrite(PAsyncSocket(h))
     else:
-      PAsyncSocket(h).deleg.mode = fmRead
+      if PAsyncSocket(h).handleWrite != nil:
+        PAsyncSocket(h).handleWrite(PAsyncSocket(h))
+      else:
+        PAsyncSocket(h).deleg.mode = fmRead
 
 when defined(ssl):
   proc asyncSockDoHandshake(h: PObject) =
@@ -340,7 +354,8 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
   # deleg.open is set in ``toDelegate``.
   
   client.socket = c
-  client.lineBuffer = ""
+  client.lineBuffer = "".TaintedString
+  client.sendBuffer = ""
   client.info = SockConnected
 
 proc accept*(server: PAsyncSocket, client: var PAsyncSocket) =
@@ -445,6 +460,26 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
   of RecvFail:
     result = false
 
+proc send*(sock: PAsyncSocket, data: string) =
+  ## Sends ``data`` to socket ``sock``. This is basically a nicer implementation
+  ## of ``sockets.sendAsync``.
+  ##
+  ## If ``data`` cannot be sent immediately it will be buffered and sent
+  ## when ``sock`` becomes writeable (during the ``handleWrite`` event).
+  ## It's possible that only a part of ``data`` will be sent immediately, while
+  ## the rest of it will be buffered and sent later.
+  if sock.sendBuffer.len != 0:
+    sock.sendBuffer.add(data)
+    return
+  let bytesSent = sock.socket.sendAsync(data)
+  assert bytesSent >= 0
+  if bytesSent == 0:
+    sock.sendBuffer.add(data)
+    sock.deleg.mode = fmReadWrite
+  elif bytesSent != data.len:
+    sock.sendBuffer.add(data[bytesSent .. -1])
+    sock.deleg.mode = fmReadWrite
+
 proc timeValFromMilliseconds(timeout = 500): TTimeVal =
   if timeout != -1:
     var seconds = timeout div 1000
diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim
index 78c96c3f1..ad3d7a3bb 100644
--- a/lib/pure/ftpclient.nim
+++ b/lib/pure/ftpclient.nim
@@ -454,11 +454,13 @@ proc doUpload(ftp: PFTPClient, async = false): bool =
   if ftp.dsockConnected:
     if ftp.job.toStore.len() > 0:
       assert(async)
-      if ftp.asyncDSock.sendAsync(ftp.job.toStore):
+      let bytesSent = ftp.asyncDSock.sendAsync(ftp.job.toStore)
+      if bytesSent == ftp.job.toStore.len:
         ftp.job.toStore = ""
-        ftp.job.progress.inc(ftp.job.toStore.len)
-        ftp.job.oneSecond.inc(ftp.job.toStore.len)
-      
+      elif bytesSent != ftp.job.toStore.len and bytesSent != 0:
+        ftp.job.toStore = ftp.job.toStore[bytesSent .. -1]
+      ftp.job.progress.inc(bytesSent)
+      ftp.job.oneSecond.inc(bytesSent)
     else:
       var s = newStringOfCap(4000)
       var len = ftp.job.file.readBuffer(addr(s[0]), 4000)
@@ -476,8 +478,12 @@ proc doUpload(ftp: PFTPClient, async = false): bool =
       if not async:
         getDSock(ftp).send(s)
       else:
-        if not ftp.asyncDSock.sendAsync(s):
-          ftp.job.toStore = s
+        let bytesSent = ftp.asyncDSock.sendAsync(s)
+        if bytesSent == 0:
+          ftp.job.toStore.add(s)
+        elif bytesSent != s.len:
+          ftp.job.toStore.add(s[bytesSent .. -1])
+        len = bytesSent
       
       ftp.job.progress.inc(len)
       ftp.job.oneSecond.inc(len)
diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim
index 01b97197e..e0eae196f 100755
--- a/lib/pure/sockets.nim
+++ b/lib/pure/sockets.nim
@@ -1317,13 +1317,18 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} =
     
     OSError()
 
-proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
-  ## sends data to a non-blocking socket. Returns whether ``data`` was sent.
-  result = true
-  var bytesSent = send(socket, cstring(data), data.len)
+proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} =
+  ## sends data to a non-blocking socket.
+  ## Returns ``0`` if no data could be sent, if data has been sent
+  ## returns the amount of bytes of ``data`` that was successfully sent. This
+  ## number may not always be the length of ``data`` but typically is.
+  ##
+  ## An EOS (or ESSL if socket is an SSL socket) exception is raised if an error
+  ## occurs.
+  result = send(socket, cstring(data), data.len)
   when defined(ssl):
     if socket.isSSL:
-      if bytesSent <= 0:
+      if result <= 0:
           let ret = SSLGetError(socket.sslHandle, bytesSent.cint)
           case ret
           of SSL_ERROR_ZERO_RETURN:
@@ -1339,17 +1344,18 @@ proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
           else: SSLError("Unknown Error")
       else:
         return
-  if bytesSent == -1:
+  if result == -1:
     when defined(windows):
       var err = WSAGetLastError()
       # TODO: Test on windows.
       if err == WSAEINPROGRESS:
-        return false
+        return 0
       else: OSError()
     else:
       if errno == EAGAIN or errno == EWOULDBLOCK:
-        return false
+        return 0
       else: OSError()
+  
 
 proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
   ## safe alternative to ``send``. Does not raise an EOS when an error occurs,