summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-09-03 00:56:06 +0200
committerAraq <rumpf_a@web.de>2012-09-03 00:56:06 +0200
commitb4cd119800c96361ff74ab804002c32d303233fc (patch)
tree210b0fbd09ad95696c5f37f74c93c9af117009eb
parentaf7c92c0038763db2ba7d7049d7d18363b15089e (diff)
parent223b4f45ed0b6ebd9c98906a5f2487b5baa48028 (diff)
downloadNim-b4cd119800c96361ff74ab804002c32d303233fc.tar.gz
Merge branch 'master' of github.com:Araq/Nimrod
-rwxr-xr-xdoc/lib.txt3
-rw-r--r--lib/posix/inotify.nim70
-rw-r--r--lib/pure/asyncio.nim232
-rw-r--r--lib/pure/fsmonitor.nim216
-rwxr-xr-xlib/pure/sockets.nim11
-rwxr-xr-xweb/news.txt2
-rwxr-xr-xweb/nimrod.ini2
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"
pan class='oid'>a654e4ec ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204