fsnotify 0.4.0.1 → 0.4.1.0
raw patch · 13 files changed
+180/−135 lines, 13 filesdep ~sandwichdep ~unliftio
Dependency ranges changed: sandwich, unliftio
Files
- CHANGELOG.md +8/−0
- fsnotify.cabal +13/−9
- src/System/FSNotify.hs +42/−36
- src/System/FSNotify/Devel.hs +6/−4
- src/System/FSNotify/Linux.hs +4/−4
- src/System/FSNotify/Listener.hs +1/−1
- src/System/FSNotify/OSX.hs +7/−5
- src/System/FSNotify/Path.hs +7/−7
- src/System/FSNotify/Polling.hs +7/−4
- src/System/FSNotify/Types.hs +22/−22
- src/System/FSNotify/Win32.hs +4/−4
- test/FSNotify/Test/EventTests.hs +36/−36
- test/FSNotify/Test/Util.hs +23/−3
CHANGELOG.md view
@@ -1,6 +1,14 @@ Changes ======= +Version 0.4.1.0+---------------++* Add `unliftio` lower bound (#106)+* Change the tests back to a test-suite to avoid building for library users. (#107)+* Fix up Windows compatibility.+* Export `WatchConfig` type (#108)+ Version 0.4.0.1 ---------------
fsnotify.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.35.0. -- -- see: https://github.com/sol/hpack name: fsnotify-version: 0.4.0.1+version: 0.4.1.0 synopsis: Cross platform library for file change notification. description: Cross platform library for file creation, modification, and deletion notification. This library builds upon existing libraries for platform-specific Windows, Mac, and Linux filesystem event notification. category: Filesystem@@ -45,10 +45,11 @@ , text >=0.11.0 , time >=1.1 , unix-compat >=0.2+ default-language: Haskell2010 if os(linux) cpp-options: -DOS_Linux if os(windows)- cpp-options: -DOS_Windows+ cpp-options: -DOS_Win32 if os(darwin) cpp-options: -DOS_Mac if os(freebsd) || os(netbsd) || os(openbsd)@@ -73,9 +74,9 @@ System.FSNotify.OSX build-depends: hfsevents >=0.1.3- default-language: Haskell2010 -executable tests+test-suite tests+ type: exitcode-stdio-1.0 main-is: Main.hs other-modules: FSNotify.Test.EventTests@@ -94,14 +95,14 @@ , random , retry , safe-exceptions- , sandwich , temporary , unix-compat- , unliftio+ , unliftio >=0.2.20+ default-language: Haskell2010 if os(linux) cpp-options: -DOS_Linux if os(windows)- cpp-options: -DOS_Windows+ cpp-options: -DOS_Win32 if os(darwin) cpp-options: -DOS_Mac if os(freebsd) || os(netbsd) || os(openbsd)@@ -109,4 +110,7 @@ if os(windows) build-depends: Win32- default-language: Haskell2010+ , sandwich >=0.1.1.1+ else+ build-depends:+ sandwich
src/System/FSNotify.hs view
@@ -2,9 +2,17 @@ -- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org ---{-# LANGUAGE CPP, ScopedTypeVariables, ExistentialQuantification, RankNTypes, LambdaCase, OverloadedStrings, MultiWayIf, FlexibleContexts, RecordWildCards, NamedFieldPuns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-} --- | NOTE: This library does not currently report changes made to directories,+-- | This library does not currently report changes made to directories, -- only files within watched directories. -- -- Minimal example:@@ -27,42 +35,40 @@ -- > -- sleep forever (until interrupted) -- > forever $ threadDelay 1000000 -module System.FSNotify- (-- -- * Events- Event(..)- , EventIsDirectory(..)- , EventChannel- , Action- , ActionPredicate-- -- * Starting/Stopping- , WatchManager- , withManager- , startManager- , stopManager+module System.FSNotify (+ -- * Events+ Event(..)+ , EventIsDirectory(..)+ , EventChannel+ , Action+ , ActionPredicate - -- * Configuration- , defaultConfig- , confWatchMode- , confThreadingMode- , confOnHandlerException- , WatchMode(..)- , ThreadingMode(..)+ -- * Starting/Stopping+ , WatchManager+ , withManager+ , startManager+ , stopManager - -- * Lower level- , withManagerConf- , startManagerConf- , StopListening+ -- * Configuration+ , defaultConfig+ , WatchConfig+ , confWatchMode+ , confThreadingMode+ , confOnHandlerException+ , WatchMode(..)+ , ThreadingMode(..) - -- * Watching- , watchDir- , watchDirChan- , watchTree- , watchTreeChan+ -- * Lower level+ , withManagerConf+ , startManagerConf+ , StopListening - ) where+ -- * Watching+ , watchDir+ , watchDirChan+ , watchTree+ , watchTreeChan+ ) where import Prelude hiding (FilePath) @@ -146,11 +152,11 @@ Nothing -> return () Just (_, t) -> cancel t --- | Like 'withManager', but configurable+-- | Like 'withManager', but configurable. withManagerConf :: WatchConfig -> (WatchManager -> IO a) -> IO a withManagerConf conf = bracket (startManagerConf conf) stopManager --- | Like 'startManager', but configurable+-- | Like 'startManager', but configurable. startManagerConf :: WatchConfig -> IO WatchManager startManagerConf conf = do # ifdef OS_Win32
src/System/FSNotify/Devel.hs view
@@ -17,10 +17,12 @@ {-# LANGUAGE NamedFieldPuns #-} -module System.FSNotify.Devel- ( treeExtAny, treeExtExists,- doAllEvents,- allEvents, existsEvents+module System.FSNotify.Devel (+ treeExtAny+ , treeExtExists+ , doAllEvents+ , allEvents+ , existsEvents ) where import Data.Text
src/System/FSNotify/Linux.hs view
@@ -14,10 +14,10 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module System.FSNotify.Linux- ( FileListener(..)- , NativeManager- ) where+module System.FSNotify.Linux (+ FileListener(..)+ , NativeManager+ ) where import Control.Concurrent.MVar import Control.Exception.Safe as E
src/System/FSNotify/Listener.hs view
@@ -18,7 +18,7 @@ import System.FSNotify.Types import System.FilePath --- | An action that cancels a watching/listening job+-- | An action that cancels a watching/listening job. type StopListening = IO () type ListenFn sessionType argType = FileListener sessionType argType => WatchConfig -> sessionType -> FilePath -> ActionPredicate -> EventCallback -> IO StopListening
src/System/FSNotify/OSX.hs view
@@ -3,12 +3,14 @@ -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org -- -{-# LANGUAGE MultiWayIf, OverloadedStrings, MultiParamTypeClasses #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-} -module System.FSNotify.OSX- ( FileListener(..)- , NativeManager- ) where+module System.FSNotify.OSX (+ FileListener(..)+ , NativeManager+ ) where import Control.Concurrent import Control.Monad
src/System/FSNotify/Path.hs view
@@ -7,13 +7,13 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE CPP #-} -module System.FSNotify.Path - ( findFiles - , findFilesAndDirs - , canonicalizeDirPath - , canonicalizePath - , hasThisExtension - ) where +module System.FSNotify.Path ( + findFiles + , findFilesAndDirs + , canonicalizeDirPath + , canonicalizePath + , hasThisExtension + ) where import Control.Monad import qualified Data.Text as T
src/System/FSNotify/Polling.hs view
@@ -7,8 +7,8 @@ -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org -- -module System.FSNotify.Polling- ( createPollManager+module System.FSNotify.Polling (+ createPollManager , PollManager(..) , FileListener(..) ) where@@ -30,6 +30,7 @@ import System.PosixCompat.Files import System.PosixCompat.Types + data EventType = AddedEvent | ModifiedEvent | RemovedEvent@@ -37,8 +38,10 @@ newtype WatchKey = WatchKey ThreadId deriving (Eq, Ord) data WatchData = WatchData FilePath EventCallback type WatchMap = Map WatchKey WatchData-data PollManager = PollManager { pollManagerWatchMap :: MVar WatchMap- , pollManagerInterval :: Int }+data PollManager = PollManager {+ pollManagerWatchMap :: MVar WatchMap+ , pollManagerInterval :: Int+ } generateEvent :: UTCTime -> EventIsDirectory -> EventType -> FilePath -> Maybe Event generateEvent timestamp isDir AddedEvent filePath = Just (Added filePath timestamp isDir)
src/System/FSNotify/Types.hs view
@@ -4,21 +4,21 @@ -- {-# LANGUAGE CPP #-} -module System.FSNotify.Types- ( act- , ActionPredicate- , Action- , DebounceFn- , WatchConfig(..)- , WatchMode(..)- , ThreadingMode(..)- , Event(..)- , EventIsDirectory(..)- , EventCallback- , EventChannel- , EventAndActionChannel- , IOEvent- ) where+module System.FSNotify.Types (+ act+ , ActionPredicate+ , Action+ , DebounceFn+ , WatchConfig(..)+ , WatchMode(..)+ , ThreadingMode(..)+ , Event(..)+ , EventIsDirectory(..)+ , EventCallback+ , EventChannel+ , EventAndActionChannel+ , IOEvent+ ) where import Control.Concurrent.Chan import Control.Exception.Safe@@ -40,11 +40,11 @@ | ModifiedAttributes { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory } | Removed { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory } | WatchedDirectoryRemoved { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }- -- ^ Note: currently only emitted on Linux+ -- ^ Note: Linux-only | CloseWrite { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }- -- ^ Note: currently only emitted on Linux+ -- ^ Note: Linux-only | Unknown { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory, eventString :: String }- -- ^ Note: currently only emitted on Linux+ -- ^ Note: Linux-only deriving (Eq, Show) type EventChannel = Chan Event@@ -73,14 +73,14 @@ | ThreadPerEvent -- ^ Launch a separate thread for every event handler. --- | Watch configuration+-- | Watch configuration. data WatchConfig = WatchConfig { confWatchMode :: WatchMode- -- ^ Watch mode to use+ -- ^ Watch mode to use. , confThreadingMode :: ThreadingMode- -- ^ Threading mode to use+ -- ^ Threading mode to use. , confOnHandlerException :: SomeException -> IO ()- -- ^ Called when a handler throws an exception+ -- ^ Called when a handler throws an exception. } type IOEvent = IORef Event
src/System/FSNotify/Win32.hs view
@@ -5,10 +5,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module System.FSNotify.Win32- ( FileListener(..)- , NativeManager- ) where+module System.FSNotify.Win32 (+ FileListener(..)+ , NativeManager+ ) where import Control.Concurrent import Control.Monad (when)
test/FSNotify/Test/EventTests.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ImplicitParams #-} {-# LANGUAGE MultiWayIf #-}@@ -25,13 +24,9 @@ import UnliftIO.Directory -eventTests :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore context) => ThreadingMode -> SpecFree context m ()+eventTests :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore' context) => ThreadingMode -> SpecFree context m () eventTests threadingMode = describe "Tests" $ parallel $ do-#ifdef OS_BSD- let pollOptions = [True]-#else- let pollOptions = [False, True]-#endif+ let pollOptions = if isBSD then [True] else [False, True] forM_ pollOptions $ \poll -> describe (if poll then "Polling" else "Native") $ parallel $ do let ?timeInterval = if poll then 2*10^(6 :: Int) else 5*10^(5 :: Int)@@ -41,7 +36,7 @@ -eventTests' :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore context, ?timeInterval :: Int) => ThreadingMode -> Bool -> Bool -> Bool -> SpecFree context m ()+eventTests' :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore' context, ?timeInterval :: Int) => ThreadingMode -> Bool -> Bool -> Bool -> SpecFree context m () eventTests' threadingMode poll recursive nested = do -- withParallelSemaphore $ let itWithFolder name action = introduceTestFolder threadingMode poll recursive nested $ it name action @@ -55,11 +50,16 @@ itWithFolder "works with a new file" $ do TestFolderContext _watchedDir f getEvents _clearEvents <- getContext testFolderContext- h <- openFile f AppendMode - flip finally (hClose h) $+ let wrapper action = if | isWin -> liftIO (writeFile f "foo") >> action+ | otherwise -> withFile f AppendMode $ \_ -> action++ wrapper $ pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events -> if | nested && not recursive -> events `shouldBe` []+ | isWin && not poll -> case events of+ [Modified {}, Added {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()+ _ -> expectationFailure $ "Got wrong events: " <> show events | otherwise -> case events of [Added {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return () _ -> expectationFailure $ "Got wrong events: " <> show events@@ -109,40 +109,29 @@ pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events -> if | poll -> return () | nested && not recursive -> events `shouldBe` []- | otherwise -> case events of-#ifdef mingw32_HOST_OS+ | isWin -> case events of [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()-#else+ _ -> expectationFailure $ "Got wrong events: " <> show events+ | otherwise -> case events of [ModifiedAttributes {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()-#endif _ -> expectationFailure $ "Got wrong events: " <> show events itWithFolder "works with a modified file" $ do TestFolderContext _watchedDir f getEvents clearEvents <- getContext testFolderContext liftIO (writeFile f "" >> clearEvents) -#ifdef mingw32_HOST_OS- writeFile f "foo"- do-#else- withFile f WriteMode $ \h ->- flip finally (hClose h) $ do- liftIO $ hPutStr h "foo"-#endif-- pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->- if | nested && not recursive -> events `shouldBe` []- | otherwise -> case events of-#ifdef darwin_HOST_OS- [Modified {..}] | poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()- [ModifiedAttributes {..}] | not poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()-#else- [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()-#endif- _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")"+ (if isWin then withSingleWriteFile f "foo" else withOpenWritableAndWrite f "foo") $+ pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->+ if | nested && not recursive -> events `shouldBe` []+ | isMac -> case events of+ [Modified {..}] | poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()+ [ModifiedAttributes {..}] | not poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()+ _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")"+ | otherwise -> case events of+ [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()+ _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")" -#ifdef linux_HOST_OS- unless poll $+ when isLinux $ unless poll $ itWithFolder "gets a close_write" $ do TestFolderContext _watchedDir f getEvents clearEvents <- getContext testFolderContext liftIO (writeFile f "" >> clearEvents)@@ -157,4 +146,15 @@ | eventPath cw `equalFilePath` f && eventIsDirectory cw == IsFile && eventPath m `equalFilePath` f && eventIsDirectory m == IsFile -> return () _ -> expectationFailure $ "Got wrong events: " <> show events-#endif++withSingleWriteFile :: MonadIO m => FilePath -> String -> m b -> m b+withSingleWriteFile fp contents action = do+ liftIO $ writeFile fp contents+ action++withOpenWritableAndWrite :: MonadUnliftIO m => FilePath -> String -> m b -> m b+withOpenWritableAndWrite fp contents action = do+ withFile fp WriteMode $ \h ->+ flip finally (hClose h) $ do+ liftIO $ hPutStr h contents+ action
test/FSNotify/Test/Util.hs view
@@ -6,6 +6,7 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-} module FSNotify.Test.Util where @@ -54,6 +55,20 @@ isWin = False #endif +isLinux :: Bool+#ifdef linux_HOST_OS+isLinux = True+#else+isLinux = False+#endif++isBSD :: Bool+#ifdef OS_BSD+isBSD = True+#else+isBSD = False+#endif+ pauseAndRetryOnExpectationFailure :: (MonadUnliftIO m, ?timeInterval :: Int) => Int -> m a -> m a pauseAndRetryOnExpectationFailure n action = threadDelay ?timeInterval >> retryOnExpectationFailure n action @@ -119,12 +134,17 @@ -- This is unfortunately necessary because of the madness of OS X FSEvents; see the comments in OSX.hs withRandomTempDirectory :: MonadUnliftIO m => (FilePath -> m ()) -> m () withRandomTempDirectory action = do- randomID <- replicateM 10 $ R.randomRIO ('a', 'z')+ randomID <- liftIO $ replicateM 10 $ R.randomRIO ('a', 'z') withSystemTempDirectory ("test." <> randomID) action withParallelSemaphore :: forall context m. (- MonadUnliftIO m, HasParallelSemaphore context+ MonadUnliftIO m, HasLabel context "parallelSemaphore" QSem ) => SpecFree context m () -> SpecFree context m () withParallelSemaphore = around' (defaultNodeOptions { nodeOptionsRecordTime = False, nodeOptionsVisibilityThreshold = 125 }) "claim semaphore" $ \action -> do- s <- getContext parallelSemaphore+ s <- getContext parallelSemaphore' bracket_ (liftIO $ waitQSem s) (liftIO $ signalQSem s) (void action)++parallelSemaphore' :: Label "parallelSemaphore" QSem+parallelSemaphore' = Label++type HasParallelSemaphore' context = HasLabel context "parallelSemaphore" QSem