{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Utils.Vigilance.TableOps ( allWatches , allWatchesEvent , AllWatchesEvent(..) , allWatchesS , createWatch , createWatchEvent , CreateWatchEvent(..) , createWatchS , deleteWatch , deleteWatchEvent , DeleteWatchEvent(..) , deleteWatchS , deleteFailedByWatch , deleteExcept , findWatch , findWatchEvent , FindWatchEvent(..) , findWatchS , pauseWatch , pauseWatchEvent , PauseWatchEvent(..) , pauseWatchS , unPauseWatch , unPauseWatchEvent , UnPauseWatchEvent(..) , unPauseWatchS , checkInWatch , checkInWatchEvent , CheckInWatchEvent(..) , checkInWatchS , watchLens , sweepTable , sweepTableEvent , SweepTableEvent(..) , sweepTableS , getNotifying , getNotifyingEvent , GetNotifyingEvent(..) , getNotifyingS , completeNotifying , completeNotifyingEvent , CompleteNotifyingEvent(..) , completeNotifyingS , mergeStaticWatches , mergeStaticWatchesEvent , MergeStaticWatchesEvent(..) , mergeStaticWatchesS , addFailedNotificationsEvent , AddFailedNotificationsEvent(..) , addFailedNotificationsS , allFailedNotificationsEvent , AllFailedNotificationsEvent(..) , allFailedNotificationsS , setFailedNotificationsEvent , SetFailedNotificationsEvent(..) , setFailedNotificationsS , fromList , getId , sWatchId , sWatchName , sWatchInterval , sWatchWState , sWatchNotifications , emptyTable) where import ClassyPrelude hiding (fromList) import Control.Lens import Data.Acid import Data.Acid.Advanced (update', query') import Data.List (foldl') import Data.Store.Lens (with) import Data.Store ((.==), (:.)(..), (.&&), (./=)) import qualified Data.Store as S import qualified Data.Store.Storable as SS import qualified Data.Store.Selection as SEL import Data.Time.Clock.POSIX (POSIXTime) import Utils.Vigilance.Sweeper (sweepWatch) import Utils.Vigilance.Types -- Selections sWatchId :: (WatchStoreTag, S.N0) sWatchId = (WatchStoreTag, S.n0) sWatchName :: (WatchStoreTag, S.N1) sWatchName = (WatchStoreTag, S.n1) sWatchInterval :: (WatchStoreTag, S.N2) sWatchInterval = (WatchStoreTag, S.n2) sWatchWState :: (WatchStoreTag, S.N3) sWatchWState = (WatchStoreTag, S.n3) sWatchNotifications :: (WatchStoreTag, S.N4) sWatchNotifications = (WatchStoreTag, S.n4) allWatches :: WatchTable -> [EWatch] allWatches = map ewatch . S.toList -- Helpers getId :: (ID :. t1) -> ID getId (wid :. _) = wid ewatch :: (ID :. i, NewWatch) -> EWatch ewatch (k, w) = w & watchId .~ getId k -- stateful? -- API createWatch :: NewWatch -> WatchTable -> (EWatch, WatchTable) createWatch w s = (w & watchId .~ getId k, s') where (k, s') = SS.insert' w s deleteWatch :: WatchName -> WatchTable -> WatchTable deleteWatch n = S.delete (sWatchName .== n) deleteFailedByWatch :: WatchName -> [FailedNotification] -> [FailedNotification] deleteFailedByWatch n = filter ((/=n) . getName) where getName = view $ failedWatch . watchName deleteExcept :: [WatchName] -> WatchTable -> WatchTable deleteExcept [] = id deleteExcept names = S.delete nameScope where nameScope = SEL.all $ map (sWatchName ./=) names findWatch :: WatchName -> WatchTable -> Maybe EWatch findWatch n = listToMaybe . map ewatch . S.lookup (sWatchName .== n) watchLens :: (NewWatch -> NewWatch) -> WatchName -> WatchTable -> WatchTable watchLens f n table = table & with (sWatchName .== n) %~ over mapped f checkInWatch :: POSIXTime -> WatchName -> WatchTable -> WatchTable checkInWatch time = watchLens doCheckIn where doCheckIn w = w & watchWState .~ (Active time) pauseWatch :: WatchName -> WatchTable -> WatchTable pauseWatch = watchLens pause where pause w = w & watchWState .~ Paused unPauseWatch :: POSIXTime -> WatchName -> WatchTable -> WatchTable unPauseWatch t = watchLens unPause where unPause w = w & watchWState %~ updateState updateState (Active newTime) = Active newTime updateState _ = Active t sweepTable :: POSIXTime -> WatchTable -> WatchTable sweepTable time = SS.map sweep where sweep = sweepWatch time getNotifying :: WatchTable -> [EWatch] getNotifying = map ewatch . S.lookup (sWatchWState .== Notifying) completeNotifying :: [WatchName] -> WatchTable -> WatchTable completeNotifying [] table = table completeNotifying names table = SS.update' (Just . updateState) scope table where updateState w = w & watchWState .~ Triggered scope = nameScope .&& isNotifyingScope nameScope = SEL.any $ map (sWatchName .==) names isNotifyingScope = sWatchWState .== Notifying mergeStaticWatches :: [NewWatch] -> WatchTable -> WatchTable mergeStaticWatches [] = const emptyTable mergeStaticWatches watches = mergeWatches . removeUnMentioned where removeUnMentioned = deleteExcept names names = map (view watchName) watches mergeWatches table = foldl' mergeWatchIn table watches mergeWatchIn table' staticW = fromMaybe forceUpdate tryInsert where forceUpdate = SS.update' (Just . mergeWatch) (sWatchName .== (staticW ^. watchName)) table' mergeWatch eWatch = eWatch & watchInterval .~ (staticW ^. watchInterval) & watchNotifications .~ (staticW ^. watchNotifications) tryInsert = snd <$> SS.insert staticW table' emptyTable :: WatchTable emptyTable = S.empty fromList :: [NewWatch] -> WatchTable fromList = SS.fromList' -- ACID State allWatchesEvent :: Query AppState [EWatch] allWatchesEvent = view (wTable . to allWatches) createWatchEvent :: NewWatch -> Update AppState EWatch createWatchEvent w = wTable %%= createWatch w deleteWatchEvent :: WatchName -> Update AppState () deleteWatchEvent n = wTable %= deleteWatch n >> failed %= deleteFailedByWatch n findWatchEvent :: WatchName -> Query AppState (Maybe EWatch) findWatchEvent i = view (wTable . findWatch') where findWatch' = to $ findWatch i checkInWatchEvent :: POSIXTime -> WatchName -> Update AppState () checkInWatchEvent t i = wTable %= checkInWatch t i pauseWatchEvent :: WatchName -> Update AppState () pauseWatchEvent i = wTable %= pauseWatch i unPauseWatchEvent :: POSIXTime -> WatchName -> Update AppState () unPauseWatchEvent t i = wTable %= unPauseWatch t i sweepTableEvent :: POSIXTime -> Update AppState () sweepTableEvent t = wTable %= sweepTable t getNotifyingEvent :: Query AppState [EWatch] getNotifyingEvent = view (wTable . to getNotifying) completeNotifyingEvent :: [WatchName] -> Update AppState () completeNotifyingEvent is = wTable %= completeNotifying is mergeStaticWatchesEvent :: [NewWatch] -> Update AppState () mergeStaticWatchesEvent watches = wTable %= mergeStaticWatches watches addFailedNotificationsEvent :: [FailedNotification] -> Update AppState () addFailedNotificationsEvent ns = failed <>= ns allFailedNotificationsEvent :: Query AppState [FailedNotification] allFailedNotificationsEvent = view failed setFailedNotificationsEvent :: [FailedNotification] -> Update AppState () setFailedNotificationsEvent ns = failed .= ns $(makeAcidic ''AppState [ 'allWatchesEvent , 'createWatchEvent , 'deleteWatchEvent , 'findWatchEvent , 'checkInWatchEvent , 'pauseWatchEvent , 'unPauseWatchEvent , 'sweepTableEvent , 'getNotifyingEvent , 'completeNotifyingEvent , 'mergeStaticWatchesEvent , 'addFailedNotificationsEvent , 'allFailedNotificationsEvent , 'setFailedNotificationsEvent ]) allWatchesS :: (QueryEvent AllWatchesEvent, MonadIO m) => AcidState (EventState AllWatchesEvent) -> m [EWatch] allWatchesS acid = query' acid AllWatchesEvent createWatchS :: (UpdateEvent CreateWatchEvent, MonadIO m) => AcidState (EventState CreateWatchEvent) -> NewWatch -> m EWatch createWatchS acid = update' acid . CreateWatchEvent deleteWatchS :: (UpdateEvent DeleteWatchEvent, MonadIO m) => AcidState (EventState DeleteWatchEvent) -> WatchName -> m () deleteWatchS acid = update' acid . DeleteWatchEvent findWatchS :: (QueryEvent FindWatchEvent, MonadIO m) => AcidState (EventState FindWatchEvent) -> WatchName -> m (Maybe EWatch) findWatchS acid = query' acid . FindWatchEvent checkInWatchS :: (UpdateEvent CheckInWatchEvent, MonadIO m) => AcidState (EventState CheckInWatchEvent) -> POSIXTime -> WatchName -> m () checkInWatchS acid t = update' acid . CheckInWatchEvent t pauseWatchS :: (UpdateEvent PauseWatchEvent, MonadIO m) => AcidState (EventState PauseWatchEvent) -> WatchName -> m () pauseWatchS acid = update' acid . PauseWatchEvent unPauseWatchS :: (UpdateEvent UnPauseWatchEvent, MonadIO m) => AcidState (EventState UnPauseWatchEvent) -> POSIXTime -> WatchName -> m () unPauseWatchS acid t = update' acid . UnPauseWatchEvent t sweepTableS :: (UpdateEvent SweepTableEvent, MonadIO m) => AcidState (EventState SweepTableEvent) -> POSIXTime -> m () sweepTableS acid = update' acid . SweepTableEvent getNotifyingS :: (QueryEvent GetNotifyingEvent, MonadIO m) => AcidState (EventState GetNotifyingEvent) -> m [EWatch] getNotifyingS acid = query' acid GetNotifyingEvent completeNotifyingS :: (UpdateEvent CompleteNotifyingEvent, MonadIO m) => AcidState (EventState CompleteNotifyingEvent) -> [WatchName] -> m () completeNotifyingS acid = update' acid . CompleteNotifyingEvent mergeStaticWatchesS :: (UpdateEvent CompleteNotifyingEvent, MonadIO m) => AcidState (EventState MergeStaticWatchesEvent) -> [NewWatch] -> m () mergeStaticWatchesS acid = update' acid . MergeStaticWatchesEvent addFailedNotificationsS :: (UpdateEvent AddFailedNotificationsEvent, MonadIO m) => AcidState (EventState AddFailedNotificationsEvent) -> [FailedNotification] -> m () addFailedNotificationsS acid = update' acid . AddFailedNotificationsEvent allFailedNotificationsS :: (QueryEvent AllFailedNotificationsEvent, MonadIO m) => AcidState (EventState AllFailedNotificationsEvent) -> m [FailedNotification] allFailedNotificationsS acid = query' acid AllFailedNotificationsEvent setFailedNotificationsS :: (UpdateEvent SetFailedNotificationsEvent, MonadIO m) => AcidState (EventState SetFailedNotificationsEvent) -> [FailedNotification] -> m () setFailedNotificationsS acid = update' acid . SetFailedNotificationsEvent