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"