matterhorn-90000.0.0: src/Matterhorn/LastRunState.hs
{-# LANGUAGE TemplateHaskell #-}
module Matterhorn.LastRunState
( LastRunState
, lrsHost
, lrsPort
, lrsUserId
, lrsSelectedChannelId
, lrsOpenThread
, writeLastRunStates
, readLastRunState
, isValidLastRunState
)
where
import Prelude ()
import Matterhorn.Prelude
import Control.Monad.Trans.Except
import qualified Data.Aeson as A
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.HashMap.Strict as HM
import Lens.Micro.Platform ( makeLenses )
import System.Directory ( createDirectoryIfMissing )
import System.FilePath ( dropFileName )
import qualified System.PosixCompat.Files as P
import qualified System.PosixCompat.Types as P
import Network.Mattermost.Lenses
import Network.Mattermost.Types
import Matterhorn.FilePaths
import Matterhorn.IOUtil
import Matterhorn.Types
-- | Run state of the program. This is saved in a file on program exit and
-- | looked up from the file on program startup.
data LastRunState =
LastRunState { _lrsHost :: Hostname -- ^ Host of the server
, _lrsPort :: Port -- ^ Post of the server
, _lrsUserId :: UserId -- ^ ID of the logged-in user
, _lrsSelectedChannelId :: Maybe ChannelId -- ^ ID of the last selected channel
, _lrsOpenThread :: Maybe (ChannelId, PostId)
}
instance A.ToJSON LastRunState where
toJSON lrs = A.object [ "host" A..= _lrsHost lrs
, "port" A..= _lrsPort lrs
, "user_id" A..= _lrsUserId lrs
, "sel_channel_id" A..= _lrsSelectedChannelId lrs
, "open_thread" A..= _lrsOpenThread lrs
]
instance A.FromJSON LastRunState where
parseJSON = A.withObject "LastRunState" $ \v ->
LastRunState
<$> v A..: "host"
<*> v A..: "port"
<*> v A..: "user_id"
<*> v A..: "sel_channel_id"
<*> v A..:? "open_thread"
makeLenses ''LastRunState
toLastRunState :: ChatState -> TeamId -> LastRunState
toLastRunState cs tId =
LastRunState { _lrsHost = cs^.csResources.crConn.cdHostnameL
, _lrsPort = cs^.csResources.crConn.cdPortL
, _lrsUserId = myUserId cs
, _lrsSelectedChannelId = cs^.csCurrentChannelId(tId)
, _lrsOpenThread = do
ti <- cs^.csTeam(tId).tsThreadInterface
return (ti^.miChannelId, ti^.miRootPostId)
}
lastRunStateFileMode :: P.FileMode
lastRunStateFileMode = P.unionFileModes P.ownerReadMode P.ownerWriteMode
-- | Writes the run state to a file. The file is specific to the current team.
-- | Writes only if the current channel is an ordrinary or a private channel.
writeLastRunStates :: ChatState -> IO ()
writeLastRunStates cs =
forM_ (HM.keys $ cs^.csTeams) $ \tId ->
writeLastRunState cs tId
writeLastRunState :: ChatState -> TeamId -> IO ()
writeLastRunState cs tId =
case cs^.csCurrentChannelId(tId) of
Nothing -> return ()
Just cId -> case cs^?csChannel(cId) of
Nothing -> return ()
Just chan ->
when (chan^.ccInfo.cdType `elem` [Ordinary, Private]) $ do
let runState = toLastRunState cs tId
lastRunStateFile <- lastRunStateFilePath $ unId $ toId tId
createDirectoryIfMissing True $ dropFileName lastRunStateFile
BS.writeFile lastRunStateFile $ LBS.toStrict $ A.encode runState
P.setFileMode lastRunStateFile lastRunStateFileMode
-- | Reads the last run state from a file given the current team ID.
readLastRunState :: TeamId -> IO (Either String LastRunState)
readLastRunState tId = runExceptT $ do
contents <- convertIOException $
lastRunStateFilePath (unId $ toId tId) >>= BS.readFile
case A.eitherDecodeStrict' contents of
Right val -> return val
Left err -> throwE $ "Failed to parse lastRunState file: " ++ err
-- | Checks if the given last run state is valid for the current server and user.
isValidLastRunState :: ChatResources -> User -> LastRunState -> Bool
isValidLastRunState cr me rs =
and [ rs^.lrsHost == cr^.crConn.cdHostnameL
, rs^.lrsPort == cr^.crConn.cdPortL
, rs^.lrsUserId == me^.userIdL
]