packages feed

hs-rs-notify 0.1.0.3 → 0.1.0.4

raw patch · 3 files changed

+83/−89 lines, 3 filesbinary-added

Files

hs-rs-notify.cabal view
@@ -1,5 +1,5 @@ name:                hs-rs-notify-version:             0.1.0.3+version:             0.1.0.4 synopsis:            Experimental! Wraps this awesome rust library so you can use it in haskell. https://docs.rs/crate/notify -- description: homepage:            https://github.com/NoRedInk/hs-rs-notify#readme@@ -10,7 +10,7 @@ copyright:           2018 NoRedInk category:            Web build-type:          Simple-extra-source-files:  README.md+extra-source-files:  README.md, libnotifier.dylib cabal-version:       >=1.10  library@@ -24,8 +24,7 @@                      , unix == 2.7.2.1                      , process == 1.4.3.0   default-language:    Haskell2010-  default-extensions:  OverloadedStrings, NoImplicitPrelude-  extra-libraries:     notifier, pthread+  default-extensions:  OverloadedStrings, NoImplicitPrelude, NamedFieldPuns  test-suite hs-rs-notify-test   type:                exitcode-stdio-1.0
+ libnotifier.dylib view

binary file changed (absent → 2092136 bytes)

src/Notify.hs view
@@ -1,108 +1,103 @@ {-| Module      : Notify-Description : Notify's main module+Description : Filesystem notifications. This is a wrapper around rust's notify crate. -This is a haddock comment describing your library-For more information on how to write Haddock comments check the user guide:-<https://www.haskell.org/haddock/doc/html/index.html> -}-module Notify where+module Notify+  ( Config(..)+  , State+  , watch+  , force+  , end+  ) where  import Control.Concurrent-import Control.Exception (bracket) import Control.Monad (when)-import Data.Foldable-import Data.Text as T+import Data.Foldable (null, traverse_)+import Data.Int+import qualified Data.Text as T import Foreign.C.String-import Foreign.ForeignPtr+import Foreign.C.Types+import Foreign.ForeignPtr () import Foreign.Ptr-import Protolude+import Protolude hiding (State, force) import System.FilePath import System.Posix.Process import System.Posix.Signals import System.Posix.Types (ProcessID)-import System.Process--data Event-  = NoticeWrite FilePath-  | NoticeRemove FilePath-  | Create FilePath-  | Write FilePath-  | Chmod FilePath-  | Remove FilePath-  | Rename FilePath-           FilePath-  | Rescan-  | Error T.Text-          (Maybe FilePath)-  | Unknown-  deriving (Show)+import System.Process ()  foreign import ccall "watch_for_changes" watchForChanges ::-               CString -> FunPtr (CString -> CString -> CString -> IO ()) -> IO ()+               CString ->+                 CInt ->+                   FunPtr (CString -> IO ()) -> FunPtr (CString -> IO ()) -> IO ()  foreign import ccall "wrapper" mkCallback ::-               (CString -> CString -> CString -> IO ()) ->-                 IO (FunPtr (CString -> CString -> CString -> IO ()))+               (CString -> IO ()) -> IO (FunPtr (CString -> IO ())) -watch :: T.Text -> [T.Text] -> (Event -> IO ()) -> IO ()-watch path extensions callback = do+{-| Internal state of the watcher.+We keep track of running processes and the config.+You might need this if you want to use `end` or `force`.+-}+data State = State+  { onChange :: IO ()+  , mVar :: MVar (Maybe ProcessID)+  }++{-| Configuration for a watcher.+-}+data Config = Config+  { pathToWatch :: FilePath -- Watch files recursivelly under this path.+  , relevantExtensions :: [T.Text] -- Which extensions do we care about? Empty list will accept all.+  , debounceInSecs :: Int -- Debounce next run by x seconds.+  }++watch :: Config -> IO () -> (T.Text -> IO ()) -> IO State+watch config onChange onError = do   mVar <- newMVar Nothing-  cb <- mkCallback $ forkCallback mVar callback extensions-  pathCStr <- newCString $ T.unpack path-  watchForChanges pathCStr cb+  let state = State {onChange = onChange, mVar = mVar}+  _ <- forkIO (start mVar config onChange onError)+  pure state -forkCallback ::-     MVar (Maybe ProcessID)-  -> (Event -> IO ())-  -> [T.Text]-  -> CString-  -> CString-  -> CString-  -> IO ()-forkCallback mVar cb extensions eventC aC bC = do-  eventStr <- T.pack <$> peekCString eventC-  a <- T.pack <$> peekCString aC-  b <- T.pack <$> peekCString bC-  let event = toEvent eventStr a b-  when (relevantEvent event extensions) $ do-    runningProcess <- takeMVar mVar-    traverse_ (signalProcess softwareTermination) runningProcess-    traverse_ (getProcessStatus True False) runningProcess -- here be dragons, potentially-    processId <- forkProcess (cb event)-    putMVar mVar (Just processId)+force :: State -> IO ()+force State {mVar, onChange} = startProcess mVar onChange -toEvent :: T.Text -> T.Text -> T.Text -> Event-toEvent "NoticeWrite" _ b = NoticeWrite (T.unpack b)-toEvent "NoticeRemove" _ b = NoticeRemove (T.unpack b)-toEvent "Create" _ b = Create (T.unpack b)-toEvent "Write" _ b = Write (T.unpack b)-toEvent "Chmod" _ b = Chmod (T.unpack b)-toEvent "Remove" _ b = Remove (T.unpack b)-toEvent "Rename" a b = Rename (T.unpack a) (T.unpack b)-toEvent "Rescan" _ _ = Rescan-toEvent "Error" msg path =-  Error-    msg-    (case path of-       "" -> Nothing-       _ -> Just (T.unpack path))-toEvent _ _ _ = Unknown+end :: State -> IO ()+end State {mVar} = stopProcess mVar -relevantEvent :: Event -> [T.Text] -> Bool-relevantEvent event extensions =-  case eventForFile event of-    Just path -> elem (T.pack (takeExtension path)) extensions-    Nothing -> False+start :: MVar (Maybe ProcessID) -> Config -> IO () -> (T.Text -> IO ()) -> IO ()+start mVar Config {pathToWatch, debounceInSecs, relevantExtensions} onChange onError = do+  onChangeCb <- mkCallback $ callbackInProcess mVar onChange relevantExtensions+  onErrorCb <- mkCallback $ onErrorCallback onError+  pathCStr <- newCString pathToWatch+  watchForChanges pathCStr (mkCInt debounceInSecs) onChangeCb onErrorCb -eventForFile :: Event -> Maybe FilePath-eventForFile (NoticeWrite path) = Just path-eventForFile (NoticeRemove path) = Just path-eventForFile (Create path) = Just path-eventForFile (Write path) = Just path-eventForFile (Chmod path) = Just path-eventForFile (Remove path) = Just path-eventForFile (Rename _ path) = Just path-eventForFile Rescan = Nothing-eventForFile (Error _ maybePath) = maybePath-eventForFile Unknown = Nothing+mkCInt :: Int -> CInt+mkCInt = fromIntegral++onErrorCallback :: (T.Text -> IO ()) -> CString -> IO ()+onErrorCallback cb msgC = do+  msg <- peekCString msgC+  cb (T.pack msg)++callbackInProcess ::+     MVar (Maybe ProcessID) -> IO () -> [T.Text] -> CString -> IO ()+callbackInProcess mVar cb relevantExtensions pathC = do+  eventForPath <- peekCString pathC+  when (isRelevant eventForPath relevantExtensions || null relevantExtensions) $+    startProcess mVar cb++startProcess :: MVar (Maybe ProcessID) -> IO () -> IO ()+startProcess mVar cb = do+  stopProcess mVar+  processId <- forkProcess cb+  putMVar mVar (Just processId)++stopProcess :: MVar (Maybe ProcessID) -> IO ()+stopProcess mVar = do+  runningProcess <- takeMVar mVar+  traverse_ (signalProcess softwareTermination) runningProcess+  traverse_ (getProcessStatus True False) runningProcess -- here be dragons, potentially++isRelevant :: FilePath -> [T.Text] -> Bool+isRelevant path = elem (T.pack (takeExtension path))