packages feed

Win32-notify (empty) → 0.1

raw patch · 5 files changed

+282/−0 lines, 5 filesdep +Win32dep +basedep +directorysetup-changed

Dependencies added: Win32, base, directory

Files

+ LICENSE view
@@ -0,0 +1,28 @@+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Win32/FileNotify.hsc view
@@ -0,0 +1,133 @@+module System.Win32.FileNotify where
+
+import System.Win32.File
+import System.Win32.Types
+
+import Foreign
+import Foreign.C
+
+import Data.Bits
+
+
+#include <windows.h>
+
+getWatchHandle :: FilePath -> IO HANDLE
+getWatchHandle dir = 
+    createFile dir 
+        fILE_LIST_DIRECTORY -- Access mode 
+        (fILE_SHARE_READ .|. fILE_SHARE_WRITE) -- Share mode
+        Nothing -- security attributes
+        oPEN_EXISTING -- Create mode, we want to look at an existing directory
+        fILE_FLAG_BACKUP_SEMANTICS -- File attribute, nb NOT using OVERLAPPED since we work synchronously
+        Nothing -- No template file
+
+
+readDirectoryChanges :: HANDLE -> Bool -> FileNotificationFlag -> IO [(Action, String)]
+readDirectoryChanges h wst mask = do
+    let maxBuf = 16384
+    allocaBytes maxBuf $ \buffer -> do
+    alloca $ \bret -> do
+        readDirectoryChangesW h buffer (toEnum maxBuf) wst mask bret
+        readChanges buffer
+
+data Action = FileAdded | FileRemoved | FileModified | FileRenamedOld | FileRenamedNew
+  deriving (Show, Read, Eq, Ord, Enum)
+
+readChanges :: Ptr FILE_NOTIFY_INFORMATION -> IO [(Action, String)]
+readChanges pfni = do 
+    fni <- peekFNI pfni
+    let entry = (faToAction $ fniAction fni, fniFileName fni)
+        nioff = fromEnum $ fniNextEntryOffset fni
+    entries <- if nioff == 0 then return [] else readChanges $ pfni `plusPtr` nioff
+    return $ entry:entries
+
+faToAction :: FileAction -> Action
+faToAction fa = toEnum $ fromEnum fa - 1
+
+-------------------------------------------------------------------
+-- Low-level stuff that binds to notifications in the Win32 API
+
+-- Defined in System.Win32.File, but with too few cases:
+-- type AccessMode = UINT
+
+#{enum AccessMode,
+ , fILE_LIST_DIRECTORY = FILE_LIST_DIRECTORY
+ }
+-- there are many more cases but I only need this one.
+
+type FileAction = DWORD
+
+#{enum FileAction,
+ , fILE_ACTION_ADDED            = FILE_ACTION_ADDED
+ , fILE_ACTION_REMOVED          = FILE_ACTION_REMOVED
+ , fILE_ACTION_MODIFIED         = FILE_ACTION_MODIFIED
+ , fILE_ACTION_RENAMED_OLD_NAME = FILE_ACTION_RENAMED_OLD_NAME
+ , fILE_ACTION_RENAMED_NEW_NAME = FILE_ACTION_RENAMED_NEW_NAME
+ }
+
+type WCHAR = Word16
+-- This is a bit overkill for now, I'll only use nullFunPtr anyway, 
+-- but who knows, maybe someday I'll want asynchronous callbacks on the OS level.
+type LPOVERLAPPED_COMPLETION_ROUTINE = FunPtr ((DWORD, DWORD, LPOVERLAPPED) -> IO ())
+
+data FILE_NOTIFY_INFORMATION = FILE_NOTIFY_INFORMATION
+    { fniNextEntryOffset, fniAction :: DWORD
+    , fniFileName :: String
+    }
+
+-- instance Storable FILE_NOTIFY_INFORMATION where
+-- ... well, we can't write an instance since the struct is not of fix size,
+-- so we'll have to do it the hard way, and not get anything for free. Sigh.
+
+-- sizeOfFNI :: FILE_NOTIFY_INFORMATION -> Int
+-- sizeOfFNI fni =  (#size FILE_NOTIFY_INFORMATION) + (#size WCHAR) * (length (fniFileName fni) - 1)
+
+peekFNI :: Ptr FILE_NOTIFY_INFORMATION -> IO FILE_NOTIFY_INFORMATION
+peekFNI buf = do
+    neof <- (#peek FILE_NOTIFY_INFORMATION, NextEntryOffset)        buf
+    acti <- (#peek FILE_NOTIFY_INFORMATION, Action)                 buf
+    fnle <- (#peek FILE_NOTIFY_INFORMATION, FileNameLength)         buf
+    fnam <- peekCWStringLen 
+                (buf `plusPtr` (#offset FILE_NOTIFY_INFORMATION, FileName), -- start of array
+                fromEnum (fnle :: DWORD) `div` 2 ) -- fnle is the length in *bytes*, and a WCHAR is 2 bytes
+    return $ FILE_NOTIFY_INFORMATION neof acti fnam
+
+
+readDirectoryChangesW :: HANDLE -> Ptr FILE_NOTIFY_INFORMATION -> DWORD -> BOOL -> FileNotificationFlag -> LPDWORD -> IO ()
+readDirectoryChangesW h buf bufSize wst f br = 
+  failIfFalse_ "ReadDirectoryChangesW" $ c_ReadDirectoryChangesW h (castPtr buf) bufSize wst f br nullPtr nullFunPtr
+
+{-
+asynchReadDirectoryChangesW :: HANDLE -> Ptr FILE_NOTIFY_INFORMATION -> DWORD -> BOOL -> FileNotificationFlag 
+                                -> LPOVERLAPPED -> IO ()
+asynchReadDirectoryChangesW h buf bufSize wst f over =
+  failIfFalse_ "ReadDirectoryChangesW" $ c_ReadDirectoryChangesW h (castPtr buf) bufSize wst f nullPtr over nullFunPtr
+
+cbReadDirectoryChangesW :: HANDLE -> Ptr FILE_NOTIFY_INFORMATION -> DWORD -> BOOL -> FileNotificationFlag 
+                                -> LPOVERLAPPED -> IO BOOL
+cbReadDirectoryChanges
+-}
+foreign import stdcall safe "windows.h ReadDirectoryChangesW"
+  c_ReadDirectoryChangesW :: HANDLE -> LPVOID -> DWORD -> BOOL -> DWORD 
+                                -> LPDWORD -> LPOVERLAPPED -> LPOVERLAPPED_COMPLETION_ROUTINE -> IO BOOL
+
+{-
+type CompletionRoutine :: (DWORD, DWORD, LPOVERLAPPED) -> IO ()
+foreign import ccall "wrapper" 
+    mkCompletionRoutine :: CompletionRoutine -> IO (FunPtr CompletionRoutine)
+
+type LPOVERLAPPED = Ptr OVERLAPPED
+type LPOVERLAPPED_COMPLETION_ROUTINE = FunPtr CompletionRoutine
+
+data OVERLAPPED = OVERLAPPED
+    {
+    }
+
+
+-- In System.Win32.File, but missing a crucial case:
+-- type FileNotificationFlag = DWORD
+-}
+#{enum FileNotificationFlag,
+ , fILE_NOTIFY_CHANGE_CREATION    = FILE_NOTIFY_CHANGE_CREATION
+ , fILE_NOTIFY_CHANGE_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS
+ }
+ System/Win32/Notify.hs view
@@ -0,0 +1,101 @@+module System.Win32.Notify (
+
+      watchDirectoryOnce    -- FilePath -> Bool -> [EventVariety] -> IO Event
+    , watchDirectory        -- FilePath -> Bool -> [EventVariety] -> IO [Event]
+    
+    , Event(..)
+    , EventVariety(..)
+
+    ) where
+
+import System.Win32.FileNotify
+import System.Win32.File
+
+import Data.Bits
+import System.Directory
+import Control.Concurrent
+
+import System.IO.Unsafe (unsafeInterleaveIO) 
+
+import Debug.Trace
+
+{-
+addDirWatch :: FilePath -> Bool -> [EventVariety] -> (Event -> IO ()) -> IO ()
+addDirWatch dir wst evs cb = trace "addDirWatch" (forkIO loop) >> trace "addDirWatch: forked" (return ())
+  where loop = do
+            trace "addDirWatch: start loop" $ return ()
+            e <- watchDirectory dir wst evs
+            trace "addDirWatch: watchDirectory returned" $ return ()
+            forkIO $ cb e
+            trace "addDirWatch: callback forked" $ return ()
+-}
+
+data EventVariety
+    = Modify
+    | Move
+    | Create
+    | Delete
+  deriving Eq
+  
+
+evToFnFlag :: [EventVariety] -> FileNotificationFlag 
+evToFnFlag = foldl (.|.) 0 . map evToFnFlag' 
+    where evToFnFlag' :: EventVariety -> FileNotificationFlag
+          evToFnFlag' ev = case ev of
+                            Modify  -> fILE_NOTIFY_CHANGE_LAST_WRITE
+                            Move    -> fILE_NOTIFY_CHANGE_FILE_NAME .|. fILE_NOTIFY_CHANGE_DIR_NAME
+                            Create  -> fILE_NOTIFY_CHANGE_FILE_NAME .|. fILE_NOTIFY_CHANGE_DIR_NAME
+                            Delete  -> fILE_NOTIFY_CHANGE_FILE_NAME .|. fILE_NOTIFY_CHANGE_DIR_NAME
+
+watchDirectoryOnce :: FilePath -> Bool -> [EventVariety] -> IO Event
+watchDirectoryOnce dir wst evs = do
+        h <- getWatchHandle dir
+        readDirectoryChanges h wst (evToFnFlag evs) >>= actsToEvent
+
+watchDirectory :: FilePath -> Bool -> [EventVariety] -> IO [Event]
+watchDirectory dir wst evs = do
+        h <- getWatchHandle dir
+        loop h
+    where loop h = do e <- readDirectoryChanges h wst (evToFnFlag evs) >>= actsToEvent 
+                      es <- unsafeInterleaveIO $ loop h
+                      return $ e:es
+
+
+
+
+actsToEvent :: [(Action, String)] -> IO Event
+actsToEvent [] = error "The impossible happened - there was no event!"
+actsToEvent [(act, fn)] = do 
+    isDir <- doesDirectoryExist fn
+    case act of
+        FileModified    -> return $ Modified isDir (Just fn)
+        FileAdded       -> return $ Created isDir fn
+        FileRemoved     -> return $ Deleted isDir fn
+        FileRenamedOld  -> return $ Renamed isDir Nothing fn
+actsToEvent [(FileRenamedOld, fnold),(FileRenamedNew, fnnew)] = do
+    isDir <- doesDirectoryExist fnnew
+    return $ Renamed isDir (Just fnold) fnnew
+
+data Event
+    -- | A file was modified. @Modified isDirectory file@
+    = Modified
+        { isDirectory :: Bool
+        , maybeFilePath :: Maybe FilePath
+        }
+    -- | A file was moved within the directory.
+    | Renamed
+        { isDirectory :: Bool
+        , oldName     :: Maybe FilePath
+        , newName     :: FilePath
+        }
+    -- | A file was created. @Created isDirectory file@
+    | Created
+        { isDirectory :: Bool
+        , filePath :: FilePath
+        }
+    -- | A file was deleted. @Deleted isDirectory file@
+    | Deleted
+        { isDirectory :: Bool
+        , filePath :: FilePath
+        }
+    deriving (Eq, Show)
+ Win32-notify.cabal view
@@ -0,0 +1,18 @@+name:           Win32-notify
+version:        0.1
+license:        BSD3
+license-file:   LICENSE
+author:         Niklas Broberg
+copyright:      Niklas Broberg, 2008
+maintainer:     Niklas Broberg <nibro@cs.chalmers.se>
+category:       System
+synopsis:       A binding to part of the Win32 library for file notification
+description:    A binding to part of the Win32 library for file notification
+build-type:     Simple
+build-depends:  base, Win32, directory
+ghc-options:    -Wall -fno-warn-incomplete-patterns -fno-warn-unused-imports
+extensions:     ForeignFunctionInterface
+
+exposed-modules:        System.Win32.Notify
+other-modules:          System.Win32.FileNotify
+