{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Filesystem watching using fsnotify
module Rib.Watch
  ( onTreeChange,
  )
where

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (race)
import Control.Concurrent.Chan
import Relude
import System.FSNotify (Event (..), watchTreeChan, withManager)

-- | Recursively monitor the contents of the given path and invoke the given IO
-- action for every event triggered.
--
-- If multiple events fire rapidly, the IO action is invoked only once, taking
-- those multiple events as its argument.
onTreeChange :: FilePath -> ([Event] -> IO ()) -> IO ()
onTreeChange fp f = do
  withManager $ \mgr -> do
    eventCh <- newChan
    void $ watchTreeChan mgr fp (const True) eventCh
    forever $ do
      firstEvent <- readChan eventCh
      events <- debounce 100 [firstEvent] $ readChan eventCh
      f events

debounce :: Int -> [event] -> IO event -> IO [event]
debounce millies events f = do
  -- Race the readEvent against the timelimit.
  race f (threadDelay (1000 * millies)) >>= \case
    Left event ->
      -- If the read event finishes first try again.
      debounce millies (events <> [event]) f
    Right () ->
      -- Otherwise continue
      return events