{-# LANGUAGE TemplateHaskell #-} module LastRunState ( LastRunState , lrsHost , lrsPort , lrsUserId , lrsSelectedChannelId , writeLastRunState , readLastRunState , isValidLastRunState ) where import Prelude () import Prelude.MH 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 Lens.Micro.Platform ( makeLenses ) import System.Directory ( createDirectoryIfMissing ) import System.FilePath ( dropFileName ) import qualified System.Posix.Files as P import qualified System.Posix.Types as P import Network.Mattermost.Lenses import Network.Mattermost.Types import FilePaths import IOUtil import 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 :: ChannelId -- ^ ID of the last selected channel } 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 ] 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" makeLenses ''LastRunState toLastRunState :: ChatState -> LastRunState toLastRunState cs = LastRunState { _lrsHost = cs^.csResources.crConn.cdHostnameL , _lrsPort = cs^.csResources.crConn.cdPortL , _lrsUserId = myUserId cs , _lrsSelectedChannelId = cs^.csCurrentChannelId } 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. writeLastRunState :: ChatState -> IO () writeLastRunState cs = when (cs^.csCurrentChannel.ccInfo.cdType `elem` [Ordinary, Private]) $ do let runState = toLastRunState cs tId = myTeamId cs 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 = rs^.lrsHost == cr^.crConn.cdHostnameL && rs^.lrsPort == cr^.crConn.cdPortL && rs^.lrsUserId == me^.userIdL