{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot   #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE QuasiQuotes           #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE UndecidableInstances  #-}
{-# OPTIONS_GHC -Wno-unused-top-binds -Wno-identities #-}

{-|
Module      : Stack.Storage.User
Description : Work with SQLite DB for caches across a user account.
License     : BSD-3-Clause

Work with SQLite database used for caches across an entire user account.
-}

module Stack.Storage.User
  ( initUserStorage
  , PrecompiledCacheKey
  , PrecompiledCacheParent (..)
  , precompiledCacheKey
  , loadPrecompiledCache
  , savePrecompiledCache
  , loadDockerImageExeCache
  , saveDockerImageExeCache
  , loadCompilerPaths
  , saveCompilerPaths
  , upgradeChecksSince
  , logUpgradeCheck
  ) where

import qualified Data.Set as Set
import qualified Data.Text as T
import           Data.Time.Clock ( UTCTime )
import           Database.Persist.Sqlite
                   ( Entity (..), SqlBackend, Unique, (=.), (==.), (>=.), count
                   , deleteBy, getBy, insert, insert_, selectList, update
                   , upsert
                   )
import           Database.Persist.TH
                   ( mkMigrate, mkPersist, persistLowerCase, share
                   , sqlSettings
                   )
import           Distribution.Text ( simpleParse, display )
import           Foreign.C.Types ( CTime (..) )
import           Pantry.SQLite ( initStorage, withStorage_ )
import           Path ( (</>), mkRelFile, parseRelFile )
import           Path.IO ( resolveFile', resolveDir' )
import qualified RIO.FilePath as FP
import           Stack.Prelude
import           Stack.Storage.Util
                   ( handleMigrationException, setUpdateDiff, updateCollection )
import           Stack.Types.Cache ( Action (..), PrecompiledCache (..) )
import           Stack.Types.Compiler ( ActualCompiler, compilerVersionText )
import           Stack.Types.CompilerBuild ( CompilerBuild )
import           Stack.Types.CompilerPaths
                   ( CompilerPaths (..), GhcPkgExe (..) )
import           Stack.Types.Config ( Config (..), HasConfig (..) )
import           Stack.Types.Storage ( UserStorage (..) )
import           System.Posix.Types ( COff (..) )
import           System.PosixCompat.Files
                   ( fileSize, getFileStatus, modificationTime )

-- | Type representing exceptions thrown by functions exported by the

-- "Stack.Storage.User" module.

data StorageUserException
  = CompilerFileMetadataMismatch
  | GlobalPackageCacheFileMetadataMismatch
  | GlobalDumpParseFailure
  | CompilerCacheArchitectureInvalid Text
  deriving Int -> StorageUserException -> ShowS
[StorageUserException] -> ShowS
StorageUserException -> String
(Int -> StorageUserException -> ShowS)
-> (StorageUserException -> String)
-> ([StorageUserException] -> ShowS)
-> Show StorageUserException
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StorageUserException -> ShowS
showsPrec :: Int -> StorageUserException -> ShowS
$cshow :: StorageUserException -> String
show :: StorageUserException -> String
$cshowList :: [StorageUserException] -> ShowS
showList :: [StorageUserException] -> ShowS
Show

instance Exception StorageUserException where
  displayException :: StorageUserException -> String
displayException StorageUserException
CompilerFileMetadataMismatch =
    String
"Error: [S-8196]\n"
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"Compiler file metadata mismatch, ignoring cache."
  displayException StorageUserException
GlobalPackageCacheFileMetadataMismatch =
    String
"Error: [S-5378]\n"
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"Global package cache file metadata mismatch, ignoring cache."
  displayException StorageUserException
GlobalDumpParseFailure =
    String
"Error: [S-2673]\n"
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"Global dump did not parse correctly."
  displayException
    (CompilerCacheArchitectureInvalid Text
compilerCacheArch) = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
      [ String
"Error: [S-8441]\n"
      , String
"Invalid arch: "
      , Text -> String
forall a. Show a => a -> String
show Text
compilerCacheArch
      ]

share [ mkPersist sqlSettings
      , mkMigrate "migrateAll"
      ]
      [persistLowerCase|
PrecompiledCacheParent sql="precompiled_cache"
  platformGhcDir FilePath default="(hex(randomblob(16)))"
  compiler Text
  cabalVersion Text
  packageKey Text
  optionsHash ByteString
  haddock Bool default=0
  library FilePath Maybe
  UniquePrecompiledCacheParent platformGhcDir compiler cabalVersion packageKey optionsHash haddock sql="unique_precompiled_cache"
  deriving Show

PrecompiledCacheSubLib
  parent PrecompiledCacheParentId sql="precompiled_cache_id" OnDeleteCascade
  value FilePath sql="sub_lib"
  UniquePrecompiledCacheSubLib parent value
  deriving Show

PrecompiledCacheExe
  parent PrecompiledCacheParentId sql="precompiled_cache_id" OnDeleteCaseCascade
  value FilePath sql="exe"
  UniquePrecompiledCacheExe parent value
  deriving Show

DockerImageExeCache
  imageHash Text
  exePath FilePath
  exeTimestamp UTCTime
  compatible Bool
  DockerImageExeCacheUnique imageHash exePath exeTimestamp
  deriving Show

CompilerCache
  actualVersion ActualCompiler
  arch Text

  -- Include ghc executable size and modified time for sanity checking entries
  ghcPath FilePath
  ghcSize Int64
  ghcModified Int64

  ghcPkgPath FilePath
  runghcPath FilePath
  haddockPath FilePath

  cabalVersion Text
  globalDb FilePath
  globalDbCacheSize Int64
  globalDbCacheModified Int64
  info ByteString

  -- This is the ugliest part of this table, simply storing a Show/Read version of the
  -- data. We could do a better job with normalized data and proper table structure.
  -- However, recomputing this value in the future if the data representation changes
  -- is very cheap, so we'll take the easy way out for now.
  globalDump Text

  UniqueCompilerInfo ghcPath

-- Last time certain actions were performed
LastPerformed
  action Action
  timestamp UTCTime
  UniqueAction action
|]

-- | Initialize the database.

initUserStorage ::
     HasLogFunc env
  => Path Abs File -- ^ storage file

  -> (UserStorage -> RIO env a)
  -> RIO env a
initUserStorage :: forall env a.
HasLogFunc env =>
Path Abs File -> (UserStorage -> RIO env a) -> RIO env a
initUserStorage Path Abs File
fp UserStorage -> RIO env a
f = RIO env a -> RIO env a
forall env a. HasLogFunc env => RIO env a -> RIO env a
handleMigrationException (RIO env a -> RIO env a) -> RIO env a -> RIO env a
forall a b. (a -> b) -> a -> b
$
  Text
-> Migration
-> Path Abs File
-> (Storage -> RIO env a)
-> RIO env a
forall env a.
HasLogFunc env =>
Text
-> Migration
-> Path Abs File
-> (Storage -> RIO env a)
-> RIO env a
initStorage Text
"Stack" Migration
migrateAll Path Abs File
fp ((Storage -> RIO env a) -> RIO env a)
-> (Storage -> RIO env a) -> RIO env a
forall a b. (a -> b) -> a -> b
$ UserStorage -> RIO env a
f (UserStorage -> RIO env a)
-> (Storage -> UserStorage) -> Storage -> RIO env a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Storage -> UserStorage
UserStorage

-- | Run an action in a database transaction

withUserStorage ::
     (HasConfig env, HasLogFunc env)
  => ReaderT SqlBackend (RIO env) a
  -> RIO env a
withUserStorage :: forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage ReaderT SqlBackend (RIO env) a
inner = do
  storage <- Getting Storage env Storage -> RIO env Storage
forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view ((Config -> Const Storage Config) -> env -> Const Storage env
forall env. HasConfig env => Lens' env Config
Lens' env Config
configL ((Config -> Const Storage Config) -> env -> Const Storage env)
-> ((Storage -> Const Storage Storage)
    -> Config -> Const Storage Config)
-> Getting Storage env Storage
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Config -> Storage) -> SimpleGetter Config Storage
forall s a. (s -> a) -> SimpleGetter s a
to (.userStorage.userStorage))
  withStorage_ storage inner

-- | Key used to retrieve the precompiled cache

type PrecompiledCacheKey = Unique PrecompiledCacheParent

-- | Build key used to retrieve the precompiled cache

precompiledCacheKey ::
     Path Rel Dir
  -> ActualCompiler
  -> Version
  -> Text
  -> ByteString
  -> Bool
  -> PrecompiledCacheKey
precompiledCacheKey :: Path Rel Dir
-> ActualCompiler
-> Version
-> Text
-> ByteString
-> Bool
-> Unique PrecompiledCacheParent
precompiledCacheKey Path Rel Dir
platformGhcDir ActualCompiler
compiler Version
cabalVersion =
  String
-> Text
-> Text
-> Text
-> ByteString
-> Bool
-> Unique PrecompiledCacheParent
UniquePrecompiledCacheParent
    (Path Rel Dir -> String
forall b t. Path b t -> String
toFilePath Path Rel Dir
platformGhcDir)
    (ActualCompiler -> Text
compilerVersionText ActualCompiler
compiler)
    (String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Version -> String
versionString Version
cabalVersion)

-- | Internal helper to read the t'PrecompiledCache' from the database

readPrecompiledCache ::
     (HasConfig env, HasLogFunc env)
  => PrecompiledCacheKey
  -> ReaderT SqlBackend (RIO env) (Maybe ( PrecompiledCacheParentId
                                         , PrecompiledCache Rel))
readPrecompiledCache :: forall env.
(HasConfig env, HasLogFunc env) =>
Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
readPrecompiledCache Unique PrecompiledCacheParent
key = do
  mparent <- Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend (RIO env) (Maybe (Entity PrecompiledCacheParent))
forall backend record (m :: * -> *).
(PersistUniqueRead backend, MonadIO m,
 PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend) =>
Unique record -> ReaderT SqlBackend m (Maybe (Entity record))
getBy Unique PrecompiledCacheParent
key
  forM mparent $ \(Entity Key PrecompiledCacheParent
parentId PrecompiledCacheParent
precompiledCacheParent) -> do
    library <-
      (String -> ReaderT SqlBackend (RIO env) (Path Rel File))
-> Maybe String
-> ReaderT SqlBackend (RIO env) (Maybe (Path Rel File))
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b.
Monad m =>
(a -> m b) -> Maybe a -> m (Maybe b)
mapM String -> ReaderT SqlBackend (RIO env) (Path Rel File)
forall (m :: * -> *). MonadThrow m => String -> m (Path Rel File)
parseRelFile PrecompiledCacheParent
precompiledCacheParent.precompiledCacheParentLibrary
    subLibs <-
      mapM (parseRelFile . (.precompiledCacheSubLibValue) . entityVal) =<<
      selectList [PrecompiledCacheSubLibParent ==. parentId] []
    exes <-
      mapM (parseRelFile . (.precompiledCacheExeValue) . entityVal) =<<
      selectList [PrecompiledCacheExeParent ==. parentId] []
    pure
      ( parentId
      , PrecompiledCache
          { library
          , subLibs
          , exes
          }
      )

-- | Load t'PrecompiledCache' from the database.

loadPrecompiledCache ::
     (HasConfig env, HasLogFunc env)
  => PrecompiledCacheKey
  -> RIO env (Maybe (PrecompiledCache Rel))
loadPrecompiledCache :: forall env.
(HasConfig env, HasLogFunc env) =>
Unique PrecompiledCacheParent
-> RIO env (Maybe (PrecompiledCache Rel))
loadPrecompiledCache Unique PrecompiledCacheParent
key =
  ReaderT SqlBackend (RIO env) (Maybe (PrecompiledCache Rel))
-> RIO env (Maybe (PrecompiledCache Rel))
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) (Maybe (PrecompiledCache Rel))
 -> RIO env (Maybe (PrecompiledCache Rel)))
-> ReaderT SqlBackend (RIO env) (Maybe (PrecompiledCache Rel))
-> RIO env (Maybe (PrecompiledCache Rel))
forall a b. (a -> b) -> a -> b
$ ((Key PrecompiledCacheParent, PrecompiledCache Rel)
 -> PrecompiledCache Rel)
-> Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel)
-> Maybe (PrecompiledCache Rel)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Key PrecompiledCacheParent, PrecompiledCache Rel)
-> PrecompiledCache Rel
forall a b. (a, b) -> b
snd (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel)
 -> Maybe (PrecompiledCache Rel))
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
-> ReaderT SqlBackend (RIO env) (Maybe (PrecompiledCache Rel))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
forall env.
(HasConfig env, HasLogFunc env) =>
Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
readPrecompiledCache Unique PrecompiledCacheParent
key

-- | Insert or update t'PrecompiledCache' to the database.

savePrecompiledCache ::
     (HasConfig env, HasLogFunc env)
  => PrecompiledCacheKey
  -> PrecompiledCache Rel
  -> RIO env ()
savePrecompiledCache :: forall env.
(HasConfig env, HasLogFunc env) =>
Unique PrecompiledCacheParent -> PrecompiledCache Rel -> RIO env ()
savePrecompiledCache
  key :: Unique PrecompiledCacheParent
key@( UniquePrecompiledCacheParent
          String
precompiledCacheParentPlatformGhcDir
          Text
precompiledCacheParentCompiler
          Text
precompiledCacheParentCabalVersion
          Text
precompiledCacheParentPackageKey
          ByteString
precompiledCacheParentOptionsHash
          Bool
precompiledCacheParentHaddock
      )
  PrecompiledCache Rel
new
  = ReaderT SqlBackend (RIO env) () -> RIO env ()
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) () -> RIO env ())
-> ReaderT SqlBackend (RIO env) () -> RIO env ()
forall a b. (a -> b) -> a -> b
$ do
      let precompiledCacheParentLibrary :: Maybe String
precompiledCacheParentLibrary = (Path Rel File -> String) -> Maybe (Path Rel File) -> Maybe String
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Path Rel File -> String
forall b t. Path b t -> String
toFilePath PrecompiledCache Rel
new.library
      (parentId, mold) <- Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
forall env.
(HasConfig env, HasLogFunc env) =>
Unique PrecompiledCacheParent
-> ReaderT
     SqlBackend
     (RIO env)
     (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
readPrecompiledCache Unique PrecompiledCacheParent
key ReaderT
  SqlBackend
  (RIO env)
  (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel))
-> (Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel)
    -> ReaderT
         SqlBackend
         (RIO env)
         (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel)))
-> ReaderT
     SqlBackend
     (RIO env)
     (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel))
forall a b.
ReaderT SqlBackend (RIO env) a
-> (a -> ReaderT SqlBackend (RIO env) b)
-> ReaderT SqlBackend (RIO env) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        Maybe (Key PrecompiledCacheParent, PrecompiledCache Rel)
Nothing -> (, Maybe (PrecompiledCache Rel)
forall a. Maybe a
Nothing) (Key PrecompiledCacheParent
 -> (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel)))
-> ReaderT SqlBackend (RIO env) (Key PrecompiledCacheParent)
-> ReaderT
     SqlBackend
     (RIO env)
     (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PrecompiledCacheParent
-> ReaderT SqlBackend (RIO env) (Key PrecompiledCacheParent)
forall backend record (m :: * -> *).
(PersistStoreWrite backend, MonadIO m,
 PersistRecordBackend record backend, SafeToInsert record) =>
record -> ReaderT backend m (Key record)
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend,
 SafeToInsert record) =>
record -> ReaderT SqlBackend m (Key record)
insert PrecompiledCacheParent
          { String
precompiledCacheParentPlatformGhcDir :: String
precompiledCacheParentPlatformGhcDir :: String
precompiledCacheParentPlatformGhcDir
          , Text
precompiledCacheParentCompiler :: Text
precompiledCacheParentCompiler :: Text
precompiledCacheParentCompiler
          , Text
precompiledCacheParentCabalVersion :: Text
precompiledCacheParentCabalVersion :: Text
precompiledCacheParentCabalVersion
          , Text
precompiledCacheParentPackageKey :: Text
precompiledCacheParentPackageKey :: Text
precompiledCacheParentPackageKey
          , ByteString
precompiledCacheParentOptionsHash :: ByteString
precompiledCacheParentOptionsHash :: ByteString
precompiledCacheParentOptionsHash
          , Bool
precompiledCacheParentHaddock :: Bool
precompiledCacheParentHaddock :: Bool
precompiledCacheParentHaddock
          , Maybe String
precompiledCacheParentLibrary :: Maybe String
precompiledCacheParentLibrary :: Maybe String
precompiledCacheParentLibrary
          }
        Just (Key PrecompiledCacheParent
parentId, PrecompiledCache Rel
old) -> do
          Key PrecompiledCacheParent
-> [Update PrecompiledCacheParent]
-> ReaderT SqlBackend (RIO env) ()
forall backend record (m :: * -> *).
(PersistStoreWrite backend, MonadIO m,
 PersistRecordBackend record backend) =>
Key record -> [Update record] -> ReaderT backend m ()
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend) =>
Key record -> [Update record] -> ReaderT SqlBackend m ()
update
            Key PrecompiledCacheParent
parentId
            [ EntityField PrecompiledCacheParent (Maybe String)
forall typ.
(typ ~ Maybe String) =>
EntityField PrecompiledCacheParent typ
PrecompiledCacheParentLibrary EntityField PrecompiledCacheParent (Maybe String)
-> Maybe String -> Update PrecompiledCacheParent
forall v typ.
PersistField typ =>
EntityField v typ -> typ -> Update v
=.
              Maybe String
precompiledCacheParentLibrary
            ]
          (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel))
-> ReaderT
     SqlBackend
     (RIO env)
     (Key PrecompiledCacheParent, Maybe (PrecompiledCache Rel))
forall a. a -> ReaderT SqlBackend (RIO env) a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Key PrecompiledCacheParent
parentId, PrecompiledCache Rel -> Maybe (PrecompiledCache Rel)
forall a. a -> Maybe a
Just PrecompiledCache Rel
old)
      updateCollection
        (setUpdateDiff PrecompiledCacheSubLibValue)
        (PrecompiledCacheSubLib parentId)
        [PrecompiledCacheSubLibParent ==. parentId]
        (maybe Set.empty (toFilePathSet . (.subLibs)) mold)
        (toFilePathSet new.subLibs)
      updateCollection
        (setUpdateDiff PrecompiledCacheExeValue)
        (PrecompiledCacheExe parentId)
        [PrecompiledCacheExeParent ==. parentId]
        (maybe Set.empty (toFilePathSet . (.exes)) mold)
        (toFilePathSet new.exes)
 where
  toFilePathSet :: [Path b t] -> Set String
toFilePathSet = [String] -> Set String
forall a. Ord a => [a] -> Set a
Set.fromList ([String] -> Set String)
-> ([Path b t] -> [String]) -> [Path b t] -> Set String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Path b t -> String) -> [Path b t] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Path b t -> String
forall b t. Path b t -> String
toFilePath

-- | Get the record of whether an executable is compatible with a Docker image

loadDockerImageExeCache ::
     (HasConfig env, HasLogFunc env)
  => Text
  -> Path Abs File
  -> UTCTime
  -> RIO env (Maybe Bool)
loadDockerImageExeCache :: forall env.
(HasConfig env, HasLogFunc env) =>
Text -> Path Abs File -> UTCTime -> RIO env (Maybe Bool)
loadDockerImageExeCache Text
imageId Path Abs File
exePath UTCTime
exeTimestamp = ReaderT SqlBackend (RIO env) (Maybe Bool) -> RIO env (Maybe Bool)
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) (Maybe Bool) -> RIO env (Maybe Bool))
-> ReaderT SqlBackend (RIO env) (Maybe Bool)
-> RIO env (Maybe Bool)
forall a b. (a -> b) -> a -> b
$
  (Entity DockerImageExeCache -> Bool)
-> Maybe (Entity DockerImageExeCache) -> Maybe Bool
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((.dockerImageExeCacheCompatible) (DockerImageExeCache -> Bool)
-> (Entity DockerImageExeCache -> DockerImageExeCache)
-> Entity DockerImageExeCache
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Entity DockerImageExeCache -> DockerImageExeCache
forall record. Entity record -> record
entityVal) (Maybe (Entity DockerImageExeCache) -> Maybe Bool)
-> ReaderT
     SqlBackend (RIO env) (Maybe (Entity DockerImageExeCache))
-> ReaderT SqlBackend (RIO env) (Maybe Bool)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
  Unique DockerImageExeCache
-> ReaderT
     SqlBackend (RIO env) (Maybe (Entity DockerImageExeCache))
forall backend record (m :: * -> *).
(PersistUniqueRead backend, MonadIO m,
 PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend) =>
Unique record -> ReaderT SqlBackend m (Maybe (Entity record))
getBy (Text -> String -> UTCTime -> Unique DockerImageExeCache
DockerImageExeCacheUnique Text
imageId (Path Abs File -> String
forall b t. Path b t -> String
toFilePath Path Abs File
exePath) UTCTime
exeTimestamp)

-- | Sets the record of whether an executable is compatible with a Docker image

saveDockerImageExeCache ::
     (HasConfig env, HasLogFunc env)
  => Text
  -> Path Abs File
  -> UTCTime
  -> Bool
  -> RIO env ()
saveDockerImageExeCache :: forall env.
(HasConfig env, HasLogFunc env) =>
Text -> Path Abs File -> UTCTime -> Bool -> RIO env ()
saveDockerImageExeCache Text
imageId Path Abs File
exePath UTCTime
exeTimestamp Bool
compatible = RIO env (Entity DockerImageExeCache) -> RIO env ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RIO env (Entity DockerImageExeCache) -> RIO env ())
-> RIO env (Entity DockerImageExeCache) -> RIO env ()
forall a b. (a -> b) -> a -> b
$
  ReaderT SqlBackend (RIO env) (Entity DockerImageExeCache)
-> RIO env (Entity DockerImageExeCache)
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) (Entity DockerImageExeCache)
 -> RIO env (Entity DockerImageExeCache))
-> ReaderT SqlBackend (RIO env) (Entity DockerImageExeCache)
-> RIO env (Entity DockerImageExeCache)
forall a b. (a -> b) -> a -> b
$
    DockerImageExeCache
-> [Update DockerImageExeCache]
-> ReaderT SqlBackend (RIO env) (Entity DockerImageExeCache)
forall backend record (m :: * -> *).
(PersistUniqueWrite backend, MonadIO m,
 PersistRecordBackend record backend, OnlyOneUniqueKey record,
 SafeToInsert record) =>
record -> [Update record] -> ReaderT backend m (Entity record)
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend,
 OnlyOneUniqueKey record, SafeToInsert record) =>
record -> [Update record] -> ReaderT SqlBackend m (Entity record)
upsert
      ( Text -> String -> UTCTime -> Bool -> DockerImageExeCache
DockerImageExeCache
          Text
imageId
          (Path Abs File -> String
forall b t. Path b t -> String
toFilePath Path Abs File
exePath)
          UTCTime
exeTimestamp
          Bool
compatible
      )
      []

-- | Type-restricted version of 'fromIntegral' to ensure we're making the value

-- bigger, not smaller.

sizeToInt64 :: COff -> Int64
sizeToInt64 :: COff -> Int64
sizeToInt64 (COff Int64
i) = Int64 -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
i -- fromIntegral added for 32-bit systems


-- | Type-restricted version of 'fromIntegral' to ensure we're making the value

-- bigger, not smaller.

timeToInt64 :: CTime -> Int64
timeToInt64 :: CTime -> Int64
timeToInt64 (CTime Int32
i) = Int32 -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int32
i -- fromIntegral added for 32-bit systems


-- | Load compiler information, if available, and confirm that the referenced

-- files are unchanged. May throw exceptions!

loadCompilerPaths ::
     HasConfig env
  => Path Abs File -- ^ compiler executable

  -> CompilerBuild
  -> Bool -- ^ sandboxed?

  -> RIO env (Maybe CompilerPaths)
loadCompilerPaths :: forall env.
HasConfig env =>
Path Abs File
-> CompilerBuild -> Bool -> RIO env (Maybe CompilerPaths)
loadCompilerPaths Path Abs File
compiler CompilerBuild
build Bool
sandboxed = do
  mres <- ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache))
-> RIO env (Maybe (Entity CompilerCache))
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache))
 -> RIO env (Maybe (Entity CompilerCache)))
-> ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache))
-> RIO env (Maybe (Entity CompilerCache))
forall a b. (a -> b) -> a -> b
$ Unique CompilerCache
-> ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache))
forall backend record (m :: * -> *).
(PersistUniqueRead backend, MonadIO m,
 PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend) =>
Unique record -> ReaderT SqlBackend m (Maybe (Entity record))
getBy (Unique CompilerCache
 -> ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache)))
-> Unique CompilerCache
-> ReaderT SqlBackend (RIO env) (Maybe (Entity CompilerCache))
forall a b. (a -> b) -> a -> b
$ String -> Unique CompilerCache
UniqueCompilerInfo (String -> Unique CompilerCache) -> String -> Unique CompilerCache
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
forall b t. Path b t -> String
toFilePath Path Abs File
compiler
  for mres $ \(Entity Key CompilerCache
_ CompilerCache
compilerCache) -> do
    compilerStatus <- IO FileStatus -> RIO env FileStatus
forall a. IO a -> RIO env a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FileStatus -> RIO env FileStatus)
-> IO FileStatus -> RIO env FileStatus
forall a b. (a -> b) -> a -> b
$ String -> IO FileStatus
getFileStatus (String -> IO FileStatus) -> String -> IO FileStatus
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
forall b t. Path b t -> String
toFilePath Path Abs File
compiler
    when
      (  compilerCache.compilerCacheGhcSize /=
           sizeToInt64 (fileSize compilerStatus)
      || compilerCache.compilerCacheGhcModified /=
           timeToInt64 (modificationTime compilerStatus)
      )
      (throwIO CompilerFileMetadataMismatch)
    globalDbStatus <- liftIO $
      getFileStatus $ compilerCache.compilerCacheGlobalDb FP.</> "package.cache"
    when
      (  compilerCache.compilerCacheGlobalDbCacheSize /=
           sizeToInt64 (fileSize globalDbStatus)
      || compilerCache.compilerCacheGlobalDbCacheModified /=
           timeToInt64 (modificationTime globalDbStatus)
      )
      (throwIO GlobalPackageCacheFileMetadataMismatch)

    -- We could use parseAbsFile instead of resolveFile' below to bypass some

    -- system calls, at the cost of some really wonky error messages in case

    -- someone screws up their GHC installation

    pkg <- GhcPkgExe <$> resolveFile' compilerCache.compilerCacheGhcPkgPath
    interpreter <- resolveFile' compilerCache.compilerCacheRunghcPath
    haddock <- resolveFile' compilerCache.compilerCacheHaddockPath
    globalDB <- resolveDir' compilerCache.compilerCacheGlobalDb

    cabalVersion <- parseVersionThrowing $
      T.unpack compilerCache.compilerCacheCabalVersion
    globalDump <-
      case readMaybe $ T.unpack compilerCache.compilerCacheGlobalDump of
        Maybe (Map PackageName DumpPackage)
Nothing -> StorageUserException -> RIO env (Map PackageName DumpPackage)
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO StorageUserException
GlobalDumpParseFailure
        Just Map PackageName DumpPackage
globalDump -> Map PackageName DumpPackage
-> RIO env (Map PackageName DumpPackage)
forall a. a -> RIO env a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Map PackageName DumpPackage
globalDump
    arch <-
      case simpleParse $ T.unpack compilerCache.compilerCacheArch of
        Maybe Arch
Nothing -> StorageUserException -> RIO env Arch
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (StorageUserException -> RIO env Arch)
-> StorageUserException -> RIO env Arch
forall a b. (a -> b) -> a -> b
$
          Text -> StorageUserException
CompilerCacheArchitectureInvalid CompilerCache
compilerCache.compilerCacheArch
        Just Arch
arch -> Arch -> RIO env Arch
forall a. a -> RIO env a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Arch
arch
    pure CompilerPaths
      { compiler
      , compilerVersion = compilerCache.compilerCacheActualVersion
      , arch
      , build
      , pkg
      , interpreter
      , haddock
      , sandboxed
      , cabalVersion
      , globalDB
      , ghcInfo = compilerCache.compilerCacheInfo
      , globalDump
      }

-- | Save compiler information. May throw exceptions!

saveCompilerPaths ::
     HasConfig env
  => CompilerPaths
  -> RIO env ()
saveCompilerPaths :: forall env. HasConfig env => CompilerPaths -> RIO env ()
saveCompilerPaths CompilerPaths
cp = ReaderT SqlBackend (RIO env) () -> RIO env ()
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) () -> RIO env ())
-> ReaderT SqlBackend (RIO env) () -> RIO env ()
forall a b. (a -> b) -> a -> b
$ do
  Unique CompilerCache -> ReaderT SqlBackend (RIO env) ()
forall backend record (m :: * -> *).
(PersistUniqueWrite backend, MonadIO m,
 PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m ()
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend) =>
Unique record -> ReaderT SqlBackend m ()
deleteBy (Unique CompilerCache -> ReaderT SqlBackend (RIO env) ())
-> Unique CompilerCache -> ReaderT SqlBackend (RIO env) ()
forall a b. (a -> b) -> a -> b
$ String -> Unique CompilerCache
UniqueCompilerInfo (String -> Unique CompilerCache) -> String -> Unique CompilerCache
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
forall b t. Path b t -> String
toFilePath CompilerPaths
cp.compiler
  compilerStatus <- IO FileStatus -> ReaderT SqlBackend (RIO env) FileStatus
forall a. IO a -> ReaderT SqlBackend (RIO env) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FileStatus -> ReaderT SqlBackend (RIO env) FileStatus)
-> IO FileStatus -> ReaderT SqlBackend (RIO env) FileStatus
forall a b. (a -> b) -> a -> b
$ String -> IO FileStatus
getFileStatus (String -> IO FileStatus) -> String -> IO FileStatus
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
forall b t. Path b t -> String
toFilePath CompilerPaths
cp.compiler
  globalDbStatus <- liftIO $
    getFileStatus $ toFilePath $ cp.globalDB </> $(mkRelFile "package.cache")
  let GhcPkgExe pkgexe = cp.pkg
  insert_ CompilerCache
    { compilerCacheActualVersion = cp.compilerVersion
    , compilerCacheGhcPath = toFilePath cp.compiler
    , compilerCacheGhcSize = sizeToInt64 $ fileSize compilerStatus
    , compilerCacheGhcModified = timeToInt64 $ modificationTime compilerStatus
    , compilerCacheGhcPkgPath = toFilePath pkgexe
    , compilerCacheRunghcPath = toFilePath cp.interpreter
    , compilerCacheHaddockPath = toFilePath cp.haddock
    , compilerCacheCabalVersion = T.pack $ versionString cp.cabalVersion
    , compilerCacheGlobalDb = toFilePath cp.globalDB
    , compilerCacheGlobalDbCacheSize = sizeToInt64 $ fileSize globalDbStatus
    , compilerCacheGlobalDbCacheModified =
        timeToInt64 $ modificationTime globalDbStatus
    , compilerCacheInfo = cp.ghcInfo
    , compilerCacheGlobalDump = tshow cp.globalDump
    , compilerCacheArch = T.pack $ Distribution.Text.display cp.arch
    }

-- | How many upgrade checks have occurred since the given timestamp?

upgradeChecksSince :: HasConfig env => UTCTime -> RIO env Int
upgradeChecksSince :: forall env. HasConfig env => UTCTime -> RIO env Int
upgradeChecksSince UTCTime
since = ReaderT SqlBackend (RIO env) Int -> RIO env Int
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) Int -> RIO env Int)
-> ReaderT SqlBackend (RIO env) Int -> RIO env Int
forall a b. (a -> b) -> a -> b
$ [Filter LastPerformed] -> ReaderT SqlBackend (RIO env) Int
forall backend (m :: * -> *) record.
(PersistQueryRead backend, MonadIO m,
 PersistRecordBackend record backend) =>
[Filter record] -> ReaderT backend m Int
forall (m :: * -> *) record.
(MonadIO m, PersistRecordBackend record SqlBackend) =>
[Filter record] -> ReaderT SqlBackend m Int
count
  [ EntityField LastPerformed Action
forall typ. (typ ~ Action) => EntityField LastPerformed typ
LastPerformedAction EntityField LastPerformed Action -> Action -> Filter LastPerformed
forall v typ.
PersistField typ =>
EntityField v typ -> typ -> Filter v
==. Action
UpgradeCheck
  , EntityField LastPerformed UTCTime
forall typ. (typ ~ UTCTime) => EntityField LastPerformed typ
LastPerformedTimestamp EntityField LastPerformed UTCTime
-> UTCTime -> Filter LastPerformed
forall v typ.
PersistField typ =>
EntityField v typ -> typ -> Filter v
>=. UTCTime
since
  ]

-- | Log in the database that an upgrade check occurred at the given time.

logUpgradeCheck :: HasConfig env => UTCTime -> RIO env ()
logUpgradeCheck :: forall env. HasConfig env => UTCTime -> RIO env ()
logUpgradeCheck UTCTime
time = ReaderT SqlBackend (RIO env) () -> RIO env ()
forall env a.
(HasConfig env, HasLogFunc env) =>
ReaderT SqlBackend (RIO env) a -> RIO env a
withUserStorage (ReaderT SqlBackend (RIO env) () -> RIO env ())
-> ReaderT SqlBackend (RIO env) () -> RIO env ()
forall a b. (a -> b) -> a -> b
$ ReaderT SqlBackend (RIO env) (Entity LastPerformed)
-> ReaderT SqlBackend (RIO env) ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (ReaderT SqlBackend (RIO env) (Entity LastPerformed)
 -> ReaderT SqlBackend (RIO env) ())
-> ReaderT SqlBackend (RIO env) (Entity LastPerformed)
-> ReaderT SqlBackend (RIO env) ()
forall a b. (a -> b) -> a -> b
$ LastPerformed
-> [Update LastPerformed]
-> ReaderT SqlBackend (RIO env) (Entity LastPerformed)
forall backend record (m :: * -> *).
(PersistUniqueWrite backend, MonadIO m,
 PersistRecordBackend record backend, OnlyOneUniqueKey record,
 SafeToInsert record) =>
record -> [Update record] -> ReaderT backend m (Entity record)
forall record (m :: * -> *).
(MonadIO m, PersistRecordBackend record SqlBackend,
 OnlyOneUniqueKey record, SafeToInsert record) =>
record -> [Update record] -> ReaderT SqlBackend m (Entity record)
upsert
  (Action -> UTCTime -> LastPerformed
LastPerformed Action
UpgradeCheck UTCTime
time)
  [EntityField LastPerformed UTCTime
forall typ. (typ ~ UTCTime) => EntityField LastPerformed typ
LastPerformedTimestamp EntityField LastPerformed UTCTime
-> UTCTime -> Update LastPerformed
forall v typ.
PersistField typ =>
EntityField v typ -> typ -> Update v
=. UTCTime
time]