{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DuplicateRecordFields #-} module QBar.Host where import QBar.BlockOutput import QBar.Core import Control.Concurrent (forkIO, forkFinally, threadDelay) import Control.Concurrent.Event as Event import Control.Concurrent.STM.TChan (TChan, newTChanIO, tryReadTChan) import Control.Exception (SomeException, catch) import Control.Lens hiding (each, (.=)) import Control.Monad (when) import Control.Monad.STM (atomically) import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.Maybe (catMaybes, mapMaybe) import qualified Data.Text.Lazy as T import Pipes import System.IO (stderr, hPutStrLn) import System.Posix.Signals data HostHandle = HostHandle { barUpdateEvent :: BarUpdateEvent, newBlockChan :: TChan CachedBlock, eventHandlerListIORef :: IORef [(T.Text, BlockEventHandler)] } installSignalHandlers :: Bar -> IO () installSignalHandlers bar = void $ installHandler sigCONT (Catch sigContAction) Nothing where sigContAction :: IO () sigContAction = do hPutStrLn stderr "SIGCONT received" updateBar' bar eventDispatcher :: Bar -> IORef [(T.Text, BlockEventHandler)] -> Consumer BlockEvent IO () eventDispatcher bar eventHandlerListIORef = eventDispatcher' where eventDispatcher' :: Consumer BlockEvent IO () eventDispatcher' = do blockEvent <- await eventHandlerList <- liftIO $ readIORef eventHandlerListIORef let maybeEventHandler = getEventHandler eventHandlerList blockEvent case maybeEventHandler of Just eventHandler -> liftIO . void . forkIO $ catch (runBarIO bar $ eventHandler blockEvent) (\(e :: SomeException) -> hPutStrLn stderr $ "event handler failed: " <> show e) Nothing -> return () eventDispatcher' getEventHandler :: [(T.Text, BlockEventHandler)] -> BlockEvent -> Maybe BlockEventHandler getEventHandler eventHandlerList blockEvent = lookup (name blockEvent) eventHandlerList runBlocks :: Bar -> HostHandle -> Producer [BlockOutput] IO () runBlocks bar HostHandle{barUpdateEvent, newBlockChan, eventHandlerListIORef} = runBlocks' [] where runBlocks' :: [CachedBlock] -> Producer [BlockOutput] IO () runBlocks' blocks = do liftIO $ do -- Wait for an update request Event.wait barUpdateEvent -- Wait for 10ms after first events to catch (almost-)simultaneous event updates threadDelay 10000 Event.clear barUpdateEvent blocks' <- liftIO $ runBarIO bar $ addNewBlocks blocks (blockStates, blocks'') <- liftIO $ runBarIO bar $ getBlockStates blocks' -- Pass blocks to output yield $ map fst $ catMaybes blockStates -- Register new event handlers immediately after rendering liftIO $ updateEventHandlers blockStates -- Wait for 90ms after rendering a line to limit cpu load of rapid events liftIO $ threadDelay 90000 -- Loop runBlocks' blocks'' addNewBlocks :: [CachedBlock] -> BarIO [CachedBlock] addNewBlocks blocks = do maybeNewBlock <- liftIO $ atomically $ tryReadTChan newBlockChan case maybeNewBlock of Nothing -> return blocks Just newBlock -> addNewBlocks (newBlock:blocks) getBlockStates :: [CachedBlock] -> BarIO ([BlockState], [CachedBlock]) getBlockStates blocks = unzip . catMaybes <$> mapM getBlockState blocks getBlockState :: CachedBlock -> BarIO (Maybe (BlockState, CachedBlock)) getBlockState producer = do next' <- next producer return $ case next' of Left _ -> Nothing Right (blockState, newProducer) -> Just (blockState, newProducer) updateEventHandlers :: [BlockState] -> IO () updateEventHandlers blockStates = writeIORef eventHandlerListIORef eventHandlerList where eventHandlerList :: [(T.Text, BlockEventHandler)] eventHandlerList = mapMaybe getEventHandler $ blockStates getEventHandler :: BlockState -> Maybe (T.Text, BlockEventHandler) getEventHandler Nothing = Nothing getEventHandler (Just (_, Nothing)) = Nothing getEventHandler (Just (blockOutput, Just eventHandler)) = do blockName' <- blockOutput^.blockName return (blockName', eventHandler) filterDuplicates :: (Monad m, Eq a) => Pipe a a m r filterDuplicates = do value <- await yield value filterDuplicates' value where filterDuplicates' :: (Monad m, Eq a) => a -> Pipe a a m r filterDuplicates' lastValue = do value <- await when (lastValue /= value) $ yield value filterDuplicates' value runBarHost :: Consumer [BlockOutput] IO () -> Producer BlockEvent IO () -> BarIO () -> IO () runBarHost host barEventProducer loadBlocks = do -- Create an event used to signal bar updates barUpdateEvent <- Event.newSet let requestBarUpdate = Event.set barUpdateEvent -- Create channel to send new block producers to render loop newBlockChan <- newTChanIO let bar = Bar { requestBarUpdate, newBlockChan } -- Install signal handler for SIGCONT installSignalHandlers bar -- Create IORef for event handlers eventHandlerListIORef <- newIORef [] let hostHandle = HostHandle { barUpdateEvent, newBlockChan, eventHandlerListIORef } runBarIO bar loadBlocks let handleStdin = liftIO $ runEffect $ barEventProducer >-> eventDispatcher bar eventHandlerListIORef -- Fork stdin handler void $ forkFinally (runBarIO bar handleStdin) (\result -> hPutStrLn stderr $ "handleStdin failed: " <> show result) -- Run bar host runEffect $ runBlocks bar hostHandle >-> filterDuplicates >-> host