packages feed

tricorder-0.5.0.0: src/Tricorder/Session/StackProject.hs

module Tricorder.Session.StackProject
    ( StackProject (..)
    , inputWithCache
    )
where

import Atelier.Effects.FileSystem (FileSystem)
import Atelier.Effects.Input (Input, runInputEff)
import Data.Aeson (FromJSON, ToJSON)
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Effectful.Concurrent.MVar.Strict (Concurrent)
import Effectful.Concurrent.STM
    ( TVar
    , atomically
    , newTVarIO
    , readTVar
    , writeTVar
    )
import Effectful.Exception (throwIO)
import Effectful.Reader.Static (Reader, ask)
import GHC.Generics (Generically (..))
import System.FilePath ((</>))
import System.IO.Error (userError)

import Atelier.Effects.FileSystem qualified as FileSystem
import Data.Yaml qualified as Yaml

import Tricorder.Runtime (ProjectRoot (..))


newtype StackProject = StackProject
    { packages :: [FilePath]
    }
    deriving stock (Eq, Generic, Show)
    deriving (FromJSON, ToJSON) via Generically StackProject


inputWithCache
    :: ( Concurrent :> es
       , FileSystem :> es
       , Reader ProjectRoot :> es
       )
    => Eff (Input StackProject : es) a -> Eff es a
inputWithCache act = do
    cache :: TVar StackProject <- newTVarIO $ StackProject []
    lastModified :: TVar UTCTime <- newTVarIO $ posixSecondsToUTCTime 0
    flip runInputEff act do
        ProjectRoot projectRoot <- ask
        let stackYamlPath = projectRoot </> "stack.yaml"
        exists <- FileSystem.doesFileExist stackYamlPath
        if not exists
            then throwIO $ userError "stack.yaml does not exist"
            else do
                newLastMod <- FileSystem.getModificationTime stackYamlPath
                oldLastMod <- atomically $ readTVar lastModified
                if oldLastMod /= newLastMod
                    then do
                        contents <- FileSystem.readFileBs stackYamlPath
                        let result = Yaml.decodeEither' contents
                        case result of
                            Right x -> do
                                atomically do
                                    writeTVar cache x
                                    writeTVar lastModified newLastMod
                                pure x
                            Left err ->
                                throwIO $ userError $ "Could not parse stack.yaml: " <> show err
                    else
                        atomically $ readTVar cache