packages feed

tricorder-0.2.0.1: src/Tricorder/SourceLookup/PackageStore.hs

module Tricorder.SourceLookup.PackageStore
    ( PackageStore (..)
    , add
    , getPath
    , run
    ) where

import Atelier.Effects.Env (Env, getEnvironment)
import Atelier.Effects.FileSystem (FileSystem)
import Effectful (Effect)
import Effectful.Dispatch.Dynamic (interpretWith_)
import Effectful.NonDet (NonDet, OnEmptyPolicy (..), emptyEff, plusEff, runNonDet)
import Effectful.TH (makeEffect)
import System.FilePath (takeDirectory, (</>))

import Atelier.Effects.FileSystem qualified as FileSystem
import Data.Map.Strict qualified as Map

import Tricorder.Module (PackageId, splitPackageId, unPackageId)


data PackageStore :: Effect where
    Add :: PackageId -> ByteString -> PackageStore m FilePath
    GetPath :: PackageId -> PackageStore m (Maybe FilePath)


makeEffect ''PackageStore


run :: (Env :> es, FileSystem :> es) => Eff (PackageStore : es) a -> Eff es a
run act = do
    storeBaseDir <- findStoreBaseDir
    let packageDir = storeBaseDir </> "packages" </> hackageRepo
    FileSystem.createDirectoryIfMissing True packageDir
    interpretWith_ act \case
        Add packageId bytes -> do
            let path = packagePath packageDir packageId
            exists <- FileSystem.doesPathExist path
            unless exists do
                FileSystem.createDirectoryIfMissing True (takeDirectory path)
                FileSystem.writeFileBS path bytes
            pure path
        GetPath packageId -> do
            let path = packagePath packageDir packageId
            exists <- FileSystem.doesPathExist path
            if exists then
                pure $ Just path
            else
                pure Nothing


packagePath :: FilePath -> PackageId -> FilePath
packagePath packageDir packageId =
    packageDir
        </> toString packageName
        </> toString packageVersion
        </> toString (unPackageId packageId <> ".tar.gz")
  where
    (packageName, packageVersion) = splitPackageId packageId


hackageRepo :: FilePath
hackageRepo = "hackage.haskell.org"


findStoreBaseDir :: (Env :> es, FileSystem :> es) => Eff es FilePath
findStoreBaseDir = do
    env <- Map.fromList <$> getEnvironment
    fmap (fromMaybe tempFallback . rightToMaybe)
        $ runNonDet OnEmptyKeep
        $ findCabalDirCandidate env
            `plusEff` findXdgCandidate env
            `plusEff` findHomeCandidate env
            `plusEff` findFallback env


findCabalDirCandidate
    :: (FileSystem :> es, NonDet :> es)
    => Map String String -> Eff es FilePath
findCabalDirCandidate env =
    case Map.lookup "CABAL_DIR" env of
        Nothing -> emptyEff
        Just cabalDir -> getDir cabalDir


findXdgCandidate
    :: (FileSystem :> es, NonDet :> es)
    => Map String String -> Eff es FilePath
findXdgCandidate env =
    case Map.lookup "XDG_CACHE_HOME" env of
        Nothing -> emptyEff
        Just cacheHome -> getDir $ cacheHome </> "cabal"


findHomeCandidate
    :: (FileSystem :> es, NonDet :> es)
    => Map String String -> Eff es FilePath
findHomeCandidate env =
    case Map.lookup "HOME" env of
        Nothing -> emptyEff
        Just home -> do
            let cacheCandidate = home </> ".cache" </> "cabal"
                homeCandidate = home </> ".cabal"
            getDir cacheCandidate
                `plusEff` getDir homeCandidate


findFallback :: (NonDet :> es) => Map String String -> Eff es FilePath
findFallback env =
    findXdgFallback env
        `plusEff` findHomeFallback env


findXdgFallback :: (NonDet :> es) => Map String String -> Eff es FilePath
findXdgFallback env =
    case Map.lookup "XDG_CACHE_HOME" env of
        Nothing -> emptyEff
        Just cacheHome -> pure $ cacheHome </> "cabal"


findHomeFallback :: (NonDet :> es) => Map String String -> Eff es FilePath
findHomeFallback env =
    case Map.lookup "HOME" env of
        Nothing -> emptyEff
        Just home ->
            pure $ home </> ".cabal"


tempFallback :: FilePath
tempFallback = "/tmp/tricorder/packages"


getDir :: (FileSystem :> es, NonDet :> es) => FilePath -> Eff es FilePath
getDir fp = do
    exists <- FileSystem.doesDirectoryExist fp
    if exists then
        pure fp
    else
        emptyEff