aboutsummaryrefslogtreecommitdiff
path: root/server/src/Thermoprint/Server/Queue.hs
blob: 17865b6e8597cb7c1ec832fc633a291b0012f572 (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
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
{-# LANGUAGE ExistentialQuantification #-}

module Thermoprint.Server.Queue
       ( Queue(..), QueueEntry(..)
       , HasQueue(..)
       , QueueManager(..), runQM
       ) where

import           Thermoprint.API (PrintingError(..), Printout)
import qualified Thermoprint.API as API (JobStatus(..))

import           Thermoprint.Server.Database

import           Data.Sequence (Seq, ViewL(..), viewl, (|>), (<|))
import qualified Data.Sequence as Seq

import           Data.Set (Set)
import qualified Data.Set as Set

import           Data.Time
import           Data.Time.Clock

import           Control.DeepSeq (NFData)
import           Data.Typeable (Typeable)
import           GHC.Generics (Generic)

import           Control.Concurrent
import           Control.Concurrent.STM
import           Control.Monad.State

import           Data.Default.Class

import           Control.Monad.Morph
import           Control.Monad.Trans.Compose

import           Data.Foldable
import           Data.Monoid

-- | Zipper for 'Seq QueueEntry' with additional support for 'PrintingError' in the section after point
data Queue = Queue
  { pending :: Seq QueueEntry -- ^ Pending jobs, closest last
  , current :: Maybe QueueEntry
  , history :: Seq (QueueEntry, Maybe PrintingError) -- ^ Completed jobs, closest first
  }
  deriving (Typeable, Generic, NFData)

data QueueElem = Pending QueueEntry | Current QueueEntry | History QueueEntry (Maybe PrintingError)

instance Eq QueueElem where
  (Pending a) == (Pending b) = a == b
  (Current a) == (Current b) = a == b
  (History a _) == (History b _) = a == b
  _ == _ = False

instance Ord QueueElem where
  (Pending _) <= _ = True
  (Current _) <= (Pending _) = False
  (Current _) <= _ = True
  (History _ _) <= (History _ _) = True
  (History _ _) <= _ = False

toSeq :: Queue -> Set (Int, QueueElem)
toSeq Queue{..} = fromSeq $ fmap Pending pending <> maybe Seq.empty (Seq.singleton . Current) current <> fmap (uncurry History) history
  where
    fromSeq = Set.fromAscList . toList . Seq.mapWithIndex ((,))

fromSeq :: Set (Int, QueueElem) -> Queue
fromSeq = foldr' (insert . snd) def
  where
    insert (Pending e) q@(Queue{..}) = q { pending = pending |> e }
    insert (Current e) q = q { current = Just e }
    insert (History e p) q@(Queue{..}) = q { history = history |> (e, p) }

class HasQueue a where
  extractQueue :: a -> TVar Queue

instance HasQueue (TVar Queue) where
  extractQueue = id

instance Default Queue where
  def = Queue
    { pending = Seq.empty
    , current = Nothing
    , history = Seq.empty
    }

data QueueEntry = QueueEntry
  { jobId :: JobId
  , created :: UTCTime
  }
  deriving (Typeable, Generic, NFData, Eq)

-- | A queue manager periodically modifies a 'Queue', e.g. for cleanup of old jobs
type QueueManager t = ComposeT (StateT Queue) t STM DiffTime

runQM :: ( HasQueue q
         , MFunctor t
         , MonadTrans t
         , MonadIO (t IO)
         , Monad (t STM)
         ) => QueueManager t -> q -> t IO ()
-- ^ Periodically modify a 'Queue'
runQM qm (extractQueue -> q) = forever $ liftIO . threadDelay . toMicro =<< qm'
  where
    qm' = hoist atomically $ (\(a, s) -> lift (writeTVar q s) >> return a) =<< runStateT (getComposeT qm) =<< lift (readTVar q)
    toMicro = (`div` 10^6) . fromEnum