aboutsummaryrefslogtreecommitdiff
path: root/server/src/Thermoprint/Server/QMConfig.hs
blob: 7255c8ce1a538e54cfaa09bfbdf778713edb8a56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{-# LANGUAGE GADTs              #-}
{-# LANGUAGE FlexibleContexts   #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE TypeOperators      #-}
{-# LANGUAGE RecordWildCards    #-}
{-# LANGUAGE ViewPatterns       #-}
{-# LANGUAGE ConstraintKinds    #-}
{-# LANGUAGE MultiParamTypeClasses       #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE FlexibleInstances       #-}
{-# LANGUAGE UndecidableInstances       #-}
{-# LANGUAGE IncoherentInstances       #-}

module Thermoprint.Server.QMConfig
       ( QMConfig(..)
       , nullQM
       , idQM
       , standardSleep
       , limitHistorySize
       , limitHistoryAge
       ) where

import Control.Monad.Trans.Resource
import Control.Monad.Trans.Control
import Control.Monad.Trans.Identity
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.IO.Class
import Control.Monad.Morph
import Control.Monad.Trans.Compose
import Control.Monad.State
import Control.Category
import Servant.Server.Internal.Enter

import Data.Constraint
import Data.Constraint.Forall

import Data.Default.Class

import           Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.Time
import Data.Foldable

import Control.Concurrent.STM

import Thermoprint.Server.Queue hiding (idQM, intersection, nullQM, union, runQM)
import qualified Thermoprint.Server.Queue as Q

import Thermoprint.Server.Database (JobId)

import Prelude hiding (length, maximum, id, (.))

type QMTrans t = ( MonadTrans t
                 , MFunctor t
                 , Monad (t STM)
                 , MonadIO (t IO)
                 , Monad (QueueManagerM t)
                 , MonadState Queue (QueueManagerM t)
                 )

class QMTrans1 (t :: (* -> *) -> * -> *) (t' :: (* -> *) -> * -> *)
instance QMTrans (ComposeT t t') => QMTrans1 t t'

type QMTrans' t = (QMTrans t, Forall (QMTrans1 t))

data QMConfig where
  QMConfig :: QMTrans' t => QueueManager t -> (forall m. (t m) :~> m) -> QMConfig
  QMConfig' :: ( forall t . QMTrans' t => QueueManager t
               ) -> QMConfig

instance Default QMConfig where
  def = idQM

intersection :: Foldable f => f QMConfig -> QMConfig
intersection = foldr' (qmCombine Q.intersection) idQM

idQM :: QMConfig
idQM = QMConfig' Q.idQM

union :: Foldable f => f QMConfig -> QMConfig
union = foldr' (qmCombine Q.union) idQM
  
nullQM :: QMConfig
nullQM = QMConfig' Q.nullQM

qmCombine :: (forall f t. (Foldable f, MonadState Queue (QueueManagerM t)) => f (QueueManager t) -> QueueManager t) -> QMConfig -> QMConfig -> QMConfig
qmCombine qmCombine' (QMConfig a natA) (QMConfig b natB) = (QMConfig (qmCombine' [a', b']) (natComp natA natB)) \\ (inst :: Forall (QMTrans1 t) :- QMTrans (ComposeT t t'))
  where
    a' = mapComposeT (hoist $ ComposeT . hoist lift) a
    b' = mapComposeT (hoist $ ComposeT . lift) b
    
natComp :: (forall m. t m :~> m)
           -> (forall m. t' m :~> m)
           -> (forall m. (MFunctor t
                         , Monad (t' m)
                         ) => ComposeT t t' m :~> m
              )
natComp natA natB = natA . hoistNat natB . Nat getComposeT

runQM :: ( HasQueue q ) => TChan JobId -> QMConfig -> q -> IO ()
runQM gcChan (QMConfig qm nat) q = unNat nat $ Q.runQM gcChan qm q
runQM gcChan (QMConfig' qm) q = runIdentityT $ Q.runQM gcChan qm q

standardSleep :: Monad (QueueManagerM t) => QueueManager t
-- ^ Instruct 'runQM' to sleep some standard amount of time
--
-- /TODO/: Investigate implementing a smarter algorithm (PID-controller)
standardSleep = return $ 20

limitHistorySize :: Int -> QMConfig
-- ^ Limit a 'Queue's 'history's size to some number of 'QueueEntry's
limitHistorySize max = QMConfig' $ modify' limitSize >> standardSleep
  where
    limitSize :: Queue -> Queue
    limitSize q@Queue{..} = q { history = Seq.take max history }

limitHistoryAge :: NominalDiffTime -- ^ Maximum age relative to the youngest 'QueueEntry'
                -> QMConfig
-- ^ Limit a 'Queue's 'history's contents to 'QueueEntry's below a certain age
limitHistoryAge  maxAge = QMConfig' $ modify' limitAge >> standardSleep
  where
    limitAge :: Queue -> Queue
    limitAge q@Queue{..} = q { history = Seq.filter (filterAge . created . fst) history}
      where
        youngest :: UTCTime
        youngest = maximum $ created . fst <$> history
        filterAge :: UTCTime -> Bool
        filterAge time = not $ (youngest `diffUTCTime` time) > maxAge