summary refs log tree commit diff stats
path: root/lib/pure/ftpclient.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/ftpclient.nim')
-rw-r--r--lib/pure/ftpclient.nim102
1 files changed, 45 insertions, 57 deletions
diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim
index 410c5e8bc..b61793866 100644
--- a/lib/pure/ftpclient.nim
+++ b/lib/pure/ftpclient.nim
@@ -26,6 +26,10 @@ import sockets, strutils, parseutils, times, os, asyncio
 ##    var ftp = FTPClient("example.org", user = "user", pass = "pass")
 ##    ftp.connect()
 ##    ftp.retrFile("file.ext", "file.ext")
+##
+## **Warning:** The API of this module is unstable, and therefore is subject
+## to change.
+
 
 type
   TFTPClient* = object of TObject
@@ -92,8 +96,9 @@ type
   EFTP* = object of ESynch
 
 proc FTPClient*(address: string, port = TPort(21),
-                user, pass = ""): TFTPClient =
-  ## Create a ``TFTPClient`` object.
+                user, pass = ""): PFTPClient =
+  ## Create a ``PFTPClient`` object.
+  new(result)
   result.user = user
   result.pass = pass
   result.address = address
@@ -109,7 +114,7 @@ proc getDSock(ftp: PFTPClient): TSocket =
 proc getCSock(ftp: PFTPClient): TSocket =
   if ftp.isAsync: return ftp.asyncCSock else: return ftp.csock
 
-template blockingOperation(sock: TSocket, body: stmt) =
+template blockingOperation(sock: TSocket, body: stmt) {.immediate.} =
   if ftp.isAsync:
     sock.setBlocking(true)
   body
@@ -274,11 +279,20 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool =
   ## It doesn't if `async` is true, because it doesn't check for 226 then.
   if ftp.dsockConnected:
     var r = TaintedString""
-    if getDSock(ftp).recvAsync(r):
-      if r.string != "":
-        ftp.job.lines.add(r.string)
-      else:
-        ftp.dsockConnected = False
+    if ftp.isAsync:
+      if ftp.asyncDSock.recvLine(r):
+        if r.string == "":
+          ftp.dsockConnected = false
+        else:
+          ftp.job.lines.add(r.string & "\n")
+    else:
+      assert(not async)
+      if ftp.dsock.recvLine(r):
+        if r.string == "":
+          ftp.dsockConnected = false
+        else:
+          ftp.job.lines.add(r.string & "\n")
+      else: OSError()
   
   if not async:
     var readSocks: seq[TSocket] = @[ftp.getCSock()]
@@ -385,7 +399,7 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string =
 proc retrText*(ftp: PFTPClient, file: string, async = false): string =
   ## Retrieves ``file``. File must be ASCII text.
   ## If ``async`` is true, this function will return immediately and
-  ## it will be your job to call ``poll`` to progress this operation.
+  ## it will be your job to call asyncio's ``poll`` to progress this operation.
   ftp.createJob(getLines, JRetrText)
   ftp.pasv()
   assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"]
@@ -400,12 +414,14 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string =
 proc getFile(ftp: PFTPClient, async = false): bool =
   if ftp.dsockConnected:
     var r = "".TaintedString
+    var bytesRead = 0
     var returned = false
     if async:
       if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.")
-      returned = ftp.AsyncDSock.recvAsync(r)
+      bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize)
+      returned = bytesRead != -1
     else: 
-      r = getDSock(ftp).recv()
+      bytesRead = getDSock(ftp).recv(r, BufferSize)
       returned = true
     let r2 = r.string
     if r2 != "":
@@ -425,8 +441,9 @@ proc getFile(ftp: PFTPClient, async = false): bool =
 proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) =
   ## Downloads ``file`` and saves it to ``dest``. Usage of this function
   ## asynchronously is recommended to view the progress of the download.
-  ## The ``EvRetr`` event is given by ``poll`` when the download is finished,
-  ## and the ``filename`` field will be equal to ``file``.
+  ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function 
+  ## when the download is finished, and the ``filename`` field will be equal
+  ## to ``file``.
   ftp.createJob(getFile, JRetr)
   ftp.job.file = open(dest, mode = fmWrite)
   ftp.pasv()
@@ -450,11 +467,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)
@@ -472,8 +491,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)
@@ -482,8 +505,9 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) =
   ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
   ## function asynchronously is recommended to view the progress of
   ## the download.
-  ## The ``EvStore`` event is given by ``poll`` when the upload is finished,
-  ## and the ``filename`` field will be equal to ``file``.
+  ## The ``EvStore`` event is passed to the specified ``handleEvent`` function 
+  ## when the upload is finished, and the ``filename`` field will be 
+  ## equal to ``file``.
   ftp.createJob(doUpload, JStore)
   ftp.job.file = open(file)
   ftp.job.total = ftp.job.file.getFileSize()
@@ -508,16 +532,6 @@ proc close*(ftp: PFTPClient) =
     ftp.csock.close()
     ftp.dsock.close()
 
-discard """proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
-  result = (SockIdle, InvalidSocket)
-  var ftp = PAsyncFTPClient(h)
-  if ftp.jobInProgress:
-    case ftp.job.typ
-    of JRetrText, JRetr, JStore:
-      if ftp.dsockStatus == SockConnecting or ftp.dsockStatus == SockConnected:
-        result = (ftp.dsockStatus, ftp.dsock)
-      else: result = (SockIdle, ftp.dsock)"""
-
 proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
   if ftp.jobInProgress:
     assertReply ftp.expectReply(), "226" # Make sure the transfer completed.
@@ -540,32 +554,6 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
     
     ftp.handleEvent(ftp, r)
 
-discard """proc handleConnect(h: PObject) =
-  var ftp = PAsyncFTPClient(h)
-  ftp.dsockStatus = SockConnected
-  assert(ftp.jobInProgress)
-  if ftp.job.typ == JStore:
-    ftp.dele.mode = MWriteable
-  else: 
-    ftp.dele.mode = MReadable"""
-
-discard """proc handleRead(h: PObject) =
-  var ftp = PAsyncFTPClient(h)
-  assert(ftp.jobInProgress)
-  assert(ftp.job.typ != JStore)
-  # This can never return true, because it shouldn't check for code 
-  # 226 from csock.
-  assert(not ftp.job.prc(ftp[], true))
-"""
-
-discard """proc csockGetSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
-  # This only returns the csock if a job is in progress. Otherwise handle read
-  # would capture data which is not for it to capture.
-  result = (SockIdle, InvalidSocket)
-  var ftp = PAsyncFTPClient(h)
-  if ftp.jobInProgress:
-    result = (SockConnected, ftp.csock)"""
-
 proc AsyncFTPClient*(address: string, port = TPort(21),
                      user, pass = "",
     handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} =