Merge branch 'master' of github.com:Araq/Nimrod

This commit is contained in:
Araq 2012-12-26 22:48:48 +01:00
commit df1e7b8021
7 changed files with 262 additions and 176 deletions

View file

@ -440,6 +440,9 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
## sockets properly. This function guarantees that ``line`` is a full line, ## sockets properly. This function guarantees that ``line`` is a full line,
## if this function can only retrieve some data; it will save this data and ## if this function can only retrieve some data; it will save this data and
## add it to the result when a full line is retrieved. ## add it to the result when a full line is retrieved.
##
## Unlike ``sockets.recvLine`` this function will raise an EOS or ESSL
## exception if an error occurs.
setLen(line.string, 0) setLen(line.string, 0)
var dataReceived = "".TaintedString var dataReceived = "".TaintedString
var ret = s.socket.recvLineAsync(dataReceived) var ret = s.socket.recvLineAsync(dataReceived)
@ -458,6 +461,7 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
of RecvDisconnected: of RecvDisconnected:
result = true result = true
of RecvFail: of RecvFail:
s.SocketError(async = true)
result = false result = false
proc send*(sock: PAsyncSocket, data: string) = proc send*(sock: PAsyncSocket, data: string) =
@ -594,7 +598,9 @@ when isMainModule:
proc testRead(s: PAsyncSocket, no: int) = proc testRead(s: PAsyncSocket, no: int) =
echo("Reading! " & $no) echo("Reading! " & $no)
var data = s.getSocket.recv() var data = ""
if not s.recvLine(data):
OSError()
if data == "": if data == "":
echo("Closing connection. " & $no) echo("Closing connection. " & $no)
s.close() s.close()

View file

@ -96,8 +96,9 @@ type
EFTP* = object of ESynch EFTP* = object of ESynch
proc FTPClient*(address: string, port = TPort(21), proc FTPClient*(address: string, port = TPort(21),
user, pass = ""): TFTPClient = user, pass = ""): PFTPClient =
## Create a ``TFTPClient`` object. ## Create a ``PFTPClient`` object.
new(result)
result.user = user result.user = user
result.pass = pass result.pass = pass
result.address = address result.address = address
@ -278,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. ## It doesn't if `async` is true, because it doesn't check for 226 then.
if ftp.dsockConnected: if ftp.dsockConnected:
var r = TaintedString"" var r = TaintedString""
if getDSock(ftp).recvAsync(r): if ftp.isAsync:
if r.string != "": if ftp.asyncDSock.recvLine(r):
ftp.job.lines.add(r.string) if r.string == "":
else: ftp.dsockConnected = false
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: if not async:
var readSocks: seq[TSocket] = @[ftp.getCSock()] var readSocks: seq[TSocket] = @[ftp.getCSock()]
@ -389,7 +399,7 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string =
proc retrText*(ftp: PFTPClient, file: string, async = false): string = proc retrText*(ftp: PFTPClient, file: string, async = false): string =
## Retrieves ``file``. File must be ASCII text. ## Retrieves ``file``. File must be ASCII text.
## If ``async`` is true, this function will return immediately and ## 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.createJob(getLines, JRetrText)
ftp.pasv() ftp.pasv()
assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"] assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"]
@ -404,12 +414,14 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string =
proc getFile(ftp: PFTPClient, async = false): bool = proc getFile(ftp: PFTPClient, async = false): bool =
if ftp.dsockConnected: if ftp.dsockConnected:
var r = "".TaintedString var r = "".TaintedString
var bytesRead = 0
var returned = false var returned = false
if async: if async:
if not ftp.isAsync: raise newException(EFTP, "FTPClient must be 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: else:
r = getDSock(ftp).recv() bytesRead = getDSock(ftp).recv(r, BufferSize)
returned = true returned = true
let r2 = r.string let r2 = r.string
if r2 != "": if r2 != "":
@ -429,8 +441,9 @@ proc getFile(ftp: PFTPClient, async = false): bool =
proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) =
## Downloads ``file`` and saves it to ``dest``. Usage of this function ## Downloads ``file`` and saves it to ``dest``. Usage of this function
## asynchronously is recommended to view the progress of the download. ## asynchronously is recommended to view the progress of the download.
## The ``EvRetr`` event is given by ``poll`` when the download is finished, ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
## and the ``filename`` field will be equal to ``file``. ## when the download is finished, and the ``filename`` field will be equal
## to ``file``.
ftp.createJob(getFile, JRetr) ftp.createJob(getFile, JRetr)
ftp.job.file = open(dest, mode = fmWrite) ftp.job.file = open(dest, mode = fmWrite)
ftp.pasv() ftp.pasv()
@ -492,8 +505,9 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) =
## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
## function asynchronously is recommended to view the progress of ## function asynchronously is recommended to view the progress of
## the download. ## the download.
## The ``EvStore`` event is given by ``poll`` when the upload is finished, ## The ``EvStore`` event is passed to the specified ``handleEvent`` function
## and the ``filename`` field will be equal to ``file``. ## when the upload is finished, and the ``filename`` field will be
## equal to ``file``.
ftp.createJob(doUpload, JStore) ftp.createJob(doUpload, JStore)
ftp.job.file = open(file) ftp.job.file = open(file)
ftp.job.total = ftp.job.file.getFileSize() ftp.job.total = ftp.job.file.getFileSize()
@ -518,16 +532,6 @@ proc close*(ftp: PFTPClient) =
ftp.csock.close() ftp.csock.close()
ftp.dsock.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) = proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
if ftp.jobInProgress: if ftp.jobInProgress:
assertReply ftp.expectReply(), "226" # Make sure the transfer completed. assertReply ftp.expectReply(), "226" # Make sure the transfer completed.
@ -550,32 +554,6 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
ftp.handleEvent(ftp, r) 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), proc AsyncFTPClient*(address: string, port = TPort(21),
user, pass = "", user, pass = "",
handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} = handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} =

View file

@ -80,62 +80,43 @@ proc fileError(msg: string) =
e.msg = msg e.msg = msg
raise e raise e
proc charAt(d: var string, i: var int, s: TSocket): char {.inline.} =
result = d[i]
while result == '\0':
d = string(s.recv())
i = 0
result = d[i]
proc parseChunks(s: TSocket): string = proc parseChunks(s: TSocket): string =
# get chunks:
var i = 0
result = "" result = ""
var d = s.recv().string var ri = 0
while true: while true:
var chunkSizeStr = ""
var chunkSize = 0 var chunkSize = 0
var digitFound = false if s.recvLine(chunkSizeStr):
while true: var i = 0
case d[i] if chunkSizeStr == "":
of '0'..'9': httpError("Server terminated connection prematurely")
digitFound = true while true:
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0')) case chunkSizeStr[i]
of 'a'..'f': of '0'..'9':
digitFound = true chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10) of 'a'..'f':
of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
digitFound = true of 'A'..'F':
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('A') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
of '\0': of '\0':
d = string(s.recv()) break
i = -1 of ';':
else: break # http://tools.ietf.org/html/rfc2616#section-3.6.1
inc(i) # We don't care about chunk-extensions.
if not digitFound: httpError("Chunksize expected") break
else:
httpError("Invalid chunk size: " & chunkSizeStr)
inc(i)
if chunkSize <= 0: break if chunkSize <= 0: break
while charAt(d, i, s) notin {'\C', '\L', '\0'}: inc(i) result.setLen(ri+chunkSize)
if charAt(d, i, s) == '\C': inc(i) var bytesRead = 0
if charAt(d, i, s) == '\L': inc(i) while bytesRead != chunkSize:
else: httpError("CR-LF after chunksize expected") let ret = recv(s, addr(result[ri]), chunkSize-bytesRead)
ri += ret
var x = substr(d, i, i+chunkSize-1) bytesRead += ret
var size = x.len s.skip(2) # Skip \c\L
result.add(x) # Trailer headers will only be sent if the request specifies that we want
inc(i, size) # them: http://tools.ietf.org/html/rfc2616#section-3.6.1
if size < chunkSize:
# read in the rest:
var missing = chunkSize - size
var L = result.len
setLen(result, L + missing)
while missing > 0:
var bytesRead = s.recv(addr(result[L]), missing)
inc(L, bytesRead)
dec(missing, bytesRead)
# next chunk:
d = string(s.recv())
i = 0
# skip trailing CR-LF:
while charAt(d, i, s) in {'\C', '\L'}: inc(i)
proc parseBody(s: TSocket, proc parseBody(s: TSocket,
headers: PStringTable): string = headers: PStringTable): string =
@ -250,7 +231,6 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
## | Requests ``url`` with the specified ``httpMethod``. ## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be seperated by ``\c\L`` ## | Extra headers can be specified and must be seperated by ``\c\L``
var r = parseUrl(url) var r = parseUrl(url)
var headers = substr($httpMethod, len("http")) var headers = substr($httpMethod, len("http"))
headers.add(" /" & r.path & r.query) headers.add(" /" & r.path & r.query)
@ -285,7 +265,7 @@ proc redirection(status: string): bool =
return True return True
proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
## | GET's the ``url`` and returns a ``TResponse`` object ## | GETs the ``url`` and returns a ``TResponse`` object
## | This proc also handles redirection ## | This proc also handles redirection
result = request(url) result = request(url)
for i in 1..maxRedirects: for i in 1..maxRedirects:
@ -295,7 +275,7 @@ proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLCon
result = request(locationHeader, sslContext = sslContext) result = request(locationHeader, sslContext = sslContext)
proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string = proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string =
## | GET's the body and returns it as a string. ## | GETs the body and returns it as a string.
## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = get(url, sslContext = sslContext) var r = get(url, sslContext = sslContext)
if r.status[0] in {'4','5'}: if r.status[0] in {'4','5'}:
@ -305,7 +285,7 @@ proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): stri
proc post*(url: string, extraHeaders = "", body = "", proc post*(url: string, extraHeaders = "", body = "",
maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
## | POST's ``body`` to the ``url`` and returns a ``TResponse`` object. ## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object.
## | This proc adds the necessary Content-Length header. ## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection. ## | This proc also handles redirection.
var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L" var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L"
@ -319,7 +299,7 @@ proc post*(url: string, extraHeaders = "", body = "",
proc postContent*(url: string, extraHeaders = "", body = "", proc postContent*(url: string, extraHeaders = "", body = "",
sslContext: PSSLContext = defaultSSLContext): string = sslContext: PSSLContext = defaultSSLContext): string =
## | POST's ``body`` to ``url`` and returns the response's body as a string ## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = post(url, extraHeaders, body) var r = post(url, extraHeaders, body)
if r.status[0] in {'4','5'}: if r.status[0] in {'4','5'}:

View file

@ -49,26 +49,35 @@ proc raiseNoOK(status: string) =
raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status)
proc parseStatus(r: TRedis): TRedisStatus = proc parseStatus(r: TRedis): TRedisStatus =
var line = r.socket.recv.string var line = ""
if r.socket.recvLine(line):
if line == "":
raise newException(ERedis, "Server closed connection prematurely")
if line[0] == '-': if line[0] == '-':
raise newException(ERedis, strip(line)) raise newException(ERedis, strip(line))
if line[0] != '+': if line[0] != '+':
raiseInvalidReply('+', line[0]) raiseInvalidReply('+', line[0])
return line.substr(1, line.len-3) # Strip '+' and \c\L. return line.substr(1) # Strip '+'
else:
OSError()
proc parseInteger(r: TRedis): TRedisInteger = proc parseInteger(r: TRedis): TRedisInteger =
var line = r.socket.recv.string var line = ""
if r.socket.recvLine(line):
if line == "":
raise newException(ERedis, "Server closed connection prematurely")
if line[0] == '-': if line[0] == '-':
raise newException(ERedis, strip(line)) raise newException(ERedis, strip(line))
if line[0] != ':': if line[0] != ':':
raiseInvalidReply(':', line[0]) raiseInvalidReply(':', line[0])
# Strip ':' and \c\L. # Strip ':'
if parseBiggestInt(line, result, 1) == 0: if parseBiggestInt(line, result, 1) == 0:
raise newException(EInvalidReply, "Unable to parse integer.") raise newException(EInvalidReply, "Unable to parse integer.")
else: OSError()
proc recv(sock: TSocket, size: int): TaintedString = proc recv(sock: TSocket, size: int): TaintedString =
result = newString(size).TaintedString result = newString(size).TaintedString
@ -838,8 +847,11 @@ proc save*(r: TRedis) =
proc shutdown*(r: TRedis) = proc shutdown*(r: TRedis) =
## Synchronously save the dataset to disk and then shut down the server ## Synchronously save the dataset to disk and then shut down the server
r.sendCommand("SHUTDOWN") r.sendCommand("SHUTDOWN")
var s = r.socket.recv() var s = "".TaintedString
if s.string.len != 0: raise newException(ERedis, s.string) if r.socket.recvLine(s):
if s.string.len != 0: raise newException(ERedis, s.string)
else:
OSError()
proc slaveof*(r: TRedis, host: string, port: string) = proc slaveof*(r: TRedis, host: string, port: string) =
## Make the server a slave of another instance, or promote it as master ## Make the server a slave of another instance, or promote it as master

View file

@ -47,12 +47,15 @@ when defined(ssl):
TSSLAcceptResult* = enum TSSLAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
const
BufferSize*: int = 4000 ## size of a buffered socket's buffer
type type
TSocketImpl = object ## socket type TSocketImpl = object ## socket type
fd: cint fd: cint
case isBuffered: bool # determines whether this socket is buffered. case isBuffered: bool # determines whether this socket is buffered.
of true: of true:
buffer: array[0..4000, char] buffer: array[0..BufferSize, char]
currPos: int # current index in buffer currPos: int # current index in buffer
bufLen: int # current length of buffer bufLen: int # current length of buffer
of false: nil of false: nil
@ -62,6 +65,8 @@ type
sslHandle: PSSL sslHandle: PSSL
sslContext: PSSLContext sslContext: PSSLContext
sslNoHandshake: bool # True if needs handshake. sslNoHandshake: bool # True if needs handshake.
sslHasPeekChar: bool
sslPeekChar: char
of false: nil of false: nil
TSocket* = ref TSocketImpl TSocket* = ref TSocketImpl
@ -291,12 +296,55 @@ when defined(ssl):
socket.sslContext = ctx socket.sslContext = ctx
socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext)) socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext))
socket.sslNoHandshake = false socket.sslNoHandshake = false
socket.sslHasPeekChar = false
if socket.sslHandle == nil: if socket.sslHandle == nil:
SSLError() SSLError()
if SSLSetFd(socket.sslHandle, socket.fd) != 1: if SSLSetFd(socket.sslHandle, socket.fd) != 1:
SSLError() SSLError()
proc SocketError*(socket: TSocket, err: int = -1, async = false) =
## Raises proper errors based on return values of ``recv`` functions.
##
## If ``async`` is ``True`` no error will be thrown in the case when the
## error was caused by no data being available to be read.
##
## If ``err`` is not lower than 0 no exception will be raised.
when defined(ssl):
if socket.isSSL:
if err <= 0:
var ret = SSLGetError(socket.sslHandle, err.cint)
case ret
of SSL_ERROR_ZERO_RETURN:
SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
if async:
return
else: SSLError("Not enough data on socket.")
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
if async:
return
else: SSLError("Not enough data on socket.")
of SSL_ERROR_WANT_X509_LOOKUP:
SSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError()
else: SSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
if async:
when defined(windows):
# TODO: Test on Windows
var err = WSAGetLastError()
if err == WSAEWOULDBLOCK:
return
else: OSError()
else:
if errno == EAGAIN or errno == EWOULDBLOCK:
return
else: OSError()
else: OSError()
proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} =
## Marks ``socket`` as accepting connections. ## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the ## ``Backlog`` specifies the maximum length of the
@ -849,11 +897,8 @@ proc checkBuffer(readfds: var seq[TSocket]): int =
var res: seq[TSocket] = @[] var res: seq[TSocket] = @[]
result = 0 result = 0
for s in readfds: for s in readfds:
if s.isBuffered: if hasDataBuffered(s):
if s.bufLen <= 0 or s.currPos == s.bufLen: inc(result)
res.add(s)
else:
inc(result)
else: else:
res.add(s) res.add(s)
readfds = res readfds = res
@ -975,47 +1020,76 @@ template retRead(flags, readBytes: int) =
proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} = proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} =
## receives data from a socket ## receives data from a socket
if size == 0: return
if socket.isBuffered: if socket.isBuffered:
if socket.bufLen == 0: if socket.bufLen == 0:
retRead(0'i32, 0) retRead(0'i32, 0)
when true: var read = 0
var read = 0 while read < size:
while read < size: if socket.currPos >= socket.bufLen:
if socket.currPos >= socket.bufLen: retRead(0'i32, read)
retRead(0'i32, read)
let chunk = min(socket.bufLen-socket.currPos, size-read) let chunk = min(socket.bufLen-socket.currPos, size-read)
var d = cast[cstring](data) var d = cast[cstring](data)
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
read.inc(chunk) read.inc(chunk)
socket.currPos.inc(chunk) socket.currPos.inc(chunk)
else:
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
var d = cast[cstring](data)
d[read] = socket.buffer[socket.currPos]
read.inc(1)
socket.currPos.inc(1)
result = read result = read
else: else:
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
result = SSLRead(socket.sslHandle, data, size) if socket.sslHasPeekChar:
copyMem(data, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = false
if size-1 > 0:
var d = cast[cstring](data)
result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1
else:
result = 1
else:
result = SSLRead(socket.sslHandle, data, size)
else: else:
result = recv(socket.fd, data, size.cint, 0'i32) result = recv(socket.fd, data, size.cint, 0'i32)
else: else:
result = recv(socket.fd, data, size.cint, 0'i32) result = recv(socket.fd, data, size.cint, 0'i32)
proc recv*(socket: TSocket, data: var string, size: int): int =
## higher-level version of the above
##
## When 0 is returned the socket's connection has been closed.
##
## This function will throw an EOS exception when an error occurs. A value
## lower than 0 is never returned.
##
## **Note**: ``data`` must be initialised.
data.setLen(size)
result = recv(socket, cstring(data), size)
if result < 0:
data.setLen(0)
socket.SocketError(result)
data.setLen(result)
proc recvAsync*(socket: TSocket, data: var string, size: int): int =
## Async version of the above.
##
## When socket is non-blocking and no data is available on the socket,
## ``-1`` will be returned and ``data`` will be ``""``.
##
## **Note**: ``data`` must be initialised.
data.setLen(size)
result = recv(socket, cstring(data), size)
if result < 0:
data.setLen(0)
socket.SocketError(async = true)
result = -1
data.setLen(result)
proc waitFor(socket: TSocket, waited: var float, timeout: int): int {. proc waitFor(socket: TSocket, waited: var float, timeout: int): int {.
tags: [FTime].} = tags: [FTime].} =
## returns the number of characters available to be read. In unbuffered ## returns the number of characters available to be read. In unbuffered
## sockets this is always 1, otherwise this may as big as the buffer, currently ## sockets this is always 1, otherwise this may as big as ``BufferSize``.
## 4000.
result = 1 result = 1
if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos: if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos:
result = socket.bufLen - socket.currPos result = socket.bufLen - socket.currPos
@ -1045,6 +1119,18 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {.
result = read result = read
proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int =
## higher-level version of the above.
##
## Similar to the non-timeout version this will throw an EOS exception
## when an error occurs.
data.setLen(size)
result = recv(socket, cstring(data), size, timeout)
if result < 0:
data.setLen(0)
socket.SocketError()
data.setLen(result)
proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
if socket.isBuffered: if socket.isBuffered:
result = 1 result = 1
@ -1057,8 +1143,12 @@ proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
else: else:
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
raise newException(ESSL, "Sorry, you cannot use recvLine on an unbuffered SSL socket.") if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = true
c = socket.sslPeekChar
return
result = recv(socket.fd, addr(c), 1, MSG_PEEK) result = recv(socket.fd, addr(c), 1, MSG_PEEK)
proc recvLine*(socket: TSocket, line: var TaintedString): bool {. proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
@ -1067,14 +1157,12 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
## added to ``line``, however if solely ``\r\L`` is received then ``line`` ## added to ``line``, however if solely ``\r\L`` is received then ``line``
## will be set to it. ## will be set to it.
## ##
## ``True`` is returned if data is available. ``False`` usually suggests an ## ``True`` is returned if data is available. ``False`` suggests an
## error, EOS exceptions are not raised in favour of this. ## error, EOS exceptions are not raised and ``False`` is simply returned
## instead.
## ##
## If the socket is disconnected, ``line`` will be set to ``""`` and ``True`` ## If the socket is disconnected, ``line`` will be set to ``""`` and ``True``
## will be returned. ## will be returned.
##
## **Warning:** Using this function on a unbuffered ssl socket will result
## in an error.
template addNLIfEmpty(): stmt = template addNLIfEmpty(): stmt =
if line.len == 0: if line.len == 0:
line.add("\c\L") line.add("\c\L")
@ -1101,6 +1189,8 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {.
tags: [FReadIO, FTime].} = tags: [FReadIO, FTime].} =
## variant with a ``timeout`` parameter, the timeout parameter specifies ## variant with a ``timeout`` parameter, the timeout parameter specifies
## how many miliseconds to wait for data. ## how many miliseconds to wait for data.
##
## ``ETimeout`` will be raised if ``timeout`` is exceeded.
template addNLIfEmpty(): stmt = template addNLIfEmpty(): stmt =
if line.len == 0: if line.len == 0:
line.add("\c\L") line.add("\c\L")
@ -1153,11 +1243,13 @@ proc recvLineAsync*(socket: TSocket,
elif c == '\L': return RecvFullLine elif c == '\L': return RecvFullLine
add(line.string, c) add(line.string, c)
proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} = proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} =
## receives all the available data from the socket. ## receives all the available data from the socket.
## Socket errors will result in an ``EOS`` error. ## Socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected ## If socket is not a connectionless socket and socket is not connected
## ``""`` will be returned. ## ``""`` will be returned.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 4000 const bufSize = 4000
result = newStringOfCap(bufSize).TaintedString result = newStringOfCap(bufSize).TaintedString
var pos = 0 var pos = 0
@ -1183,10 +1275,12 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} =
if bytesRead != bufSize-1: break if bytesRead != bufSize-1: break
proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {. proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {.
tags: [FReadIO].} = tags: [FReadIO], deprecated.} =
## overloaded variant to support a ``timeout`` parameter, the ``timeout`` ## overloaded variant to support a ``timeout`` parameter, the ``timeout``
## parameter specifies the amount of miliseconds to wait for data on the ## parameter specifies the amount of miliseconds to wait for data on the
## socket. ## socket.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
if socket.bufLen == 0: if socket.bufLen == 0:
var s = @[socket] var s = @[socket]
if s.select(timeout) != 1: if s.select(timeout) != 1:
@ -1195,12 +1289,14 @@ proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {.
return socket.recv return socket.recv
proc recvAsync*(socket: TSocket, s: var TaintedString): bool {. proc recvAsync*(socket: TSocket, s: var TaintedString): bool {.
tags: [FReadIO].} = tags: [FReadIO], deprecated.} =
## receives all the data from a non-blocking socket. If socket is non-blocking ## receives all the data from a non-blocking socket. If socket is non-blocking
## and there are no messages available, `False` will be returned. ## and there are no messages available, `False` will be returned.
## Other socket errors will result in an ``EOS`` error. ## Other socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected ## If socket is not a connectionless socket and socket is not connected
## ``s`` will be set to ``""``. ## ``s`` will be set to ``""``.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 1000 const bufSize = 1000
# ensure bufSize capacity: # ensure bufSize capacity:
setLen(s.string, bufSize) setLen(s.string, bufSize)
@ -1287,13 +1383,25 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int,
return False return False
else: OSError() else: OSError()
proc skip*(socket: TSocket) {.tags: [FReadIO].} = proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} =
## skips all the data that is pending for the socket ## skips all the data that is pending for the socket
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 1000 const bufSize = 1000
var buf = alloc(bufSize) var buf = alloc(bufSize)
while recv(socket, buf, bufSize) == bufSize: nil while recv(socket, buf, bufSize) == bufSize: nil
dealloc(buf) dealloc(buf)
proc skip*(socket: TSocket, size: int) =
## Skips ``size`` amount of bytes.
##
## Returns the number of skipped bytes.
var dummy = alloc(size)
var bytesSkipped = 0
while bytesSkipped != size:
bytesSkipped += recv(socket, dummy, size-bytesSkipped)
dealloc(dummy)
proc send*(socket: TSocket, data: pointer, size: int): int {. proc send*(socket: TSocket, data: pointer, size: int): int {.
tags: [FWriteIO].} = tags: [FWriteIO].} =
## sends data to a socket. ## sends data to a socket.
@ -1397,7 +1505,6 @@ proc sendTo*(socket: TSocket, address: string, port: TPort,
when defined(Windows): when defined(Windows):
const const
SOCKET_ERROR = -1
IOCPARM_MASK = 127 IOCPARM_MASK = 127
IOC_IN = int(-2147483648) IOC_IN = int(-2147483648)
FIONBIO = int(IOC_IN or ((sizeof(int) and IOCPARM_MASK) shl 16) or FIONBIO = int(IOC_IN or ((sizeof(int) and IOCPARM_MASK) shl 16) or
@ -1410,7 +1517,7 @@ when defined(Windows):
proc setBlocking(s: TSocket, blocking: bool) = proc setBlocking(s: TSocket, blocking: bool) =
when defined(Windows): when defined(Windows):
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
if SOCKET_ERROR == ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)): if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1:
OSError() OSError()
else: # BSD sockets else: # BSD sockets
var x: int = fcntl(s.fd, F_GETFL, 0) var x: int = fcntl(s.fd, F_GETFL, 0)

View file

@ -16596,6 +16596,8 @@ proc message_dialog_new*(parent: PWindow, flags: TDialogFlags,
thetype: TMessageType, buttons: TButtonsType, thetype: TMessageType, buttons: TButtonsType,
message_format: cstring): PMessageDialog{.varargs, message_format: cstring): PMessageDialog{.varargs,
cdecl, importc: "gtk_message_dialog_new", dynlib: lib.} cdecl, importc: "gtk_message_dialog_new", dynlib: lib.}
proc set_markup*(msgDialog: PMessageDialog, str: cstring) {.cdecl,
importc: "gtk_message_dialog_set_markup", dynlib: lib.}
proc signal_new*(name: cstring, signal_flags: TSignalRunType, proc signal_new*(name: cstring, signal_flags: TSignalRunType,
object_type: TType, function_offset: guint, object_type: TType, function_offset: guint,
marshaller: TSignalMarshaller, return_val: TType, n_args: guint): guint{. marshaller: TSignalMarshaller, return_val: TType, n_args: guint): guint{.

View file

@ -272,7 +272,7 @@ proc handleWebMessage(state: PState, line: string) =
message.add(limitCommitMsg(commit["message"].str)) message.add(limitCommitMsg(commit["message"].str))
# Send message to #nimrod. # Send message to #nimrod.
state.ircClient[].privmsg(joinChans[0], message) state.ircClient.privmsg(joinChans[0], message)
elif json.existsKey("redisinfo"): elif json.existsKey("redisinfo"):
assert json["redisinfo"].existsKey("port") assert json["redisinfo"].existsKey("port")
#let redisPort = json["redisinfo"]["port"].num #let redisPort = json["redisinfo"]["port"].num
@ -336,10 +336,11 @@ proc hubConnect(state: PState) =
state.dispatcher.register(state.sock) state.dispatcher.register(state.sock)
proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) = proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) =
case event.typ case event.typ
of EvConnected: nil
of EvDisconnected: of EvDisconnected:
while not state.ircClient[].isConnected: while not state.ircClient.isConnected:
try: try:
state.ircClient.connect() state.ircClient.connect()
except: except:
@ -355,12 +356,12 @@ proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) =
let msg = event.params[event.params.len-1] let msg = event.params[event.params.len-1]
let words = msg.split(' ') let words = msg.split(' ')
template pm(msg: string): stmt = template pm(msg: string): stmt =
state.ircClient[].privmsg(event.origin, msg) state.ircClient.privmsg(event.origin, msg)
case words[0] case words[0]
of "!ping": pm("pong") of "!ping": pm("pong")
of "!lag": of "!lag":
if state.ircClient[].getLag != -1.0: if state.ircClient.getLag != -1.0:
var lag = state.ircClient[].getLag var lag = state.ircClient.getLag
lag = lag * 1000.0 lag = lag * 1000.0
pm($int(lag) & "ms between me and the server.") pm($int(lag) & "ms between me and the server.")
else: else:
@ -433,7 +434,7 @@ proc open(port: TPort = TPort(5123)): PState =
res.hubPort = port res.hubPort = port
res.hubConnect() res.hubConnect()
let hirc = let hirc =
proc (a: var TAsyncIRC, ev: TIRCEvent) = proc (a: PAsyncIRC, ev: TIRCEvent) =
handleIrc(a, ev, res) handleIrc(a, ev, res)
# Connect to the irc server. # Connect to the irc server.
res.ircClient = AsyncIrc(ircServer, nick = botNickname, user = botNickname, res.ircClient = AsyncIrc(ircServer, nick = botNickname, user = botNickname,