hinotify-0.4.2/src/ 0000755 0000000 0000000 00000000000 14755070044 012331 5 ustar 00 0000000 0000000 hinotify-0.4.2/src/System/ 0000755 0000000 0000000 00000000000 14755243757 013632 5 ustar 00 0000000 0000000 hinotify-0.4.2/src/System/INotify/ 0000755 0000000 0000000 00000000000 14755070044 015176 5 ustar 00 0000000 0000000 hinotify-0.4.2/tests/ 0000755 0000000 0000000 00000000000 14755070044 012704 5 ustar 00 0000000 0000000 hinotify-0.4.2/src/System/INotify.hsc 0000644 0000000 0000000 00000027702 14755243757 015722 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.INotify
-- Copyright : (c) Lennart Kolmodin 2006-2012
-- License : BSD3
-- Maintainer : kolmodin@gmail.com
-- Stability : experimental
-- Portability : hc portable, linux only
--
-- A Haskell binding to INotify.
-- See and @man
-- inotify@.
--
-- Use 'initINotify' to get a 'INotify', then use 'addWatch' to
-- add a watch on a file or directory. Select which events you're interested
-- in with 'EventVariety', which corresponds to the 'Event' events.
--
-- Use 'removeWatch' once you don't want to watch a file any more.
--
-----------------------------------------------------------------------------
module System.INotify
( initINotify
, killINotify
, withINotify
, addWatch
, removeWatch
, INotify
, WatchDescriptor
, Event(..)
, EventVariety(..)
, Cookie
) where
#include "sys/inotify.h"
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception as E hiding (mask)
import Control.Monad
import Data.Bits ((.|.))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Foreign.C
import Foreign.Marshal hiding (void)
import Foreign.Ptr
import Foreign.Storable
import GHC.IO.Device (IODeviceType(Stream))
import GHC.IO.FD as FD (mkFD)
import GHC.IO.Handle (mkFileHandle)
import Prelude hiding (init)
import System.INotify.Masks
import System.IO
import System.IO.Error
import System.Posix.ByteString.FilePath
import System.Posix.Files.ByteString
type FD = CInt
type WD = CInt
type Masks = CUInt
type EventMap = Map WD (Event -> IO ())
type WDEvent = (WD, Event)
data INotify = INotify Handle FD (MVar EventMap) (Async ()) (Async ())
data WatchDescriptor = WatchDescriptor INotify WD deriving Eq
instance Eq INotify where
(INotify _ fd1 _ _ _) == (INotify _ fd2 _ _ _) = fd1 == fd2
newtype Cookie = Cookie CUInt deriving (Eq,Ord)
data FDEvent = FDEvent WD Masks CUInt{-Cookie-} (Maybe RawFilePath) deriving (Eq, Show)
data Event =
-- | A file was accessed. @Accessed isDirectory file@
Accessed
{ isDirectory :: Bool
, maybeFilePath :: Maybe RawFilePath
}
-- | A file was modified. @Modified isDirectory file@
| Modified
{ isDirectory :: Bool
, maybeFilePath :: Maybe RawFilePath
}
-- | A files attributes where changed. @Attributes isDirectory file@
| Attributes
{ isDirectory :: Bool
, maybeFilePath :: Maybe RawFilePath
}
-- | A file was closed. @Closed isDirectory file wasWriteable@
| Closed
{ isDirectory :: Bool
, maybeFilePath :: Maybe RawFilePath
, wasWriteable :: Bool
}
-- | A file was opened. @Opened isDirectory maybeFilePath@
| Opened
{ isDirectory :: Bool
, maybeFilePath :: Maybe RawFilePath
}
-- | A file was moved away from the watched dir. @MovedFrom isDirectory from cookie@
| MovedOut
{ isDirectory :: Bool
, filePath :: RawFilePath
, moveCookie :: Cookie
}
-- | A file was moved into the watched dir. @MovedTo isDirectory to cookie@
| MovedIn
{ isDirectory :: Bool
, filePath :: RawFilePath
, moveCookie :: Cookie
}
-- | The watched file was moved. @MovedSelf isDirectory@
| MovedSelf
{ isDirectory :: Bool
}
-- | A file was created. @Created isDirectory file@
| Created
{ isDirectory :: Bool
, filePath :: RawFilePath
}
-- | A file was deleted. @Deleted isDirectory file@
| Deleted
{ isDirectory :: Bool
, filePath :: RawFilePath
}
-- | The file watched was deleted.
| DeletedSelf
-- | The file watched was unmounted.
| Unmounted
-- | The queue overflowed.
| QOverflow
| Ignored
| Unknown FDEvent
deriving (Eq, Show)
data EventVariety
= Access
| Modify
| Attrib
| Close
| CloseWrite
| CloseNoWrite
| Open
| Move
| MoveIn
| MoveOut
| MoveSelf
| Create
| Delete
| DeleteSelf
| OnlyDir
| NoSymlink
| MaskAdd
| OneShot
| AllEvents
deriving Eq
instance Show INotify where
show (INotify _ fd _ _ _) =
showString ""
instance Show WatchDescriptor where
show (WatchDescriptor _ wd) = showString ""
instance Show Cookie where
show (Cookie c) = showString ""
initINotify :: IO INotify
initINotify = do
fdint <- throwErrnoIfMinus1 "initINotify" $ c_inotify_init1 (c_IN_NONBLOCK .|. c_IN_CLOEXEC)
(fd, _) <- FD.mkFD fdint ReadMode (Just (Stream,0,0))
False -- is_socket
True -- make non-blocking. Otherwise reading uses select(), which can fail when there are >=1024 FDs
h <- mkFileHandle
fd
(showString "")
ReadMode
Nothing -- no encoding, so binary
noNewlineTranslation
em <- newMVar Map.empty
(tid1, tid2) <- inotify_start_thread h em
return (INotify h fdint em tid1 tid2)
addWatch :: INotify -> [EventVariety] -> RawFilePath -> (Event -> IO ()) -> IO WatchDescriptor
addWatch inotify@(INotify _ fd em _ _) masks fp cb = do
catch_IO (void $
(if (NoSymlink `elem` masks) then getSymbolicLinkStatus else getFileStatus)
fp) $ \_ ->
ioError $ mkIOError doesNotExistErrorType
"can't watch what isn't there!"
Nothing
(Just (show fp))
let mask = joinMasks (map eventVarietyToMask masks)
wd <- withFilePath fp $ \fp_c ->
throwErrnoIfMinus1 "addWatch" $
c_inotify_add_watch (fromIntegral fd) fp_c mask
let event = \e -> ignore_failure $ do
case e of
-- if the event is Ignored then we know for sure that
-- this is the last event on that WatchDescriptor
Ignored -> rm_watch inotify wd
_ -> return ()
cb e
modifyMVar_ em $ \em' -> return (Map.insertWith (liftM2 (>>)) wd event em')
return (WatchDescriptor inotify wd)
where
-- catch_IO is same as catchIOError from base >= 4.5.0.0
catch_IO :: IO a -> (IOError -> IO a) -> IO a
catch_IO = E.catch
eventVarietyToMask ev =
case ev of
Access -> inAccess
Modify -> inModify
Attrib -> inAttrib
Close -> inClose
CloseWrite -> inCloseWrite
CloseNoWrite -> inCloseNowrite
Open -> inOpen
Move -> inMove
MoveIn -> inMovedTo
MoveOut -> inMovedFrom
MoveSelf -> inMoveSelf
Create -> inCreate
Delete -> inDelete
DeleteSelf-> inDeleteSelf
OnlyDir -> inOnlydir
NoSymlink -> inDontFollow
MaskAdd -> inMaskAdd
OneShot -> inOneshot
AllEvents -> inAllEvents
ignore_failure :: IO () -> IO ()
ignore_failure action = action `E.catch` ignore
where
ignore :: SomeException -> IO ()
ignore e
##if MIN_VERSION_async(2,2,1)
| Just AsyncCancelled <- fromException e = throwIO e
##else
| Just ThreadKilled{} <- fromException e = throwIO e
##endif
| otherwise = return ()
removeWatch :: WatchDescriptor -> IO ()
removeWatch (WatchDescriptor (INotify _ fd _ _ _) wd) = do
_ <- throwErrnoIfMinus1 "removeWatch" $
c_inotify_rm_watch (fromIntegral fd) wd
return ()
rm_watch :: INotify -> WD -> IO ()
rm_watch (INotify _ _ em _ _) wd =
modifyMVar_ em (return . Map.delete wd)
read_events :: Handle -> IO [WDEvent]
read_events h =
let maxRead = 16385 in
allocaBytes maxRead $ \buffer -> do
_ <- hWaitForInput h (-1) -- wait forever
r <- hGetBufNonBlocking h buffer maxRead
read_events' buffer r
where
read_events' :: Ptr a -> Int -> IO [WDEvent]
read_events' _ r | r <= 0 = return []
read_events' ptr r = do
wd <- (#peek struct inotify_event, wd) ptr :: IO CInt
mask <- (#peek struct inotify_event, mask) ptr :: IO CUInt
cookie <- (#peek struct inotify_event, cookie) ptr :: IO CUInt
len <- (#peek struct inotify_event, len) ptr :: IO CUInt
nameM <- if len == 0
then return Nothing
else do
fmap Just $ peekFilePath ((#ptr struct inotify_event, name) ptr)
let event_size = (#size struct inotify_event) + (fromIntegral len)
event = cEvent2Haskell (FDEvent wd mask cookie nameM)
rest <- read_events' (ptr `plusPtr` event_size) (r - event_size)
return (event:rest)
cEvent2Haskell :: FDEvent
-> WDEvent
cEvent2Haskell fdevent@(FDEvent wd mask cookie nameM)
= (wd, event)
where
event
| isSet inAccess = Accessed isDir nameM
| isSet inModify = Modified isDir nameM
| isSet inAttrib = Attributes isDir nameM
| isSet inClose = Closed isDir nameM (isSet inCloseWrite)
| isSet inOpen = Opened isDir nameM
| isSet inMovedFrom = MovedOut isDir name (Cookie cookie)
| isSet inMovedTo = MovedIn isDir name (Cookie cookie)
| isSet inMoveSelf = MovedSelf isDir
| isSet inCreate = Created isDir name
| isSet inDelete = Deleted isDir name
| isSet inDeleteSelf = DeletedSelf
| isSet inUnmount = Unmounted
| isSet inQOverflow = QOverflow
| isSet inIgnored = Ignored
| otherwise = Unknown fdevent
isDir = isSet inIsdir
isSet bits = maskIsSet bits mask
name = fromJust nameM
inotify_start_thread :: Handle -> MVar EventMap -> IO (Async (), Async ())
inotify_start_thread h em = do
chan_events <- newChan
tid1 <- async (logFailure "dispatcher" (dispatcher chan_events))
tid2 <- async (logFailure "start_thread" (start_thread chan_events))
return (tid1,tid2)
where
start_thread :: Chan [WDEvent] -> IO ()
start_thread chan_events = do
events <- read_events h
writeChan chan_events events
start_thread chan_events
dispatcher :: Chan [WDEvent] -> IO ()
dispatcher chan_events = do
events <- readChan chan_events
mapM_ runHandler events
dispatcher chan_events
runHandler :: WDEvent -> IO ()
runHandler (_, e@QOverflow) = do -- send overflows to all handlers
handlers <- readMVar em
mapM_ ($ e) (Map.elems handlers)
runHandler (wd, event) = do
handlers <- readMVar em
let handlerM = Map.lookup wd handlers
case handlerM of
Nothing -> putStrLn "runHandler: couldn't find handler" -- impossible?
Just handler -> handler event
logFailure name io = io `E.catch` \e ->
case e of
##if MIN_VERSION_async(2,2,1)
_ | Just AsyncCancelled <- fromException e -> return ()
##else
_ | Just ThreadKilled{} <- fromException e -> return ()
##endif
| otherwise -> hPutStrLn stderr (name ++ " dying: " ++ show e)
killINotify :: INotify -> IO ()
killINotify (INotify h _ _ tid1 tid2) =
do cancelWait tid1
cancelWait tid2
hClose h
cancelWait :: Async a -> IO ()
##if MIN_VERSION_async(2,1,1)
cancelWait = cancel
##else
cancelWait a = do cancel a; void $ waitCatch a
##endif
withINotify :: (INotify -> IO a) -> IO a
withINotify = bracket initINotify killINotify
c_IN_NONBLOCK :: CInt
c_IN_NONBLOCK = #const IN_NONBLOCK
c_IN_CLOEXEC :: CInt
c_IN_CLOEXEC = #const IN_CLOEXEC
foreign import ccall unsafe "sys/inotify.h inotify_init1" c_inotify_init1 :: CInt -> IO CInt
foreign import ccall unsafe "sys/inotify.h inotify_add_watch" c_inotify_add_watch :: CInt -> CString -> CUInt -> IO CInt
foreign import ccall unsafe "sys/inotify.h inotify_rm_watch" c_inotify_rm_watch :: CInt -> CInt -> IO CInt
hinotify-0.4.2/src/System/INotify/Masks.hsc 0000644 0000000 0000000 00000004360 14755070044 016756 0 ustar 00 0000000 0000000 module System.INotify.Masks
( inAccess
, inModify
, inAttrib
, inCloseWrite
, inCloseNowrite
, inOpen
, inMovedFrom
, inMovedTo
, inMoveSelf
, inCreate
, inDelete
, inDeleteSelf
, inUnmount
, inQOverflow
, inIgnored
, inClose
, inMove
, inOnlydir
, inDontFollow
, inMaskAdd
, inIsdir
, inOneshot
, inAllEvents
, maskIsSet
, joinMasks
, Mask
) where
import Data.Bits
import Data.Maybe
import Foreign.C.Types
#include "sys/inotify.h"
data Mask
= UserSpace CUInt
| Extra CUInt
| Helper CUInt
| Special CUInt
| All CUInt
deriving (Eq,Ord)
maskIsSet :: Mask -> CUInt -> Bool
maskIsSet mask cuint =
value mask .&. cuint > 0
value :: Mask -> CUInt
value (UserSpace i) = i
value (Extra i) = i
value (Helper i) = i
value (Special i) = i
value (All i) = i
instance Show Mask where
show mask =
fromJust $ lookup mask [
(inAccess, "IN_ACCESS"),
(inModify, "IN_MODIFY"),
(inAttrib, "IN_ATTRIB"),
(inClose, "IN_CLOSE"),
(inCloseWrite, "IN_CLOSE_WRITE"),
(inCloseNowrite, "IN_CLOSE_NOWRITE"),
(inOpen, "IN_OPEN"),
(inMove, "IN_MOVE"),
(inMovedFrom, "IN_MOVED_FROM"),
(inMovedTo, "IN_MOVED_TO"),
(inMoveSelf, "IN_MOVE_SELF"),
(inCreate, "IN_CREATE"),
(inDelete, "IN_DELETE"),
(inDeleteSelf, "IN_DELETE_SELF"),
(inUnmount, "IN_UNMOUNT"),
(inQOverflow, "IN_Q_OVERFLOW"),
(inIgnored, "IN_IGNORED"),
(inClose, "IN_CLOSE"),
(inIsdir, "IN_ISDIR"),
(inOneshot, "IN_ONESHOT")]
joinMasks :: [Mask] -> CUInt
joinMasks = foldr (.|.) 0 . map value
#enum Mask, UserSpace, IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE
#enum Mask, UserSpace, IN_CLOSE_NOWRITE, IN_OPEN, IN_MOVED_FROM, IN_MOVED_TO
#enum Mask, UserSpace, IN_CREATE, IN_DELETE, IN_DELETE_SELF, IN_MOVE_SELF
#enum Mask, Extra, IN_UNMOUNT, IN_Q_OVERFLOW, IN_IGNORED
#enum Mask, Helper, IN_CLOSE, IN_MOVE
#enum Mask, Special, IN_ONLYDIR, IN_DONT_FOLLOW, IN_MASK_ADD, IN_ISDIR
#enum Mask, Special, IN_ONESHOT
#enum Mask, All, IN_ALL_EVENTS
hinotify-0.4.2/tests/test006-callbackHang.hs 0000644 0000000 0000000 00000001547 14755070044 017004 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Char8 as BC8
import Control.Concurrent
import Control.Exception
import System.INotify as INotify
import System.Timeout
import Utils
file :: String
file = "hello"
write :: String -> IO ()
write path = do
writeFile (path ++ '/':file) ""
main :: IO ()
main = maybe testFailure (const testSuccess) =<< timeout 1000000 doTest
doTest :: IO ()
doTest =
withTempDir $ \testPath ->
bracket
initINotify
killINotify -- should complete and kill all threads
$ \inot -> do
mvar1 <- newEmptyMVar
mvar2 <- newEmptyMVar
_ <- addWatch inot [AllEvents] testPath $ \_event -> do
putMVar mvar1 ()
takeMVar mvar2 -- hangs here
write (BC8.unpack testPath)
takeMVar mvar1
hinotify-0.4.2/tests/Utils.hs 0000644 0000000 0000000 00000003563 14755070044 014347 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Utils where
import Control.Concurrent.Chan
import Control.Exception
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC8
import Data.String
import System.Directory ( removeDirectoryRecursive )
import System.Environment
import System.Exit
import System.INotify
import System.Posix.ByteString.FilePath
import System.Posix.Directory.ByteString
import System.Posix.Files.ByteString
testName :: IO RawFilePath
testName = do
n <- getProgName
return (fromString n `B.append` "-playground")
withTempDir :: (RawFilePath -> IO a) -> IO a
withTempDir f = do
path <- testName
bracket
( createDirectory path ownerModes >> return path )
( removeDirectoryRecursive . fromString . BC8.unpack )
f
withWatch :: INotify -> [EventVariety] -> RawFilePath -> (Event -> IO ()) -> IO a -> IO a
withWatch inot events path action f =
bracket
( addWatch inot events path action )
removeWatch
( const f )
inTestEnviron :: [EventVariety] -> (FilePath -> IO a) -> ([Event] -> IO b) -> IO b
inTestEnviron events action f =
withTempDir $ \testPath -> do
inot <- initINotify
chan <- newChan
withWatch inot events testPath (writeChan chan) $ do
_ <- action (fromString . BC8.unpack $ testPath)
events' <- getChanContents chan
f events'
(~=) :: Eq a => [a] -> [a] -> Bool
[] ~= _ = True
(x:xs) ~= (y:ys) = x == y && xs ~= ys
_ ~= _ = False
asMany :: [a] -> [a] -> [a]
asMany xs ys = take (length xs) ys
explainFailure :: Show a => [a] -> [a] -> String
explainFailure expected reality = unlines $
[ "Expected:" ] ++
[ "> " ++ show x | x <- expected ] ++
[ "But got:" ] ++
[ "< " ++ show x | x <- asMany expected reality ]
testFailure, testSuccess :: IO a
testFailure = exitFailure
testSuccess = exitSuccess
hinotify-0.4.2/tests/test005-move-file.hs 0000644 0000000 0000000 00000002141 14755070044 016323 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad
import System.Directory
import System.INotify as INotify
import Utils
file, file2 :: String
file = "hello"
file2 = "hello2"
write :: String -> IO ()
write path =
writeFile (path ++ '/':file) ""
move :: String -> IO ()
move path =
renameFile (path ++ '/':file) (path ++ '/':file2)
remove :: String -> IO ()
remove path =
removeFile (path ++ '/':file2)
action :: String -> IO ()
action path = do
write path
move path
remove path
main :: IO ()
main =
inTestEnviron [AllEvents] action $ \ events -> do
let cookie = head [ c | MovedOut _ _ c <- events ]
when (expected cookie ~= events)
testSuccess
putStrLn $ explainFailure (expected cookie) events
testFailure
expected :: Cookie -> [Event]
expected cookie =
[ Created False "hello"
, Opened False (Just "hello")
, Modified False (Just "hello")
, Closed False (Just "hello") True
, MovedOut False "hello" cookie
, MovedIn False "hello2" cookie
, Deleted False "hello2"
]
hinotify-0.4.2/tests/test004-modify-file.hs 0000644 0000000 0000000 00000002173 14755070044 016650 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Exception
import Control.Monad
import System.Directory
import System.IO
import System.INotify as INotify
import Utils
file :: String
file = "hello"
write :: String -> IO ()
write path =
writeFile (path ++ '/':file) ""
modify :: String -> IO ()
modify path =
bracket
(openFile (path ++ '/':file) AppendMode)
hClose
(\h -> hPutStr h "yarr!")
remove :: String -> IO ()
remove path =
removeFile (path ++ '/':file)
action :: String -> IO ()
action path = do
write path
modify path
remove path
main :: IO ()
main =
inTestEnviron [AllEvents] action $ \ events -> do
when (expected ~= events)
testSuccess
putStrLn $ explainFailure expected events
testFailure
expected :: [Event]
expected =
[ Created False "hello"
, Opened False (Just "hello")
, Modified False (Just "hello")
, Closed False (Just "hello") True
, Opened False (Just "hello")
, Modified False (Just "hello")
, Closed False (Just "hello") True
, Deleted False "hello"
]
hinotify-0.4.2/tests/test003-removefile.hs 0000644 0000000 0000000 00000001466 14755070044 016604 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad
import System.Directory
import System.INotify as INotify
import Utils
file :: String
file = "hello"
write :: String -> IO ()
write path =
writeFile (path ++ '/':file) ""
remove :: String -> IO ()
remove path =
removeFile (path ++ '/':file)
action :: String -> IO ()
action path = do
write path
remove path
main :: IO ()
main =
inTestEnviron [AllEvents] action $ \ events -> do
when (expected ~= events)
testSuccess
putStrLn $ explainFailure expected events
testFailure
expected :: [Event]
expected =
[ Created False "hello"
, Opened False (Just "hello")
, Modified False (Just "hello")
, Closed False (Just "hello") True
, Deleted False "hello"
]
hinotify-0.4.2/tests/test002-writefile.hs 0000644 0000000 0000000 00000001263 14755070044 016433 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad
import qualified Data.ByteString as B
import System.INotify as INotify
import Utils
write :: FilePath -> IO ()
write path =
B.writeFile (path ++ "/hello") ""
-- actually writing any contents gives me two Modified
main :: IO ()
main =
inTestEnviron [AllEvents] write $ \ events -> do
when (expected ~= events)
testSuccess
putStrLn $ explainFailure expected events
testFailure
expected :: [Event]
expected =
[ Created False "hello"
, Opened False (Just "hello")
, Modified False (Just "hello")
, Closed False (Just "hello") True
]
hinotify-0.4.2/tests/test001-list-dir-contents.hs 0000644 0000000 0000000 00000000655 14755070044 020026 0 ustar 00 0000000 0000000 module Main where
import Control.Monad
import System.Directory
import System.INotify as INotify
import Utils
main :: IO ()
main =
inTestEnviron [Open, Close] getDirectoryContents $ \ events -> do
when (expected ~= events)
testSuccess
putStrLn $ explainFailure expected events
testFailure
expected :: [Event]
expected =
[ Opened True Nothing
, Closed True Nothing False
]
hinotify-0.4.2/README.md 0000644 0000000 0000000 00000002412 14755315360 013023 0 ustar 00 0000000 0000000 hinotify: inotify for Haskell
=============================
[](https://github.com/haskell-fswatch/hinotify/actions/workflows/ci.yml)
About
-----
This package provides bindings to [inotify](http://www.kernel.org/pub/linux/kernel/people/rml/inotify/), which has been part of the Linux kernel since 2.6.13.
The `inotify` subsystem provides file system event notifications. Simply add a watcher to a file or directory and you get events when it is accessed, modified, etc.
News
----
See [CHANGELOG.md](./CHANGELOG.md).
API
---
The API basically consists of:
```haskell
initINotify :: IO INotify
addWatch :: INotify
-> [EventVariety] -- different events to listen on
-> RawFilePath -- file/directory to watch
-> (Event -> IO ()) -- event handler callback
-> IO WatchDescriptor
removeWatch :: WatchDescriptor -> IO ()
```
See this [example](./examples/simple/simple.hs) or the [docs](https://hackage.haskell.org/package/hinotify/docs/System-INotify.html).
Author
------
Lennart Kolmodin
`kolmodin at gmail.com`
Legal
-----
This software is released under a BSD-style license. See LICENSE for more details.
Copyright © 2007-2012 Lennart Kolmodin
hinotify-0.4.2/CHANGELOG.md 0000644 0000000 0000000 00000004173 14755750772 013375 0 ustar 00 0000000 0000000 hinotify
======
## 0.4.2
* Depend on libinotify on NetBSD (https://github.com/haskell-fswatch/hinotify/pull/35)
* Fix for GHC 9.10 (https://github.com/haskell-fswatch/hinotify/pull/39)
* Switch maintainer to thomasjm
* Switch to GitHub Actions CI to test both Cabal and Stack builds
* Start testing Ubuntu ARM in CI
## 0.4.1
* Depend on inotify library on {Free,Open}BSD
## 0.4
* Allow async-2.*
## 0.3.10
- Allow async-2.2.
- Use `RawFilePath` (`ByteString`) for filenames, from `unix` package.
Changes the `Event` type and `addWatch` function.
## 0.3.9
Patches contributed by Simon Marlow marlowsd@gmail.com
- Don't run callbacks in `mask_`.
It prevented the callback threads from receiving StackOverflow, amongst other things.
- Synchronous `killThread`.
`killThread` will now wait for the callback dispatcher threads to finish.
- Bug fixes
https://github.com/kolmodin/hinotify/pull/23
## 0.3.8
- Use file system encoding for file names.
When run in a locale like LANG=C, this ensures that the filename is encoded
as a filename, so that arbitrary bytes in it will round-trip correctly,
rather than being stripped out.
Fixes https://github.com/kolmodin/hinotify/issues/13
Patch contributed by Joey Hess joeyh@joeyh.name
## 0.3.7
* Bug fix: When registerering a new watch on a path which is already watched,
don't overwrite the event listener from the previous watch.
## 0.3.2
* Make each `WatchDescriptor` contain its `INotify`. Changes to the function types:
> -removeWatch :: INotify -> WatchDescriptor -> IO ()
> +removeWatch :: WatchDescriptor -> IO ()
* Fix typo in declaration of `Deleted` in `data Event`;
> - { isDirecotry :: Bool
> + { isDirectory :: Bool
## 0.3.1
* Use `inotify.h` from `glibc` rather than from the linux headers, as
recommended upstream.
## 0.3
* Compiles with GHC 6.12, GHC 6.10.4, GHC 6.8.2 and GHC 6.6.1
## 0.2
* Updates to the API
- Function names is now in semiCamelCase
- Restructure event parameters to make it more consistent
* Small test suit in `tests/`
* Compiles with GHC 6.8.2 and GHC 6.6.1
* Requires Cabal 1.2
## 0.1
* Initial release
hinotify-0.4.2/LICENSE 0000644 0000000 0000000 00000002663 14755070044 012556 0 ustar 00 0000000 0000000 Copyright (c) Lennart Kolmodin
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 CONTRIBUTORS ``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.
hinotify-0.4.2/Setup.lhs 0000755 0000000 0000000 00000000110 14755070044 013345 0 ustar 00 0000000 0000000 #!/usr/bin/runhaskell
> import Distribution.Simple
> main = defaultMain
hinotify-0.4.2/hinotify.cabal 0000644 0000000 0000000 00000005245 14755751012 014366 0 ustar 00 0000000 0000000 name: hinotify
version: 0.4.2
build-type: Simple
synopsis: Haskell binding to inotify
description:
This library provides a wrapper to the Linux Kernel's inotify feature,
allowing applications to subscribe to notifications when a file is
accessed or modified.
category: System
homepage: https://github.com/kolmodin/hinotify
bug-reports: https://github.com/kolmodin/hinotify/issues
license: BSD3
license-file: LICENSE
author: Lennart Kolmodin
maintainer: Tom McLaughlin
extra-source-files: README.md, CHANGELOG.md
cabal-version: >= 1.10
source-repository head
type: git
location: git://github.com/kolmodin/hinotify.git
library
default-language: Haskell2010
build-depends: base >= 4.5.0.0 && < 5, bytestring, containers, unix,
async == 2.*
exposed-modules:
System.INotify
other-modules:
System.INotify.Masks
ghc-options: -Wall
includes: sys/inotify.h
hs-source-dirs: src
if os(freebsd) || os(netbsd) || os(openbsd)
extra-libraries: inotify
test-suite test001
type: exitcode-stdio-1.0
default-language: Haskell2010
build-depends: base, bytestring, directory, hinotify, unix
hs-source-dirs: src tests
main-is: test001-list-dir-contents.hs
other-modules: Utils
ghc-options: -Wall
test-suite test002
type: exitcode-stdio-1.0
default-language: Haskell2010
build-depends: base, bytestring, directory, hinotify, unix
hs-source-dirs: src tests
main-is: test002-writefile.hs
other-modules: Utils
ghc-options: -Wall
test-suite test003
type: exitcode-stdio-1.0
default-language: Haskell2010
build-depends: base, bytestring, directory, hinotify, unix
hs-source-dirs: src tests
main-is: test003-removefile.hs
other-modules: Utils
ghc-options: -Wall
test-suite test004
type: exitcode-stdio-1.0
default-language: Haskell2010
build-depends: base, bytestring, directory, hinotify, unix
hs-source-dirs: src tests
main-is: test004-modify-file.hs
other-modules: Utils
ghc-options: -Wall
test-suite test005
type: exitcode-stdio-1.0
build-depends: base, bytestring, directory, hinotify, unix
default-language: Haskell2010
hs-source-dirs: src tests
main-is: test005-move-file.hs
other-modules: Utils
ghc-options: -Wall
test-suite test006
type: exitcode-stdio-1.0
build-depends: base, bytestring, directory, hinotify, unix
default-language: Haskell2010
hs-source-dirs: src tests
main-is: test006-callbackHang.hs
other-modules: Utils
ghc-options: -Wall