summary refs log tree commit diff stats
path: root/lib/pure/asyncfile.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/asyncfile.nim')
-rw-r--r--lib/pure/asyncfile.nim177
1 files changed, 92 insertions, 85 deletions
diff --git a/lib/pure/asyncfile.nim b/lib/pure/asyncfile.nim
index 37339d3d1..0f6504342 100644
--- a/lib/pure/asyncfile.nim
+++ b/lib/pure/asyncfile.nim
@@ -9,31 +9,37 @@
 
 ## This module implements asynchronous file reading and writing.
 ##
-## .. code-block:: Nim
-##    import asyncfile, asyncdispatch, os
+##   ```Nim
+##   import std/[asyncfile, asyncdispatch, os]
 ##
-##    proc main() {.async.} =
-##      var file = openAsync(getTempDir() / "foobar.txt", fmReadWrite)
-##      await file.write("test")
-##      file.setFilePos(0)
-##      let data = await file.readAll()
-##      doAssert data == "test"
-##      file.close()
+##   proc main() {.async.} =
+##     var file = openAsync(getTempDir() / "foobar.txt", fmReadWrite)
+##     await file.write("test")
+##     file.setFilePos(0)
+##     let data = await file.readAll()
+##     doAssert data == "test"
+##     file.close()
 ##
-##    waitFor main()
+##   waitFor main()
+##   ```
 
-import asyncdispatch, os
+import std/[asyncdispatch, os]
+
+when defined(nimPreviewSlimSystem):
+  import std/[assertions, syncio]
+  when defined(windows) or defined(nimdoc):
+    import std/widestrs
 
 # TODO: Fix duplication introduced by PR #4683.
 
 when defined(windows) or defined(nimdoc):
-  import winlean
+  import std/winlean
 else:
-  import posix
+  import std/posix
 
 type
   AsyncFile* = ref object
-    fd: AsyncFd
+    fd: AsyncFD
     offset: int64
 
 when defined(windows) or defined(nimdoc):
@@ -72,7 +78,7 @@ else:
 proc getFileSize*(f: AsyncFile): int64 =
   ## Retrieves the specified file's size.
   when defined(windows) or defined(nimdoc):
-    var high: DWord
+    var high: DWORD
     let low = getFileSize(f.fd.Handle, addr high)
     if low == INVALID_FILE_SIZE:
       raiseOSError(osLastError())
@@ -83,32 +89,27 @@ proc getFileSize*(f: AsyncFile): int64 =
     f.offset = lseek(f.fd.cint, curPos, SEEK_SET)
     assert(f.offset == curPos)
 
-proc newAsyncFile*(fd: AsyncFd): AsyncFile =
+proc newAsyncFile*(fd: AsyncFD): AsyncFile =
   ## Creates `AsyncFile` with a previously opened file descriptor `fd`.
   new result
   result.fd = fd
   register(fd)
 
 proc openAsync*(filename: string, mode = fmRead): AsyncFile =
-  ## Opens a file specified by the path in ``filename`` using
-  ## the specified FileMode ``mode`` asynchronously.
+  ## Opens a file specified by the path in `filename` using
+  ## the specified FileMode `mode` asynchronously.
   when defined(windows) or defined(nimdoc):
     let flags = FILE_FLAG_OVERLAPPED or FILE_ATTRIBUTE_NORMAL
     let desiredAccess = getDesiredAccess(mode)
     let creationDisposition = getCreationDisposition(mode, filename)
-    when useWinUnicode:
-      let fd = createFileW(newWideCString(filename), desiredAccess,
-          FILE_SHARE_READ,
-          nil, creationDisposition, flags, 0)
-    else:
-      let fd = createFileA(filename, desiredAccess,
-          FILE_SHARE_READ,
-          nil, creationDisposition, flags, 0)
+    let fd = createFileW(newWideCString(filename), desiredAccess,
+        FILE_SHARE_READ,
+        nil, creationDisposition, flags, 0)
 
     if fd == INVALID_HANDLE_VALUE:
       raiseOSError(osLastError())
 
-    result = newAsyncFile(fd.AsyncFd)
+    result = newAsyncFile(fd.AsyncFD)
 
     if mode == fmAppend:
       result.offset = getFileSize(result)
@@ -121,21 +122,20 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
     if fd == -1:
       raiseOSError(osLastError())
 
-    result = newAsyncFile(fd.AsyncFd)
+    result = newAsyncFile(fd.AsyncFD)
 
 proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
-  ## Read ``size`` bytes from the specified file asynchronously starting at
+  ## Read `size` bytes from the specified file asynchronously starting at
   ## the current position of the file pointer.
   ##
   ## If the file pointer is past the end of the file then zero is returned
-  ## and no bytes are read into ``buf``
+  ## and no bytes are read into `buf`
   var retFuture = newFuture[int]("asyncfile.readBuffer")
 
   when defined(windows) or defined(nimdoc):
-    var ol = PCustomOverlapped()
-    GC_ref(ol)
+    var ol = newCustom()
     ol.data = CompletionData(fd: f.fd, cb:
-      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+      proc (fd: AsyncFD, bytesCount: DWORD, errcode: OSErrorCode) =
         if not retFuture.finished:
           if errcode == OSErrorCode(-1):
             assert bytesCount > 0
@@ -146,10 +146,10 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
             if errcode.int32 == ERROR_HANDLE_EOF:
               retFuture.complete(0)
             else:
-              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+              retFuture.fail(newOSError(errcode))
     )
-    ol.offset = DWord(f.offset and 0xffffffff)
-    ol.offsetHigh = DWord(f.offset shr 32)
+    ol.offset = DWORD(f.offset and 0xffffffff)
+    ol.offsetHigh = DWORD(f.offset shr 32)
 
     # According to MSDN we're supposed to pass nil to lpNumberOfBytesRead.
     let ret = readFile(f.fd.Handle, buf, size.int32, nil,
@@ -162,18 +162,18 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
           # This happens in Windows Server 2003
           retFuture.complete(0)
         else:
-          retFuture.fail(newException(OSError, osErrorMsg(err)))
+          retFuture.fail(newOSError(err))
     else:
       # Request completed immediately.
-      var bytesRead: DWord
+      var bytesRead: DWORD
       let overlappedRes = getOverlappedResult(f.fd.Handle,
-          cast[POverlapped](ol), bytesRead, false.WinBool)
+          cast[POVERLAPPED](ol), bytesRead, false.WINBOOL)
       if not overlappedRes.bool:
         let err = osLastError()
         if err.int32 == ERROR_HANDLE_EOF:
           retFuture.complete(0)
         else:
-          retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+          retFuture.fail(newOSError(osLastError()))
       else:
         assert bytesRead > 0
         assert bytesRead <= size
@@ -186,7 +186,7 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
       if res < 0:
         let lastError = osLastError()
         if lastError.int32 != EAGAIN:
-          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+          retFuture.fail(newOSError(lastError))
         else:
           result = false # We still want this callback to be called.
       elif res == 0:
@@ -202,20 +202,20 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
   return retFuture
 
 proc read*(f: AsyncFile, size: int): Future[string] =
-  ## Read ``size`` bytes from the specified file asynchronously starting at
-  ## the current position of the file pointer.
+  ## Read `size` bytes from the specified file asynchronously starting at
+  ## the current position of the file pointer. `size` should be greater than zero.
   ##
   ## If the file pointer is past the end of the file then an empty string is
   ## returned.
+  assert size > 0
   var retFuture = newFuture[string]("asyncfile.read")
 
   when defined(windows) or defined(nimdoc):
     var buffer = alloc0(size)
 
-    var ol = PCustomOverlapped()
-    GC_ref(ol)
+    var ol = newCustom()
     ol.data = CompletionData(fd: f.fd, cb:
-      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+      proc (fd: AsyncFD, bytesCount: DWORD, errcode: OSErrorCode) =
         if not retFuture.finished:
           if errcode == OSErrorCode(-1):
             assert bytesCount > 0
@@ -228,13 +228,13 @@ proc read*(f: AsyncFile, size: int): Future[string] =
             if errcode.int32 == ERROR_HANDLE_EOF:
               retFuture.complete("")
             else:
-              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+              retFuture.fail(newOSError(errcode))
         if buffer != nil:
           dealloc buffer
           buffer = nil
     )
-    ol.offset = DWord(f.offset and 0xffffffff)
-    ol.offsetHigh = DWord(f.offset shr 32)
+    ol.offset = DWORD(f.offset and 0xffffffff)
+    ol.offsetHigh = DWORD(f.offset shr 32)
 
     # According to MSDN we're supposed to pass nil to lpNumberOfBytesRead.
     let ret = readFile(f.fd.Handle, buffer, size.int32, nil,
@@ -251,18 +251,18 @@ proc read*(f: AsyncFile, size: int): Future[string] =
           # This happens in Windows Server 2003
           retFuture.complete("")
         else:
-          retFuture.fail(newException(OSError, osErrorMsg(err)))
+          retFuture.fail(newOSError(err))
     else:
       # Request completed immediately.
-      var bytesRead: DWord
+      var bytesRead: DWORD
       let overlappedRes = getOverlappedResult(f.fd.Handle,
-          cast[POverlapped](ol), bytesRead, false.WinBool)
+          cast[POVERLAPPED](ol), bytesRead, false.WINBOOL)
       if not overlappedRes.bool:
         let err = osLastError()
         if err.int32 == ERROR_HANDLE_EOF:
           retFuture.complete("")
         else:
-          retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+          retFuture.fail(newOSError(osLastError()))
       else:
         assert bytesRead > 0
         assert bytesRead <= size
@@ -279,7 +279,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
       if res < 0:
         let lastError = osLastError()
         if lastError.int32 != EAGAIN:
-          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+          retFuture.fail(newOSError(lastError))
         else:
           result = false # We still want this callback to be called.
       elif res == 0:
@@ -301,6 +301,8 @@ proc readLine*(f: AsyncFile): Future[string] {.async.} =
   result = ""
   while true:
     var c = await read(f, 1)
+    if c.len == 0:
+      break
     if c[0] == '\c':
       c = await read(f, 1)
       break
@@ -334,29 +336,28 @@ proc readAll*(f: AsyncFile): Future[string] {.async.} =
     result.add data
 
 proc writeBuffer*(f: AsyncFile, buf: pointer, size: int): Future[void] =
-  ## Writes ``size`` bytes from ``buf`` to the file specified asynchronously.
+  ## Writes `size` bytes from `buf` to the file specified asynchronously.
   ##
   ## The returned Future will complete once all data has been written to the
   ## specified file.
   var retFuture = newFuture[void]("asyncfile.writeBuffer")
   when defined(windows) or defined(nimdoc):
-    var ol = PCustomOverlapped()
-    GC_ref(ol)
+    var ol = newCustom()
     ol.data = CompletionData(fd: f.fd, cb:
-      proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
+      proc (fd: AsyncFD, bytesCount: DWORD, errcode: OSErrorCode) =
         if not retFuture.finished:
           if errcode == OSErrorCode(-1):
             assert bytesCount == size.int32
             retFuture.complete()
           else:
-            retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+            retFuture.fail(newOSError(errcode))
     )
     # passing -1 here should work according to MSDN, but doesn't. For more
     # information see
     # http://stackoverflow.com/questions/33650899/does-asynchronous-file-
     #   appending-in-windows-preserve-order
-    ol.offset = DWord(f.offset and 0xffffffff)
-    ol.offsetHigh = DWord(f.offset shr 32)
+    ol.offset = DWORD(f.offset and 0xffffffff)
+    ol.offsetHigh = DWORD(f.offset shr 32)
     f.offset.inc(size)
 
     # According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten.
@@ -366,14 +367,14 @@ proc writeBuffer*(f: AsyncFile, buf: pointer, size: int): Future[void] =
       let err = osLastError()
       if err.int32 != ERROR_IO_PENDING:
         GC_unref(ol)
-        retFuture.fail(newException(OSError, osErrorMsg(err)))
+        retFuture.fail(newOSError(err))
     else:
       # Request completed immediately.
-      var bytesWritten: DWord
+      var bytesWritten: DWORD
       let overlappedRes = getOverlappedResult(f.fd.Handle,
-          cast[POverlapped](ol), bytesWritten, false.WinBool)
+          cast[POVERLAPPED](ol), bytesWritten, false.WINBOOL)
       if not overlappedRes.bool:
-        retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+        retFuture.fail(newOSError(osLastError()))
       else:
         assert bytesWritten == size.int32
         retFuture.complete()
@@ -382,13 +383,13 @@ proc writeBuffer*(f: AsyncFile, buf: pointer, size: int): Future[void] =
 
     proc cb(fd: AsyncFD): bool =
       result = true
-      let remainderSize = size-written
+      let remainderSize = size - written
       var cbuf = cast[cstring](buf)
       let res = write(fd.cint, addr cbuf[written], remainderSize.cint)
       if res < 0:
         let lastError = osLastError()
         if lastError.int32 != EAGAIN:
-          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+          retFuture.fail(newOSError(lastError))
         else:
           result = false # We still want this callback to be called.
       else:
@@ -404,7 +405,7 @@ proc writeBuffer*(f: AsyncFile, buf: pointer, size: int): Future[void] =
   return retFuture
 
 proc write*(f: AsyncFile, data: string): Future[void] =
-  ## Writes ``data`` to the file specified asynchronously.
+  ## Writes `data` to the file specified asynchronously.
   ##
   ## The returned Future will complete once all data has been written to the
   ## specified file.
@@ -412,24 +413,23 @@ proc write*(f: AsyncFile, data: string): Future[void] =
   var copy = data
   when defined(windows) or defined(nimdoc):
     var buffer = alloc0(data.len)
-    copyMem(buffer, addr copy[0], data.len)
+    copyMem(buffer, copy.cstring, data.len)
 
-    var ol = PCustomOverlapped()
-    GC_ref(ol)
+    var ol = newCustom()
     ol.data = CompletionData(fd: f.fd, cb:
-      proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
+      proc (fd: AsyncFD, bytesCount: DWORD, errcode: OSErrorCode) =
         if not retFuture.finished:
           if errcode == OSErrorCode(-1):
             assert bytesCount == data.len.int32
             retFuture.complete()
           else:
-            retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+            retFuture.fail(newOSError(errcode))
         if buffer != nil:
           dealloc buffer
           buffer = nil
     )
-    ol.offset = DWord(f.offset and 0xffffffff)
-    ol.offsetHigh = DWord(f.offset shr 32)
+    ol.offset = DWORD(f.offset and 0xffffffff)
+    ol.offsetHigh = DWORD(f.offset shr 32)
     f.offset.inc(data.len)
 
     # According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten.
@@ -442,14 +442,14 @@ proc write*(f: AsyncFile, data: string): Future[void] =
           dealloc buffer
           buffer = nil
         GC_unref(ol)
-        retFuture.fail(newException(OSError, osErrorMsg(err)))
+        retFuture.fail(newOSError(err))
     else:
       # Request completed immediately.
-      var bytesWritten: DWord
+      var bytesWritten: DWORD
       let overlappedRes = getOverlappedResult(f.fd.Handle,
-          cast[POverlapped](ol), bytesWritten, false.WinBool)
+          cast[POVERLAPPED](ol), bytesWritten, false.WINBOOL)
       if not overlappedRes.bool:
-        retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+        retFuture.fail(newOSError(osLastError()))
       else:
         assert bytesWritten == data.len.int32
         retFuture.complete()
@@ -458,12 +458,19 @@ proc write*(f: AsyncFile, data: string): Future[void] =
 
     proc cb(fd: AsyncFD): bool =
       result = true
-      let remainderSize = data.len-written
-      let res = write(fd.cint, addr copy[written], remainderSize.cint)
+
+      let remainderSize = data.len - written
+
+      let res =
+        if data.len == 0:
+          write(fd.cint, copy.cstring, 0)
+        else:
+          write(fd.cint, addr copy[written], remainderSize.cint)
+
       if res < 0:
         let lastError = osLastError()
         if lastError.int32 != EAGAIN:
-          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+          retFuture.fail(newOSError(lastError))
         else:
           result = false # We still want this callback to be called.
       else:
@@ -482,13 +489,13 @@ proc setFileSize*(f: AsyncFile, length: int64) =
   ## Set a file length.
   when defined(windows) or defined(nimdoc):
     var
-      high = (length shr 32).Dword
+      high = (length shr 32).DWORD
     let
-      low = (length and 0xffffffff).Dword
+      low = (length and 0xffffffff).DWORD
       status = setFilePointer(f.fd.Handle, low, addr high, 0)
       lastErr = osLastError()
     if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
-       (setEndOfFile(f.fd.Handle) == 0):
+        (setEndOfFile(f.fd.Handle) == 0):
       raiseOSError(osLastError())
   else:
     # will truncate if Off is a 32-bit type!