packages feed

monomer-1.5.0.0: src/Monomer/Main/WidgetTask.hs

{-|
Module      : Monomer.Main.WidgetTask
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Handles the lifecycle and reporting of generated events of WidgetTasks (single
message) and Producers (multiple messages).
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Strict #-}

module Monomer.Main.WidgetTask (handleWidgetTasks) where

import Control.Concurrent.Async (poll)
import Control.Concurrent.STM.TChan (tryReadTChan)
import Control.Exception.Base
import Control.Lens ((&), (^.), (.=), use)
import Control.Monad.Extra
import Control.Monad.IO.Class
import Control.Monad.STM (atomically)
import Data.Foldable (toList)
import Data.Maybe
import Data.Typeable

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Helper (collectJustM, putStrLnErr)
import Monomer.Main.Handlers
import Monomer.Main.Lens
import Monomer.Main.Util
import Monomer.Main.Types

import qualified Monomer.Core.Lens as L

-- | Checks the status and collects results of active tasks.
handleWidgetTasks
  :: MonomerM s e m
  => WidgetEnv s e        -- ^ The widget environment.
  -> WidgetNode s e       -- ^ The widget root.
  -> m (HandlerStep s e)  -- ^ The updated "Monomer.Main.Handlers.HandlerStep".
handleWidgetTasks wenv widgetRoot = do
  tasks <- use widgetTasks
  (active, finished) <- partitionM isThreadActive (toList tasks)
  widgetTasks .= Seq.fromList active

  processTasks wenv widgetRoot tasks

processTasks
  :: (MonomerM s e m, Traversable t)
  => WidgetEnv s e
  -> WidgetNode s e
  -> t WidgetTask
  -> m (HandlerStep s e)
processTasks wenv widgetRoot tasks = nextStep where
  reducer (wenv1, root1, reqs1) task = do
    (wenv2, root2, reqs2) <- processTask wenv1 root1 task
    return (wenv2, root2, reqs1 <> reqs2)
  nextStep = foldM reducer (wenv, widgetRoot, Seq.empty) tasks

processTask
  :: MonomerM s e m
  => WidgetEnv s e
  -> WidgetNode s e
  -> WidgetTask
  -> m (HandlerStep s e)
processTask wenv widgetRoot (WidgetTask widgetId task) = do
  taskStatus <- liftIO $ poll task

  case taskStatus of
    Just taskRes -> processTaskResult wenv widgetRoot widgetId taskRes
    Nothing -> return (wenv, widgetRoot, Seq.empty)
processTask wenv widgetRoot (WidgetProducer widgetId channel task) = do
  channelStatus <- collectJustM . liftIO . atomically $ tryReadTChan channel

  foldM processMsg (wenv, widgetRoot, Seq.empty) channelStatus
  where
    processMsg (wenv1, root1, reqs1) taskMsg = do
      (wenv2, root2, reqs2) <- processTaskEvent wenv1 root1 widgetId taskMsg
      return (wenv2, root2, reqs1 <> reqs2)

processTaskResult
  :: (MonomerM s e m, Typeable i)
  => WidgetEnv s e
  -> WidgetNode s e
  -> WidgetId
  -> Either SomeException i
  -> m (HandlerStep s e)
processTaskResult wenv widgetRoot _ (Left ex) = do
  liftIO . putStrLnErr $ "Error processing Widget task result: " ++ show ex
  return (wenv, widgetRoot, Seq.empty)
processTaskResult wenv widgetRoot widgetId (Right taskResult)
  = processTaskEvent wenv widgetRoot widgetId taskResult

processTaskEvent
  :: (MonomerM s e m, Typeable i)
  => WidgetEnv s e
  -> WidgetNode s e
  -> WidgetId
  -> i
  -> m (HandlerStep s e)
processTaskEvent wenv widgetRoot widgetId event = do
  path <- getWidgetIdPath widgetId

  let emptyResult = WidgetResult widgetRoot Seq.empty
  let widget = widgetRoot ^. L.widget
  let msgResult = widgetHandleMessage widget wenv widgetRoot path event
  let widgetResult = fromMaybe emptyResult msgResult

  handleWidgetResult wenv True widgetResult

isThreadActive :: MonomerM s e m => WidgetTask -> m Bool
isThreadActive (WidgetTask _ task) = fmap isNothing (liftIO $ poll task)
isThreadActive (WidgetProducer _ _ task) = fmap isNothing (liftIO $ poll task)

taskWidgetId :: WidgetTask -> WidgetId
taskWidgetId (WidgetTask widgetId _) = widgetId
taskWidgetId (WidgetProducer widgetId _ _) = widgetId