diff options
Diffstat (limited to 'lib/pure/asyncfile.nim')
-rw-r--r-- | lib/pure/asyncfile.nim | 177 |
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! |