Win32-notify-0.2: src/System/Win32/FileNotify.hsc
{-# LANGUAGE ForeignFunctionInterface #-}
#if __GLASGOW_HASKELL__ >= 701
{-# LANGUAGE InterruptibleFFI #-}
#endif
module System.Win32.FileNotify
( Handle
, Action(..)
, getWatchHandle
, readDirectoryChanges
) where
import System.Win32.File
import System.Win32.Types
import Foreign
import Foreign.C
import Data.Bits
#include <windows.h>
type Handle = HANDLE
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
-}
-- The interruptible qualifier will keep threads listening for events from hanging blocking when killed
#if __GLASGOW_HASKELL__ >= 701
foreign import stdcall interruptible "windows.h ReadDirectoryChangesW"
#else
foreign import stdcall safe "windows.h ReadDirectoryChangesW"
#endif
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
}