packages feed

fswatcher 0.2.0 → 0.2.1

raw patch · 5 files changed

+98/−6 lines, 5 filessetup-changed

Files

+ Opts.hs view
@@ -0,0 +1,30 @@+module Opts where++import           Options.Applicative++data WatchOpt = WatchOpt { watchPath   :: String+                         , includePath :: String  -- ^ an reg exp to include particular files when watching dir+                         , excludePath :: String  -- ^ an reg exp to exclude particular files when watching dir+                         , throttlingDelay :: Int     -- ^ milliseconds to wait for duplicate events+                         , actionCmd   :: [String]+                         } deriving (Show)++watchOpt :: Parser WatchOpt+watchOpt = WatchOpt+     <$> strOption (long "path"+                    <> metavar "PATH"+                    <> help "directory / file to watch" )+     <*> strOption (long "include"+                    <> value []+                    <> metavar "INCLUDE"+                    <> help "pattern for including files")+     <*> strOption (long "exclude"+                    <> value []+                    <> metavar "EXCLUDE"+                    <> help "pattern for excluding files")+     <*> option auto (long "throttle"+                    <> value 0+                    <> metavar "MILLIS"+                    <> help "milliseconds to wait for duplicate events")+     <*> (many . strArgument) (metavar "COMMAND"+                      <> help "command to run" )
+ Pipeline.hs view
@@ -0,0 +1,50 @@+-- poor man's streaming library, using threads communicating with MVars+module Pipeline where++import Prelude hiding (id, (.))++import Control.Category+import Control.Concurrent (ThreadId, forkIO, threadDelay)+import Control.Concurrent.MVar+import Control.Monad+++newtype Pipeline a b = Pipeline {+  -- launches zero or more threads, forming a pipeline which takes from+  -- the 'MVar a' and puts to the 'MVar b'.+  runPipeline :: MVar a -> IO ([ThreadId], MVar b)+}++instance Category Pipeline where+    id = Pipeline $ \inputMVar -> return ([], inputMVar)+    s2 . s1 = Pipeline $ \inputMVar -> do+      (threads1, intermediateMVar) <- runPipeline s1 inputMVar+      (threads2, outputMVar) <- runPipeline s2 intermediateMVar+      return (threads1 ++ threads2, outputMVar)+++mkPipeline :: (a -> IO b) -> Pipeline a b+mkPipeline f = Pipeline $ \inputMVar -> do+    outputMVar <- newEmptyMVar+    threadId <- forkIO $ forever $ do+      x <- takeMVar inputMVar+      y <- f x+      putMVar outputMVar y+    return ([threadId], outputMVar)+++-- ignore duplicate events, defined as events closer than 'delay'+-- milliseconds apart+throttle :: Int -> Pipeline () ()+throttle delay = Pipeline $ \inputMVar -> do+    intermediateMVar <- newEmptyMVar+    outputMVar <- newEmptyMVar+    absorberThreadId <- forkIO $ forever $ do+      () <- takeMVar inputMVar+      void $ tryPutMVar intermediateMVar () -- don't block if the next thread is sleeping+    sleepingThreadId <- forkIO $ forever $ do+      () <- takeMVar intermediateMVar+      threadDelay (1000 * delay)+      _ <- tryTakeMVar intermediateMVar -- drop any event received while sleeping+      putMVar outputMVar ()+    return ([absorberThreadId, sleepingThreadId], outputMVar)
Setup.hs view
@@ -1,3 +1,2 @@ import Distribution.Simple- main = defaultMain
fswatcher.cabal view
@@ -1,7 +1,7 @@ name:               fswatcher category:           Tools build-type:         Simple-version:            0.2.0+version:            0.2.1 synopsis:           Watch a file/directory and run a command when it's modified description:        A simple program that watches a file or a directory and                     runs a given command whenever the file or a file within the@@ -27,6 +27,8 @@                    , regex-pcre-builtin   >= 0.94     ghc-options:     -Wall     main-is:         fswatcher.hs+    other-modules:   Opts+                   , Pipeline  source-repository head   type:     git
fswatcher.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} +import Prelude hiding (id, (.))+ import System.IO (hPutStrLn, stderr) import System.Posix.Files (getFileStatus, isDirectory) import System.Directory (canonicalizePath, getCurrentDirectory)@@ -10,6 +12,7 @@        stopManager, watchTree, watchDir, eventPath) import System.Exit (ExitCode (..), exitSuccess) import System.Process (createProcess, proc, waitForProcess)+import Control.Category import Control.Monad (void) import System.Posix.Signals (installHandler, Handler(Catch), sigINT, sigTERM) import Control.Concurrent (forkIO, killThread)@@ -18,6 +21,7 @@ import Options.Applicative  import Opts+import Pipeline  data FileType = File | Directory deriving Eq @@ -74,10 +78,17 @@   s <- getFileStatus canonicalPath   let filetype = if isDirectory s then Directory else File -  runTrigger <- newEmptyMVar-  runThread <- forkIO $ runCmd cmd args runTrigger-  stopWatcher <- watch filetype m canonicalPath runTrigger opt+  -- Check if throttling was requested.+  let delay = throttlingDelay opt+  let pipeline = if delay > 0 then throttle delay else id +  inputMVar <- newEmptyMVar+  (pipelineThreads, outputMVar) <- runPipeline pipeline inputMVar+  runThread <- forkIO $ runCmd cmd args outputMVar+  stopWatcher <- watch filetype m canonicalPath inputMVar opt+  +  let allThreads = runThread : pipelineThreads+   -- Calculate the full path in order to print the "real" file when watching a   -- path with one or more symlinks.   currDir <- getCurrentDirectory@@ -92,7 +103,7 @@   putStrLn "\nStopping."   stopWatcher   stopManager m-  killThread runThread+  mapM_ killThread allThreads   exitSuccess  main :: IO ()