diff options
author | Araq <rumpf_a@web.de> | 2012-09-03 00:56:06 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2012-09-03 00:56:06 +0200 |
commit | b4cd119800c96361ff74ab804002c32d303233fc (patch) | |
tree | 210b0fbd09ad95696c5f37f74c93c9af117009eb | |
parent | af7c92c0038763db2ba7d7049d7d18363b15089e (diff) | |
parent | 223b4f45ed0b6ebd9c98906a5f2487b5baa48028 (diff) | |
download | Nim-b4cd119800c96361ff74ab804002c32d303233fc.tar.gz |
Merge branch 'master' of github.com:Araq/Nimrod
-rwxr-xr-x | doc/lib.txt | 3 | ||||
-rw-r--r-- | lib/posix/inotify.nim | 70 | ||||
-rw-r--r-- | lib/pure/asyncio.nim | 232 | ||||
-rw-r--r-- | lib/pure/fsmonitor.nim | 216 | ||||
-rwxr-xr-x | lib/pure/sockets.nim | 11 | ||||
-rwxr-xr-x | web/news.txt | 2 | ||||
-rwxr-xr-x | web/nimrod.ini | 2 |
7 files changed, 439 insertions, 97 deletions
diff --git a/doc/lib.txt b/doc/lib.txt index f5bef435c..f4d3dde30 100755 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -160,6 +160,9 @@ Generic Operating System Services This module provides support for memory mapped files (Posix's ``mmap``) 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 -------------- diff --git a/lib/posix/inotify.nim b/lib/posix/inotify.nim new file mode 100644 index 000000000..28dcd652f --- /dev/null +++ b/lib/posix/inotify.nim @@ -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>".} \ No newline at end of file diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 113b1d080..653aa38b3 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -70,22 +70,24 @@ import sockets, os ## the socket has established a connection to a server socket; from that point ## 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 - - TDelegate = object + TDelegate* = object + fd*: cint deleVal*: PObject handleRead*: proc (h: PObject) {.nimcall.} handleWrite*: proc (h: PObject) {.nimcall.} - handleConnect*: proc (h: PObject) {.nimcall.} - - handleAccept*: proc (h: PObject) {.nimcall.} - getSocket*: proc (h: PObject): tuple[info: TInfo, sock: TSocket] {.nimcall.} - + handleError*: proc (h: PObject) {.nimcall.} + hasDataBuffered*: proc (h: PObject): bool {.nimcall.} + + open*: bool task*: proc (h: PObject) {.nimcall.} - mode*: TMode + mode*: TFileMode PDelegate* = ref TDelegate @@ -106,24 +108,20 @@ type lineBuffer: TaintedString ## Temporary storage for ``recvLine`` sslNeedAccept: bool proto: TProtocol + deleg: PDelegate - TInfo* = enum + TInfo = enum SockIdle, SockConnecting, SockConnected, SockListening, SockClosed, SockUDPBound - - TMode* = enum - MReadable, MWriteable, MReadWrite proc newDelegate*(): PDelegate = ## Creates a new delegate. new(result) result.handleRead = (proc (h: PObject) = nil) result.handleWrite = (proc (h: PObject) = nil) - result.handleConnect = (proc (h: PObject) = nil) - result.handleAccept = (proc (h: PObject) = nil) - result.getSocket = (proc (h: PObject): tuple[info: TInfo, sock: TSocket] = - doAssert(false)) + result.handleError = (proc (h: PObject) = nil) + result.hasDataBuffered = (proc (h: PObject): bool = return false) result.task = (proc (h: PObject) = nil) - result.mode = MReadable + result.mode = fmRead proc newAsyncSocket(): PAsyncSocket = new(result) @@ -144,21 +142,28 @@ proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, if result.socket == InvalidSocket: OSError() result.socket.setBlocking(false) -proc asyncSockHandleConnect(h: PObject) = +proc asyncSockHandleRead(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)) + return -proc asyncSockHandleRead(h: PObject) = + 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 - PAsyncSocket(h).handleRead(PAsyncSocket(h)) + + if PAsyncSocket(h).info == SockConnecting: + PAsyncSocket(h).handleConnect(PAsyncSocket(h)) + # Stop receiving write events + PAsyncSocket(h).deleg.mode = fmRead when defined(ssl): proc asyncSockDoHandshake(h: PObject) = @@ -173,19 +178,27 @@ when defined(ssl): else: # handshake will set socket's ``sslNoHandshake`` field. discard PAsyncSocket(h).socket.handshake() - + proc toDelegate(sock: PAsyncSocket): PDelegate = result = newDelegate() result.deleVal = sock - result.getSocket = (proc (h: PObject): tuple[info: TInfo, sock: TSocket] = - return (PAsyncSocket(h).info, PAsyncSocket(h).socket)) - - result.handleConnect = asyncSockHandleConnect - + result.fd = getFD(sock.socket) + # We need this to get write events, just to know when the socket connects. + result.mode = fmReadWrite result.handleRead = asyncSockHandleRead - - result.handleAccept = (proc (h: PObject) = - PAsyncSocket(h).handleAccept(PAsyncSocket(h))) + result.handleWrite = asyncSockHandleWrite + # TODO: Errors? + #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): result.task = asyncSockDoHandshake @@ -195,22 +208,26 @@ proc connect*(sock: PAsyncSocket, name: string, port = TPort(0), ## Begins connecting ``sock`` to ``name``:``port``. sock.socket.connectAsync(name, port, af) sock.info = SockConnecting + sock.deleg.open = true proc close*(sock: PAsyncSocket) = ## Closes ``sock``. Terminates any current connections. - sock.info = SockClosed sock.socket.close() + sock.info = SockClosed + sock.deleg.open = false proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") = ## Equivalent to ``sockets.bindAddr``. sock.socket.bindAddr(port, address) if sock.proto == IPPROTO_UDP: sock.info = SockUDPBound + sock.deleg.open = true proc listen*(sock: PAsyncSocket) = ## Equivalent to ``sockets.listen``. sock.socket.listen() sock.info = SockListening + sock.deleg.open = true proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, address: var string) = @@ -245,8 +262,11 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, if c == InvalidSocket: OSError() c.setBlocking(false) # TODO: Needs to be tested. + # deleg.open is set in ``toDelegate``. + client.socket = c client.lineBuffer = "" + client.info = SockConnected proc accept*(server: PAsyncSocket, client: var PAsyncSocket) = ## Equivalent to ``sockets.accept``. @@ -297,9 +317,6 @@ proc isWriteable*(s: PAsyncSocket): bool = var writeSock = @[s.socket] return selectWrite(writeSock, 1) != 0 and s.socket notin writeSock -proc `userArg=`*(s: PAsyncSocket, val: PObject) = - s.userArg = val - converter getSocket*(s: PAsyncSocket): TSocket = return s.socket @@ -338,75 +355,102 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool = of RecvFail: 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 = - ## 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. - ## - ## **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 - ## connected (or connecting), otherwise ``False``. Sockets that have been + ## This function returns ``True`` if there are file descriptors that are still + ## open, otherwise ``False``. File descriptors that have been ## closed are immediately removed from the dispatcher automatically. ## ## **Note:** Each delegate has a task associated with it. This gets called - ## after each select() call, if you make timeout ``-1`` the tasks will - ## only be executed after one or more sockets becomes readable or writeable. - + ## after each select() call, if you set timeout to ``-1`` the tasks will + ## only be executed after one or more file descriptors becomes readable or + ## writeable. result = true - var readSocks, writeSocks: seq[TSocket] = @[] - - var L = d.delegates.len + var readDg, writeDg, errorDg: seq[PDelegate] = @[] + var len = d.delegates.len 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 - - if select(readSocks, writeSocks, timeout) != 0: + + if select(readDg, writeDg, errorDg, timeout) != 0: for i in 0..len(d.delegates)-1: if i > len(d.delegates)-1: break # One delegate might've been removed. let deleg = d.delegates[i] - let sock = deleg.getSocket(deleg.deleVal) - if sock.info == SockConnected or - sock.info == SockUDPBound: - if deleg.mode != MWriteable and sock.sock notin readSocks: - if not (sock.info == SockConnecting): - assert(not (sock.info == SockListening)) - deleg.handleRead(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) + if (deleg.mode != fmWrite or deleg.mode != fmAppend) and + deleg notin readDg: + deleg.handleRead(deleg.deleVal) + if (deleg.mode != fmRead) and deleg notin writeDg: + deleg.handleWrite(deleg.deleVal) + if deleg notin errorDg: + deleg.handleError(deleg.deleVal) # Execute tasks for i in items(d.delegates): diff --git a/lib/pure/fsmonitor.nim b/lib/pure/fsmonitor.nim new file mode 100644 index 000000000..92a80425a --- /dev/null +++ b/lib/pure/fsmonitor.nim @@ -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 + \ No newline at end of file diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index ec1817e72..d0a4c216a 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -711,7 +711,7 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0), ## A variant of ``connect`` for non-blocking sockets. ## ## 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. ## ## **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) 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 = ## 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. @@ -1385,6 +1391,9 @@ proc connect*(socket: TSocket, timeout: int, name: string, port = TPort(0), proc isSSL*(socket: TSocket): bool = return socket.isSSL ## Determines whether ``socket`` is a SSL socket. +proc getFD*(socket: TSocket): cint = return socket.fd + ## Returns the socket's file descriptor + when defined(Windows): var wsa: TWSADATA if WSAStartup(0x0101'i16, wsa) != 0: OSError() diff --git a/web/news.txt b/web/news.txt index 81bf304a5..a9d3336a7 100755 --- a/web/news.txt +++ b/web/news.txt @@ -65,7 +65,7 @@ Library Additions some operation. - Added ``strutils.format``, ``subexes.format`` which use the new ``varargs`` type. - +- Added module ``fsmonitor``. Changes affecting backwards compatibility ----------------------------------------- diff --git a/web/nimrod.ini b/web/nimrod.ini index 10f65308f..e8160146b 100755 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -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: "impure/zipfiles;pure/htmlgen;pure/parseutils;pure/browsers" 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/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes" srcdoc: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics" |