ffcn

ffcn: Implementation of the Win32-API-Call FindFirstChangeNotification()

Notes:

 ################################################################################
 # Modul: ffcn.tcl
 # Stand: 11.03.2004
 # Zweck: Mapping von Win32-API-Calls: 'FindFirstChangeNotification'
 #                                     'FindCloseChangeNotification'
 #                                     'WaitForSingleObject'
 # Autor: (C) M.Hoffmann, März 2004
 # Siehe:
 #  API-Deklaration (Original Win32):
 #   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/findfirstchangenotification.asp
 #  FFIDL:
 #   https://wiki.tcl-lang.org/1197, http://www.elf.org/ffidl/
 #  API-Deklarationen in PB:
 #   DECLARE FUNCTION FindFirstChangeNotification LIB "KERNEL32.DLL" ALIAS
 #    "FindFirstChangeNotificationA" (lpPathName AS ASCIIZ,
 #                                    BYVAL bWatchSubtree AS LONG,
 #                                    BYVAL dwNotifyFilter AS DWORD) AS DWORD
 #   DECLARE FUNCTION FindCloseChangeNotification LIB "KERNEL32.DLL" ALIAS
 #    "FindCloseChangeNotification" (BYVAL hChangeHandle AS DWORD) AS LONG
 #   DECLARE FUNCTION WaitForSingleObject LIB "KERNEL32.DLL" ALIAS
 #    "WaitForSingleObject" (BYVAL hHandle AS DWORD,
 #                           BYVAL dwMilliseconds AS DWORD) AS DWORD
 #     FILE_NOTIFY_CHANGE_FILE_NAME  = 0x00000001 default
 #     FILE_NOTIFY_CHANGE_DIR_NAME   = 0x00000002 default
 #     FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004 default
 #     FILE_NOTIFY_CHANGE_SIZE       = 0x00000008 default
 #     FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010 default
 #     FILE_NOTIFY_CHANGE_LAST_ACCESS= 0x00000020
 #     FILE_NOTIFY_CHANGE_CREATION   = 0x00000040 default
 #     FILE_NOTIFY_CHANGE_SECURITY   = 0x00000100
 #     INVALID_HANDLE_VALUE          = 0xffffffff
 #     INFINITE                      = 0xffffffff
 #  Datentypen:
 #   http://www.a-m-i.de/tips/strings/strings.php
 #   URL homepages.fh-giessen.de/~hg6661/vorlesungen/systemschnittstellen no longer avail
 #          script/win/datentypen.php
 # Offen: Namespace,RoboDOCu,Konstanten implementieren,Catch für ffidl::callout,
 #        Abbruchmöglichkeit,nicht blockierende Variante,gleich die tatsächlichen
 #        Änderungen zurückliefern (glob vorher/nachher;intersect liefern)
 ################################################################################

 package provide ffcn  1.0
 package require Ffidl 0.5

 ffidl::callout dll_ffcn {pointer-utf8 long long} long \
                         [ffidl::symbol kernel32.dll FindFirstChangeNotificationA]
 ffidl::callout dll_fccn {long} long \
                         [ffidl::symbol kernel32.dll FindCloseChangeNotification]
 ffidl::callout dll_wfso {long long} long \
                         [ffidl::symbol kernel32.dll WaitForSingleObject]

 # Dieser Call wartet (wohl blockierend), bis eine Änderung im Verzeichnis auftritt
 proc waitChange {pathName {includeSubDirs 0} {notifyFilter 0x5F}} {
      # Überwachung einleiten
      if {[catch {dll_ffcn $pathName $includeSubDirs $notifyFilter} h] ||
          $h == -1} {
         return -code error "FindFirstChangeNotification() gescheitert ($h)"
      }
      # puts $h
      # Achtung: das folgende blockiert vermutlich Tcl-Eventloop!
      # Überwachung starten (momentan OHNE CATCH)
      set r [dll_wfso $h 0xffffffff]
      # Überwachung beenden (momentan OHNE CATCH)
      set r [dll_fccn $h]
      return {}
 }

 ################################################################################

Example:

 package require ffcn

 # because of the Win32-API-Call waitForSingleObject(), the following call blocks
 # the whole program; I haven't found a solution for this yet...
 # If a file in e:/demodir is created, deleted etc., the call returns:
 #
 waitChange e:/demodir

A 'tcl-only' alternative for tracking directory-changes using a polling-method (so the after ms-Value should not be too small because of cpu-load-aspects!):

 proc watchDirChange {dir intv {script {}} {lastMTime {}}} {
      set nowMTime [file mtime $dir]
      if [string eq $lastMTime ""] {
         set lastMTime $nowMTime
      } elseif {$nowMTime != $lastMTime} {
         # synchronous execution, so no other after event may fire in between
         catch {uplevel #0 $script}
         set lastMTime $nowMTime
      }
      after $intv [list watchDirChange $dir $intv $script $lastMTime]
 }

 watchDirChange e:/work 5000 {
    puts stdout {Directory 'e:/work' changed!}
 }
 vwait forever