Merge branch 'master' of github.com:Araq/Nimrod
This commit is contained in:
commit
b4cd119800
7 changed files with 442 additions and 100 deletions
|
|
@ -160,6 +160,9 @@ Generic Operating System Services
|
||||||
This module provides support for memory mapped files (Posix's ``mmap``)
|
This module provides support for memory mapped files (Posix's ``mmap``)
|
||||||
on the different operating systems.
|
on the different operating systems.
|
||||||
|
|
||||||
|
* `fsmonitor <fsmonitor.html>`_
|
||||||
|
This module implements the ability to monitor a directory/file for changes
|
||||||
|
using Posix's inotify API.
|
||||||
|
|
||||||
Math libraries
|
Math libraries
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
70
lib/posix/inotify.nim
Normal file
70
lib/posix/inotify.nim
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Nimrod's Runtime Library
|
||||||
|
# (c) Copyright 2012 Dominik Picheta
|
||||||
|
#
|
||||||
|
# See the file "copying.txt", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Get the platform-dependent flags.
|
||||||
|
# Structure describing an inotify event.
|
||||||
|
type
|
||||||
|
Tinotify_event*{.pure, final, importc: "struct inotify_event",
|
||||||
|
header: "<sys/inotify.h>".} = object
|
||||||
|
wd*{.importc: "wd".}: cint # Watch descriptor.
|
||||||
|
mask*{.importc: "mask".}: uint32 # Watch mask.
|
||||||
|
cookie*{.importc: "cookie".}: uint32 # Cookie to synchronize two events.
|
||||||
|
len*{.importc: "len".}: uint32 # Length (including NULs) of name.
|
||||||
|
name*{.importc: "name".}: char # Name.
|
||||||
|
|
||||||
|
# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
|
||||||
|
const
|
||||||
|
IN_ACCESS* = 0x00000001 # File was accessed.
|
||||||
|
IN_MODIFY* = 0x00000002 # File was modified.
|
||||||
|
IN_ATTRIB* = 0x00000004 # Metadata changed.
|
||||||
|
IN_CLOSE_WRITE* = 0x00000008 # Writtable file was closed.
|
||||||
|
IN_CLOSE_NOWRITE* = 0x00000010 # Unwrittable file closed.
|
||||||
|
IN_CLOSE* = (IN_CLOSE_WRITE or IN_CLOSE_NOWRITE) # Close.
|
||||||
|
IN_OPEN* = 0x00000020 # File was opened.
|
||||||
|
IN_MOVED_FROM* = 0x00000040 # File was moved from X.
|
||||||
|
IN_MOVED_TO* = 0x00000080 # File was moved to Y.
|
||||||
|
IN_MOVE* = (IN_MOVED_FROM or IN_MOVED_TO) # Moves.
|
||||||
|
IN_CREATE* = 0x00000100 # Subfile was created.
|
||||||
|
IN_DELETE* = 0x00000200 # Subfile was deleted.
|
||||||
|
IN_DELETE_SELF* = 0x00000400 # Self was deleted.
|
||||||
|
IN_MOVE_SELF* = 0x00000800 # Self was moved.
|
||||||
|
# Events sent by the kernel.
|
||||||
|
const
|
||||||
|
IN_UNMOUNT* = 0x00002000 # Backing fs was unmounted.
|
||||||
|
IN_Q_OVERFLOW* = 0x00004000 # Event queued overflowed.
|
||||||
|
IN_IGNORED* = 0x00008000 # File was ignored.
|
||||||
|
# Special flags.
|
||||||
|
const
|
||||||
|
IN_ONLYDIR* = 0x01000000 # Only watch the path if it is a
|
||||||
|
# directory.
|
||||||
|
IN_DONT_FOLLOW* = 0x02000000 # Do not follow a sym link.
|
||||||
|
IN_EXCL_UNLINK* = 0x04000000 # Exclude events on unlinked
|
||||||
|
# objects.
|
||||||
|
IN_MASK_ADD* = 0x20000000 # Add to the mask of an already
|
||||||
|
# existing watch.
|
||||||
|
IN_ISDIR* = 0x40000000 # Event occurred against dir.
|
||||||
|
IN_ONESHOT* = 0x80000000 # Only send event once.
|
||||||
|
# All events which a program can wait on.
|
||||||
|
const
|
||||||
|
IN_ALL_EVENTS* = (IN_ACCESS or IN_MODIFY or IN_ATTRIB or IN_CLOSE_WRITE or
|
||||||
|
IN_CLOSE_NOWRITE or IN_OPEN or IN_MOVED_FROM or IN_MOVED_TO or
|
||||||
|
IN_CREATE or IN_DELETE or IN_DELETE_SELF or IN_MOVE_SELF)
|
||||||
|
# Create and initialize inotify instance.
|
||||||
|
proc inotify_init*(): cint{.cdecl, importc: "inotify_init",
|
||||||
|
header: "<sys/inotify.h>".}
|
||||||
|
# Create and initialize inotify instance.
|
||||||
|
proc inotify_init1*(flags: cint): cint{.cdecl, importc: "inotify_init1",
|
||||||
|
header: "<sys/inotify.h>".}
|
||||||
|
# Add watch of object NAME to inotify instance FD. Notify about
|
||||||
|
# events specified by MASK.
|
||||||
|
proc inotify_add_watch*(fd: cint; name: cstring; mask: uint32): cint{.
|
||||||
|
cdecl, importc: "inotify_add_watch", header: "<sys/inotify.h>".}
|
||||||
|
# Remove the watch specified by WD from the inotify instance FD.
|
||||||
|
proc inotify_rm_watch*(fd: cint; wd: cint): cint{.cdecl,
|
||||||
|
importc: "inotify_rm_watch", header: "<sys/inotify.h>".}
|
||||||
|
|
@ -70,22 +70,24 @@ import sockets, os
|
||||||
## the socket has established a connection to a server socket; from that point
|
## the socket has established a connection to a server socket; from that point
|
||||||
## it can be safely written to.
|
## it can be safely written to.
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
from winlean import TTimeVal, TFdSet, FD_ZERO, FD_SET, FD_ISSET, select
|
||||||
|
else:
|
||||||
|
from posix import TTimeVal, TFdSet, FD_ZERO, FD_SET, FD_ISSET, select
|
||||||
|
|
||||||
type
|
type
|
||||||
|
TDelegate* = object
|
||||||
TDelegate = object
|
fd*: cint
|
||||||
deleVal*: PObject
|
deleVal*: PObject
|
||||||
|
|
||||||
handleRead*: proc (h: PObject) {.nimcall.}
|
handleRead*: proc (h: PObject) {.nimcall.}
|
||||||
handleWrite*: proc (h: PObject) {.nimcall.}
|
handleWrite*: proc (h: PObject) {.nimcall.}
|
||||||
handleConnect*: proc (h: PObject) {.nimcall.}
|
handleError*: proc (h: PObject) {.nimcall.}
|
||||||
|
hasDataBuffered*: proc (h: PObject): bool {.nimcall.}
|
||||||
handleAccept*: proc (h: PObject) {.nimcall.}
|
|
||||||
getSocket*: proc (h: PObject): tuple[info: TInfo, sock: TSocket] {.nimcall.}
|
open*: bool
|
||||||
|
|
||||||
task*: proc (h: PObject) {.nimcall.}
|
task*: proc (h: PObject) {.nimcall.}
|
||||||
mode*: TMode
|
mode*: TFileMode
|
||||||
|
|
||||||
PDelegate* = ref TDelegate
|
PDelegate* = ref TDelegate
|
||||||
|
|
||||||
|
|
@ -106,24 +108,20 @@ type
|
||||||
lineBuffer: TaintedString ## Temporary storage for ``recvLine``
|
lineBuffer: TaintedString ## Temporary storage for ``recvLine``
|
||||||
sslNeedAccept: bool
|
sslNeedAccept: bool
|
||||||
proto: TProtocol
|
proto: TProtocol
|
||||||
|
deleg: PDelegate
|
||||||
|
|
||||||
TInfo* = enum
|
TInfo = enum
|
||||||
SockIdle, SockConnecting, SockConnected, SockListening, SockClosed, SockUDPBound
|
SockIdle, SockConnecting, SockConnected, SockListening, SockClosed, SockUDPBound
|
||||||
|
|
||||||
TMode* = enum
|
|
||||||
MReadable, MWriteable, MReadWrite
|
|
||||||
|
|
||||||
proc newDelegate*(): PDelegate =
|
proc newDelegate*(): PDelegate =
|
||||||
## Creates a new delegate.
|
## Creates a new delegate.
|
||||||
new(result)
|
new(result)
|
||||||
result.handleRead = (proc (h: PObject) = nil)
|
result.handleRead = (proc (h: PObject) = nil)
|
||||||
result.handleWrite = (proc (h: PObject) = nil)
|
result.handleWrite = (proc (h: PObject) = nil)
|
||||||
result.handleConnect = (proc (h: PObject) = nil)
|
result.handleError = (proc (h: PObject) = nil)
|
||||||
result.handleAccept = (proc (h: PObject) = nil)
|
result.hasDataBuffered = (proc (h: PObject): bool = return false)
|
||||||
result.getSocket = (proc (h: PObject): tuple[info: TInfo, sock: TSocket] =
|
|
||||||
doAssert(false))
|
|
||||||
result.task = (proc (h: PObject) = nil)
|
result.task = (proc (h: PObject) = nil)
|
||||||
result.mode = MReadable
|
result.mode = fmRead
|
||||||
|
|
||||||
proc newAsyncSocket(): PAsyncSocket =
|
proc newAsyncSocket(): PAsyncSocket =
|
||||||
new(result)
|
new(result)
|
||||||
|
|
@ -144,21 +142,28 @@ proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||||
if result.socket == InvalidSocket: OSError()
|
if result.socket == InvalidSocket: OSError()
|
||||||
result.socket.setBlocking(false)
|
result.socket.setBlocking(false)
|
||||||
|
|
||||||
proc asyncSockHandleConnect(h: PObject) =
|
|
||||||
when defined(ssl):
|
|
||||||
if PAsyncSocket(h).socket.isSSL and not
|
|
||||||
PAsyncSocket(h).socket.gotHandshake:
|
|
||||||
return
|
|
||||||
|
|
||||||
PAsyncSocket(h).info = SockConnected
|
|
||||||
PAsyncSocket(h).handleConnect(PAsyncSocket(h))
|
|
||||||
|
|
||||||
proc asyncSockHandleRead(h: PObject) =
|
proc asyncSockHandleRead(h: PObject) =
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
if PAsyncSocket(h).socket.isSSL and not
|
if PAsyncSocket(h).socket.isSSL and not
|
||||||
PAsyncSocket(h).socket.gotHandshake:
|
PAsyncSocket(h).socket.gotHandshake:
|
||||||
return
|
return
|
||||||
PAsyncSocket(h).handleRead(PAsyncSocket(h))
|
|
||||||
|
if PAsyncSocket(h).info != SockListening:
|
||||||
|
assert PAsyncSocket(h).info != SockConnecting
|
||||||
|
PAsyncSocket(h).handleRead(PAsyncSocket(h))
|
||||||
|
else:
|
||||||
|
PAsyncSocket(h).handleAccept(PAsyncSocket(h))
|
||||||
|
|
||||||
|
proc asyncSockHandleWrite(h: PObject) =
|
||||||
|
when defined(ssl):
|
||||||
|
if PAsyncSocket(h).socket.isSSL and not
|
||||||
|
PAsyncSocket(h).socket.gotHandshake:
|
||||||
|
return
|
||||||
|
|
||||||
|
if PAsyncSocket(h).info == SockConnecting:
|
||||||
|
PAsyncSocket(h).handleConnect(PAsyncSocket(h))
|
||||||
|
# Stop receiving write events
|
||||||
|
PAsyncSocket(h).deleg.mode = fmRead
|
||||||
|
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
proc asyncSockDoHandshake(h: PObject) =
|
proc asyncSockDoHandshake(h: PObject) =
|
||||||
|
|
@ -173,19 +178,27 @@ when defined(ssl):
|
||||||
else:
|
else:
|
||||||
# handshake will set socket's ``sslNoHandshake`` field.
|
# handshake will set socket's ``sslNoHandshake`` field.
|
||||||
discard PAsyncSocket(h).socket.handshake()
|
discard PAsyncSocket(h).socket.handshake()
|
||||||
|
|
||||||
proc toDelegate(sock: PAsyncSocket): PDelegate =
|
proc toDelegate(sock: PAsyncSocket): PDelegate =
|
||||||
result = newDelegate()
|
result = newDelegate()
|
||||||
result.deleVal = sock
|
result.deleVal = sock
|
||||||
result.getSocket = (proc (h: PObject): tuple[info: TInfo, sock: TSocket] =
|
result.fd = getFD(sock.socket)
|
||||||
return (PAsyncSocket(h).info, PAsyncSocket(h).socket))
|
# We need this to get write events, just to know when the socket connects.
|
||||||
|
result.mode = fmReadWrite
|
||||||
result.handleConnect = asyncSockHandleConnect
|
|
||||||
|
|
||||||
result.handleRead = asyncSockHandleRead
|
result.handleRead = asyncSockHandleRead
|
||||||
|
result.handleWrite = asyncSockHandleWrite
|
||||||
result.handleAccept = (proc (h: PObject) =
|
# TODO: Errors?
|
||||||
PAsyncSocket(h).handleAccept(PAsyncSocket(h)))
|
#result.handleError = (proc (h: PObject) = assert(false))
|
||||||
|
|
||||||
|
result.hasDataBuffered =
|
||||||
|
proc (h: PObject): bool {.nimcall.} =
|
||||||
|
return PAsyncSocket(h).socket.hasDataBuffered()
|
||||||
|
|
||||||
|
sock.deleg = result
|
||||||
|
if sock.info notin {SockIdle, SockClosed}:
|
||||||
|
sock.deleg.open = true
|
||||||
|
else:
|
||||||
|
sock.deleg.open = false
|
||||||
|
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
result.task = asyncSockDoHandshake
|
result.task = asyncSockDoHandshake
|
||||||
|
|
@ -195,22 +208,26 @@ proc connect*(sock: PAsyncSocket, name: string, port = TPort(0),
|
||||||
## Begins connecting ``sock`` to ``name``:``port``.
|
## Begins connecting ``sock`` to ``name``:``port``.
|
||||||
sock.socket.connectAsync(name, port, af)
|
sock.socket.connectAsync(name, port, af)
|
||||||
sock.info = SockConnecting
|
sock.info = SockConnecting
|
||||||
|
sock.deleg.open = true
|
||||||
|
|
||||||
proc close*(sock: PAsyncSocket) =
|
proc close*(sock: PAsyncSocket) =
|
||||||
## Closes ``sock``. Terminates any current connections.
|
## Closes ``sock``. Terminates any current connections.
|
||||||
sock.info = SockClosed
|
|
||||||
sock.socket.close()
|
sock.socket.close()
|
||||||
|
sock.info = SockClosed
|
||||||
|
sock.deleg.open = false
|
||||||
|
|
||||||
proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
|
proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
|
||||||
## Equivalent to ``sockets.bindAddr``.
|
## Equivalent to ``sockets.bindAddr``.
|
||||||
sock.socket.bindAddr(port, address)
|
sock.socket.bindAddr(port, address)
|
||||||
if sock.proto == IPPROTO_UDP:
|
if sock.proto == IPPROTO_UDP:
|
||||||
sock.info = SockUDPBound
|
sock.info = SockUDPBound
|
||||||
|
sock.deleg.open = true
|
||||||
|
|
||||||
proc listen*(sock: PAsyncSocket) =
|
proc listen*(sock: PAsyncSocket) =
|
||||||
## Equivalent to ``sockets.listen``.
|
## Equivalent to ``sockets.listen``.
|
||||||
sock.socket.listen()
|
sock.socket.listen()
|
||||||
sock.info = SockListening
|
sock.info = SockListening
|
||||||
|
sock.deleg.open = true
|
||||||
|
|
||||||
proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
||||||
address: var string) =
|
address: var string) =
|
||||||
|
|
@ -245,8 +262,11 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
||||||
if c == InvalidSocket: OSError()
|
if c == InvalidSocket: OSError()
|
||||||
c.setBlocking(false) # TODO: Needs to be tested.
|
c.setBlocking(false) # TODO: Needs to be tested.
|
||||||
|
|
||||||
|
# deleg.open is set in ``toDelegate``.
|
||||||
|
|
||||||
client.socket = c
|
client.socket = c
|
||||||
client.lineBuffer = ""
|
client.lineBuffer = ""
|
||||||
|
client.info = SockConnected
|
||||||
|
|
||||||
proc accept*(server: PAsyncSocket, client: var PAsyncSocket) =
|
proc accept*(server: PAsyncSocket, client: var PAsyncSocket) =
|
||||||
## Equivalent to ``sockets.accept``.
|
## Equivalent to ``sockets.accept``.
|
||||||
|
|
@ -297,9 +317,6 @@ proc isWriteable*(s: PAsyncSocket): bool =
|
||||||
var writeSock = @[s.socket]
|
var writeSock = @[s.socket]
|
||||||
return selectWrite(writeSock, 1) != 0 and s.socket notin writeSock
|
return selectWrite(writeSock, 1) != 0 and s.socket notin writeSock
|
||||||
|
|
||||||
proc `userArg=`*(s: PAsyncSocket, val: PObject) =
|
|
||||||
s.userArg = val
|
|
||||||
|
|
||||||
converter getSocket*(s: PAsyncSocket): TSocket =
|
converter getSocket*(s: PAsyncSocket): TSocket =
|
||||||
return s.socket
|
return s.socket
|
||||||
|
|
||||||
|
|
@ -338,75 +355,102 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
|
||||||
of RecvFail:
|
of RecvFail:
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
|
proc timeValFromMilliseconds(timeout = 500): TTimeVal =
|
||||||
|
if timeout != -1:
|
||||||
|
var seconds = timeout div 1000
|
||||||
|
result.tv_sec = seconds.int32
|
||||||
|
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
||||||
|
|
||||||
|
proc createFdSet(fd: var TFdSet, s: seq[PDelegate], m: var int) =
|
||||||
|
FD_ZERO(fd)
|
||||||
|
for i in items(s):
|
||||||
|
m = max(m, int(i.fd))
|
||||||
|
FD_SET(i.fd, fd)
|
||||||
|
|
||||||
|
proc pruneSocketSet(s: var seq[PDelegate], fd: var TFdSet) =
|
||||||
|
var i = 0
|
||||||
|
var L = s.len
|
||||||
|
while i < L:
|
||||||
|
if FD_ISSET(s[i].fd, fd) != 0'i32:
|
||||||
|
s[i] = s[L-1]
|
||||||
|
dec(L)
|
||||||
|
else:
|
||||||
|
inc(i)
|
||||||
|
setLen(s, L)
|
||||||
|
|
||||||
|
proc select(readfds, writefds, exceptfds: var seq[PDelegate],
|
||||||
|
timeout = 500): int =
|
||||||
|
var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
|
||||||
|
|
||||||
|
var rd, wr, ex: TFdSet
|
||||||
|
var m = 0
|
||||||
|
createFdSet(rd, readfds, m)
|
||||||
|
createFdSet(wr, writefds, m)
|
||||||
|
createFdSet(ex, exceptfds, m)
|
||||||
|
|
||||||
|
if timeout != -1:
|
||||||
|
result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), addr(tv)))
|
||||||
|
else:
|
||||||
|
result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), nil))
|
||||||
|
|
||||||
|
pruneSocketSet(readfds, (rd))
|
||||||
|
pruneSocketSet(writefds, (wr))
|
||||||
|
pruneSocketSet(exceptfds, (ex))
|
||||||
|
|
||||||
proc poll*(d: PDispatcher, timeout: int = 500): bool =
|
proc poll*(d: PDispatcher, timeout: int = 500): bool =
|
||||||
## This function checks for events on all the sockets in the `PDispatcher`.
|
## This function checks for events on all the delegates in the `PDispatcher`.
|
||||||
## It then proceeds to call the correct event handler.
|
## It then proceeds to call the correct event handler.
|
||||||
##
|
|
||||||
## **Note:** There is no event which signifes when you have been disconnected,
|
|
||||||
## it is your job to check whether what you get from ``recv`` is ``""``.
|
|
||||||
## If you have been disconnected, `d`'s ``getSocket`` function should report
|
|
||||||
## this appropriately.
|
|
||||||
##
|
##
|
||||||
## This function returns ``True`` if there are sockets that are still
|
## This function returns ``True`` if there are file descriptors that are still
|
||||||
## connected (or connecting), otherwise ``False``. Sockets that have been
|
## open, otherwise ``False``. File descriptors that have been
|
||||||
## closed are immediately removed from the dispatcher automatically.
|
## closed are immediately removed from the dispatcher automatically.
|
||||||
##
|
##
|
||||||
## **Note:** Each delegate has a task associated with it. This gets called
|
## **Note:** Each delegate has a task associated with it. This gets called
|
||||||
## after each select() call, if you make timeout ``-1`` the tasks will
|
## after each select() call, if you set timeout to ``-1`` the tasks will
|
||||||
## only be executed after one or more sockets becomes readable or writeable.
|
## only be executed after one or more file descriptors becomes readable or
|
||||||
|
## writeable.
|
||||||
result = true
|
result = true
|
||||||
var readSocks, writeSocks: seq[TSocket] = @[]
|
var readDg, writeDg, errorDg: seq[PDelegate] = @[]
|
||||||
|
var len = d.delegates.len
|
||||||
var L = d.delegates.len
|
|
||||||
var dc = 0
|
var dc = 0
|
||||||
while dc < L:
|
|
||||||
template deleg: expr = d.delegates[dc]
|
|
||||||
let aSock = deleg.getSocket(deleg.deleVal)
|
|
||||||
if (deleg.mode != MWriteable and aSock.info == SockConnected) or
|
|
||||||
aSock.info == SockListening or aSock.info == SockUDPBound:
|
|
||||||
readSocks.add(aSock.sock)
|
|
||||||
if aSock.info == SockConnecting or
|
|
||||||
(aSock.info == SockConnected and deleg.mode != MReadable):
|
|
||||||
writeSocks.add(aSock.sock)
|
|
||||||
if aSock.info == SockClosed:
|
|
||||||
# Socket has been closed remove it from the dispatcher.
|
|
||||||
d.delegates[dc] = d.delegates[L-1]
|
|
||||||
|
|
||||||
dec L
|
|
||||||
else: inc dc
|
|
||||||
d.delegates.setLen(L)
|
|
||||||
|
|
||||||
if readSocks.len() == 0 and writeSocks.len() == 0:
|
while dc < len:
|
||||||
|
let deleg = d.delegates[dc]
|
||||||
|
if (deleg.mode != fmWrite or deleg.mode != fmAppend) and deleg.open:
|
||||||
|
readDg.add(deleg)
|
||||||
|
if (deleg.mode != fmRead) and deleg.open:
|
||||||
|
writeDg.add(deleg)
|
||||||
|
if deleg.open:
|
||||||
|
errorDg.add(deleg)
|
||||||
|
inc dc
|
||||||
|
else:
|
||||||
|
# File/socket has been closed. Remove it from dispatcher.
|
||||||
|
d.delegates[dc] = d.delegates[len-1]
|
||||||
|
dec len
|
||||||
|
d.delegates.setLen(len)
|
||||||
|
|
||||||
|
var hasDataBufferedCount = 0
|
||||||
|
for d in d.delegates:
|
||||||
|
if d.hasDataBuffered(d.deleVal):
|
||||||
|
hasDataBufferedCount.inc()
|
||||||
|
d.handleRead(d.deleVal)
|
||||||
|
if hasDataBufferedCount > 0: return True
|
||||||
|
|
||||||
|
if readDg.len() == 0 and writeDg.len() == 0:
|
||||||
|
## TODO: Perhaps this shouldn't return if errorDg has something?
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if select(readSocks, writeSocks, timeout) != 0:
|
if select(readDg, writeDg, errorDg, timeout) != 0:
|
||||||
for i in 0..len(d.delegates)-1:
|
for i in 0..len(d.delegates)-1:
|
||||||
if i > len(d.delegates)-1: break # One delegate might've been removed.
|
if i > len(d.delegates)-1: break # One delegate might've been removed.
|
||||||
let deleg = d.delegates[i]
|
let deleg = d.delegates[i]
|
||||||
let sock = deleg.getSocket(deleg.deleVal)
|
if (deleg.mode != fmWrite or deleg.mode != fmAppend) and
|
||||||
if sock.info == SockConnected or
|
deleg notin readDg:
|
||||||
sock.info == SockUDPBound:
|
deleg.handleRead(deleg.deleVal)
|
||||||
if deleg.mode != MWriteable and sock.sock notin readSocks:
|
if (deleg.mode != fmRead) and deleg notin writeDg:
|
||||||
if not (sock.info == SockConnecting):
|
deleg.handleWrite(deleg.deleVal)
|
||||||
assert(not (sock.info == SockListening))
|
if deleg notin errorDg:
|
||||||
deleg.handleRead(deleg.deleVal)
|
deleg.handleError(deleg.deleVal)
|
||||||
else:
|
|
||||||
assert(false)
|
|
||||||
if deleg.mode != MReadable and sock.sock notin writeSocks:
|
|
||||||
deleg.handleWrite(deleg.deleVal)
|
|
||||||
|
|
||||||
if sock.info == SockListening:
|
|
||||||
if sock.sock notin readSocks:
|
|
||||||
# This is a server socket, that had listen() called on it.
|
|
||||||
# This socket should have a client waiting now.
|
|
||||||
deleg.handleAccept(deleg.deleVal)
|
|
||||||
|
|
||||||
if sock.info == SockConnecting:
|
|
||||||
# Checking whether the socket has connected this way should work on
|
|
||||||
# Windows and Posix. I've checked.
|
|
||||||
if sock.sock notin writeSocks:
|
|
||||||
deleg.handleConnect(deleg.deleVal)
|
|
||||||
|
|
||||||
# Execute tasks
|
# Execute tasks
|
||||||
for i in items(d.delegates):
|
for i in items(d.delegates):
|
||||||
|
|
|
||||||
216
lib/pure/fsmonitor.nim
Normal file
216
lib/pure/fsmonitor.nim
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Nimrod's Runtime Library
|
||||||
|
# (c) Copyright 2012 Dominik Picheta
|
||||||
|
#
|
||||||
|
# See the file "copying.txt", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
## This module allows you to monitor files or directories for changes using
|
||||||
|
## asyncio.
|
||||||
|
##
|
||||||
|
## Windows support is not yet implemented.
|
||||||
|
##
|
||||||
|
## **Note:** This module uses ``inotify`` on Linux (Other Unixes are not yet
|
||||||
|
## supported). ``inotify`` was merged into the 2.6.13 Linux kernel, this
|
||||||
|
## module will therefore not work with any Linux kernel prior to that, unless
|
||||||
|
## it has been patched to support inotify.
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
{.error: "Windows is not yet supported by this module.".}
|
||||||
|
elif defined(linux):
|
||||||
|
from posix import read
|
||||||
|
else:
|
||||||
|
{.error: "Your platform is not supported.".}
|
||||||
|
|
||||||
|
import inotify, os, asyncio, tables
|
||||||
|
|
||||||
|
type
|
||||||
|
PFSMonitor* = ref TFSMonitor
|
||||||
|
TFSMonitor = object of TObject
|
||||||
|
fd: cint
|
||||||
|
handleEvent: proc (m: PFSMonitor, ev: TMonitorEvent) {.closure.}
|
||||||
|
targets: TTable[cint, string]
|
||||||
|
|
||||||
|
TMonitorEventType* = enum ## Monitor event type
|
||||||
|
MonitorAccess, ## File was accessed.
|
||||||
|
MonitorAttrib, ## Metadata changed.
|
||||||
|
MonitorCloseWrite, ## Writtable file was closed.
|
||||||
|
MonitorCloseNoWrite, ## Unwrittable file closed.
|
||||||
|
MonitorCreate, ## Subfile was created.
|
||||||
|
MonitorDelete, ## Subfile was deleted.
|
||||||
|
MonitorDeleteSelf, ## Watched file/directory was itself deleted.
|
||||||
|
MonitorModify, ## File was modified.
|
||||||
|
MonitorMoveSelf, ## Self was moved.
|
||||||
|
MonitorMoved, ## File was moved.
|
||||||
|
MonitorOpen, ## File was opened.
|
||||||
|
MonitorAll ## Filter for all event types.
|
||||||
|
|
||||||
|
TMonitorEvent* = object
|
||||||
|
case kind*: TMonitorEventType ## Type of the event.
|
||||||
|
of MonitorMoveSelf, MonitorMoved:
|
||||||
|
oldPath*: string ## Old absolute location
|
||||||
|
newPath*: string ## New absolute location
|
||||||
|
else:
|
||||||
|
fullname*: string ## Absolute filename of the file/directory affected.
|
||||||
|
name*: string ## Non absolute filepath of the file/directory
|
||||||
|
## affected relative to the directory watched.
|
||||||
|
## "" if this event refers to the file/directory
|
||||||
|
## watched.
|
||||||
|
wd*: cint ## Watch descriptor.
|
||||||
|
|
||||||
|
const
|
||||||
|
MaxEvents = 100
|
||||||
|
|
||||||
|
proc newMonitor*(): PFSMonitor =
|
||||||
|
## Creates a new file system monitor.
|
||||||
|
new(result)
|
||||||
|
result.fd = inotifyInit()
|
||||||
|
result.targets = initTable[cint, string]()
|
||||||
|
if result.fd < 0:
|
||||||
|
OSError()
|
||||||
|
|
||||||
|
proc add*(monitor: PFSMonitor, target: string,
|
||||||
|
filters = {MonitorAll}): cint {.discardable.} =
|
||||||
|
## Adds ``target`` which may be a directory or a file to the list of
|
||||||
|
## watched paths of ``monitor``.
|
||||||
|
## You can specify the events to report using the ``filters`` parameter.
|
||||||
|
|
||||||
|
var INFilter = -1
|
||||||
|
for f in filters:
|
||||||
|
case f
|
||||||
|
of MonitorAccess: INFilter = INFilter and IN_ACCESS
|
||||||
|
of MonitorAttrib: INFilter = INFilter and IN_ATTRIB
|
||||||
|
of MonitorCloseWrite: INFilter = INFilter and IN_CLOSE_WRITE
|
||||||
|
of MonitorCloseNoWrite: INFilter = INFilter and IN_CLOSE_NO_WRITE
|
||||||
|
of MonitorCreate: INFilter = INFilter and IN_CREATE
|
||||||
|
of MonitorDelete: INFilter = INFilter and IN_DELETE
|
||||||
|
of MonitorDeleteSelf: INFilter = INFilter and IN_DELETE_SELF
|
||||||
|
of MonitorModify: INFilter = INFilter and IN_MODIFY
|
||||||
|
of MonitorMoveSelf: INFilter = INFilter and IN_MOVE_SELF
|
||||||
|
of MonitorMoved: INFilter = INFilter and IN_MOVED_FROM and IN_MOVED_TO
|
||||||
|
of MonitorOpen: INFilter = INFilter and IN_OPEN
|
||||||
|
of MonitorAll: INFilter = INFilter and IN_ALL_EVENTS
|
||||||
|
|
||||||
|
result = inotifyAddWatch(monitor.fd, target, INFilter.uint32)
|
||||||
|
if result < 0:
|
||||||
|
OSError()
|
||||||
|
monitor.targets.add(result, target)
|
||||||
|
|
||||||
|
proc del*(monitor: PFSMonitor, wd: cint) =
|
||||||
|
## Removes watched directory or file as specified by ``wd`` from ``monitor``.
|
||||||
|
##
|
||||||
|
## If ``wd`` is not a part of ``monitor`` an EOS error is raised.
|
||||||
|
if inotifyRmWatch(monitor.fd, wd) < 0:
|
||||||
|
OSError()
|
||||||
|
|
||||||
|
proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] =
|
||||||
|
result = @[]
|
||||||
|
let size = (sizeof(TINotifyEvent)+2000)*MaxEvents
|
||||||
|
var buffer = newString(size)
|
||||||
|
|
||||||
|
let le = read(fd, addr(buffer[0]), size)
|
||||||
|
|
||||||
|
var movedFrom: TTable[cint, tuple[wd: cint, old: string]] =
|
||||||
|
initTable[cint, tuple[wd: cint, old: string]]()
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while i < le:
|
||||||
|
var event = cast[ptr TINotifyEvent](addr(buffer[i]))
|
||||||
|
var mev: TMonitorEvent
|
||||||
|
mev.wd = event.wd
|
||||||
|
if event.len.int != 0:
|
||||||
|
mev.name = newString(event.len.int)
|
||||||
|
copyMem(addr(mev.name[0]), addr event.name, event.len.int-1)
|
||||||
|
else:
|
||||||
|
mev.name = ""
|
||||||
|
|
||||||
|
if (event.mask.int and IN_MOVED_FROM) != 0:
|
||||||
|
# Moved from event, add to m's collection
|
||||||
|
movedFrom.add(event.cookie.cint, (mev.wd, mev.name))
|
||||||
|
inc(i, sizeof(TINotifyEvent) + event.len.int)
|
||||||
|
continue
|
||||||
|
elif (event.mask.int and IN_MOVED_TO) != 0:
|
||||||
|
mev.kind = MonitorMoved
|
||||||
|
assert movedFrom.hasKey(event.cookie.cint)
|
||||||
|
# Find the MovedFrom event.
|
||||||
|
mev.oldPath = movedFrom[event.cookie.cint].old
|
||||||
|
mev.newPath = "" # Set later
|
||||||
|
# Delete it from the TTable
|
||||||
|
movedFrom.del(event.cookie.cint)
|
||||||
|
elif (event.mask.int and IN_ACCESS) != 0: mev.kind = MonitorAccess
|
||||||
|
elif (event.mask.int and IN_ATTRIB) != 0: mev.kind = MonitorAttrib
|
||||||
|
elif (event.mask.int and IN_CLOSE_WRITE) != 0:
|
||||||
|
mev.kind = MonitorCloseWrite
|
||||||
|
elif (event.mask.int and IN_CLOSE_NOWRITE) != 0:
|
||||||
|
mev.kind = MonitorCloseNoWrite
|
||||||
|
elif (event.mask.int and IN_CREATE) != 0: mev.kind = MonitorCreate
|
||||||
|
elif (event.mask.int and IN_DELETE) != 0:
|
||||||
|
mev.kind = MonitorDelete
|
||||||
|
elif (event.mask.int and IN_DELETE_SELF) != 0:
|
||||||
|
mev.kind = MonitorDeleteSelf
|
||||||
|
elif (event.mask.int and IN_MODIFY) != 0: mev.kind = MonitorModify
|
||||||
|
elif (event.mask.int and IN_MOVE_SELF) != 0:
|
||||||
|
mev.kind = MonitorMoveSelf
|
||||||
|
elif (event.mask.int and IN_OPEN) != 0: mev.kind = MonitorOpen
|
||||||
|
|
||||||
|
if mev.kind != MonitorMoved:
|
||||||
|
mev.fullname = ""
|
||||||
|
|
||||||
|
result.add(mev)
|
||||||
|
inc(i, sizeof(TINotifyEvent) + event.len.int)
|
||||||
|
|
||||||
|
# If movedFrom events have not been matched with a moveTo. File has
|
||||||
|
# been moved to an unwatched location, emit a MonitorDelete.
|
||||||
|
for cookie, t in pairs(movedFrom):
|
||||||
|
var mev: TMonitorEvent
|
||||||
|
mev.kind = MonitorDelete
|
||||||
|
mev.wd = t.wd
|
||||||
|
mev.name = t.old
|
||||||
|
result.add(mev)
|
||||||
|
|
||||||
|
proc FSMonitorRead(h: PObject) =
|
||||||
|
var events = PFSMonitor(h).getEvent(PFSMonitor(h).fd)
|
||||||
|
#var newEv: TMonitorEvent
|
||||||
|
for ev in events:
|
||||||
|
var target = PFSMonitor(h).targets[ev.wd]
|
||||||
|
var newEv = ev
|
||||||
|
if newEv.kind == MonitorMoved:
|
||||||
|
newEv.oldPath = target / newEv.oldPath
|
||||||
|
newEv.newPath = target / newEv.name
|
||||||
|
else:
|
||||||
|
newEv.fullName = target / newEv.name
|
||||||
|
PFSMonitor(h).handleEvent(PFSMonitor(h), newEv)
|
||||||
|
|
||||||
|
proc toDelegate(m: PFSMonitor): PDelegate =
|
||||||
|
result = newDelegate()
|
||||||
|
result.deleVal = m
|
||||||
|
result.fd = m.fd
|
||||||
|
result.mode = fmRead
|
||||||
|
result.handleRead = FSMonitorRead
|
||||||
|
result.open = true
|
||||||
|
|
||||||
|
proc register*(d: PDispatcher, monitor: PFSMonitor,
|
||||||
|
handleEvent: proc (m: PFSMonitor, ev: TMonitorEvent) {.closure.}) =
|
||||||
|
## Registers ``monitor`` with dispatcher ``d``.
|
||||||
|
monitor.handleEvent = handleEvent
|
||||||
|
var deleg = toDelegate(monitor)
|
||||||
|
d.register(deleg)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var disp = newDispatcher()
|
||||||
|
var monitor = newMonitor()
|
||||||
|
echo monitor.add("/home/dom/inotifytests/")
|
||||||
|
disp.register(monitor,
|
||||||
|
proc (m: PFSMonitor, ev: TMonitorEvent) =
|
||||||
|
echo("Got event: ", ev.kind)
|
||||||
|
if ev.kind == MonitorMoved:
|
||||||
|
echo("From ", ev.oldPath, " to ", ev.newPath)
|
||||||
|
echo("Name is ", ev.name)
|
||||||
|
else:
|
||||||
|
echo("Name ", ev.name, " fullname ", ev.fullName))
|
||||||
|
|
||||||
|
while true:
|
||||||
|
if not disp.poll(): break
|
||||||
|
|
||||||
|
|
@ -711,7 +711,7 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0),
|
||||||
## A variant of ``connect`` for non-blocking sockets.
|
## A variant of ``connect`` for non-blocking sockets.
|
||||||
##
|
##
|
||||||
## This procedure will immediatelly return, it will not block until a connection
|
## This procedure will immediatelly return, it will not block until a connection
|
||||||
## is made. It is up to the caller to make sure the connections has been established
|
## is made. It is up to the caller to make sure the connection has been established
|
||||||
## by checking (using ``select``) whether the socket is writeable.
|
## by checking (using ``select``) whether the socket is writeable.
|
||||||
##
|
##
|
||||||
## **Note**: For SSL sockets, the ``handshake`` procedure must be called
|
## **Note**: For SSL sockets, the ``handshake`` procedure must be called
|
||||||
|
|
@ -820,6 +820,12 @@ proc pruneSocketSet(s: var seq[TSocket], fd: var TFdSet) =
|
||||||
inc(i)
|
inc(i)
|
||||||
setLen(s, L)
|
setLen(s, L)
|
||||||
|
|
||||||
|
proc hasDataBuffered*(s: TSocket): bool =
|
||||||
|
## Determines whether a socket has data buffered.
|
||||||
|
result = false
|
||||||
|
if s.isBuffered:
|
||||||
|
result = s.bufLen > 0 and s.currPos != s.bufLen
|
||||||
|
|
||||||
proc checkBuffer(readfds: var seq[TSocket]): int =
|
proc checkBuffer(readfds: var seq[TSocket]): int =
|
||||||
## Checks the buffer of each socket in ``readfds`` to see whether there is data.
|
## Checks the buffer of each socket in ``readfds`` to see whether there is data.
|
||||||
## Removes the sockets from ``readfds`` and returns the count of removed sockets.
|
## Removes the sockets from ``readfds`` and returns the count of removed sockets.
|
||||||
|
|
@ -1385,6 +1391,9 @@ proc connect*(socket: TSocket, timeout: int, name: string, port = TPort(0),
|
||||||
proc isSSL*(socket: TSocket): bool = return socket.isSSL
|
proc isSSL*(socket: TSocket): bool = return socket.isSSL
|
||||||
## Determines whether ``socket`` is a SSL socket.
|
## Determines whether ``socket`` is a SSL socket.
|
||||||
|
|
||||||
|
proc getFD*(socket: TSocket): cint = return socket.fd
|
||||||
|
## Returns the socket's file descriptor
|
||||||
|
|
||||||
when defined(Windows):
|
when defined(Windows):
|
||||||
var wsa: TWSADATA
|
var wsa: TWSADATA
|
||||||
if WSAStartup(0x0101'i16, wsa) != 0: OSError()
|
if WSAStartup(0x0101'i16, wsa) != 0: OSError()
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ Library Additions
|
||||||
some operation.
|
some operation.
|
||||||
- Added ``strutils.format``, ``subexes.format`` which use the
|
- Added ``strutils.format``, ``subexes.format`` which use the
|
||||||
new ``varargs`` type.
|
new ``varargs`` type.
|
||||||
|
- Added module ``fsmonitor``.
|
||||||
|
|
||||||
Changes affecting backwards compatibility
|
Changes affecting backwards compatibility
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ srcdoc: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql"
|
||||||
srcdoc: "pure/streams;pure/terminal;pure/cgi;impure/web;pure/unicode"
|
srcdoc: "pure/streams;pure/terminal;pure/cgi;impure/web;pure/unicode"
|
||||||
srcdoc: "impure/zipfiles;pure/htmlgen;pure/parseutils;pure/browsers"
|
srcdoc: "impure/zipfiles;pure/htmlgen;pure/parseutils;pure/browsers"
|
||||||
srcdoc: "impure/db_postgres;impure/db_mysql;impure/db_sqlite;impure/db_mongo"
|
srcdoc: "impure/db_postgres;impure/db_mysql;impure/db_sqlite;impure/db_mongo"
|
||||||
srcdoc: "pure/httpserver;pure/httpclient;pure/smtp;impure/ssl"
|
srcdoc: "pure/httpserver;pure/httpclient;pure/smtp;impure/ssl;pure/fsmonitor"
|
||||||
srcdoc: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser"
|
srcdoc: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser"
|
||||||
srcdoc: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes"
|
srcdoc: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes"
|
||||||
srcdoc: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics"
|
srcdoc: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue