packages feed

headroom-0.4.3.0: src/Headroom/Command/Bootstrap.hs

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE StrictData        #-}

{-|
Module      : Headroom.Command.Bootstrap
Description : Logic for bootstrapping Headroom
Copyright   : (c) 2019-2022 Vaclav Svejcar
License     : BSD-3-Clause
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

Logic for running shared code and bootstrapping all /Headroom/ command /RIO/
applications.
-}

module Headroom.Command.Bootstrap
  ( BootstrapEnv(..)
  , bootstrap
  , runRIO'
  , globalKVStore
  )
where

import           Data.String.Interpolate             ( iii )
import           Headroom.Config.Global              ( GlobalConfig(..)
                                                     , globalConfigPath
                                                     , initGlobalConfigIfNeeded
                                                     , loadGlobalConfig
                                                     )
import           Headroom.Data.Has                   ( Has(..)
                                                     , HasRIO
                                                     )
import           Headroom.IO.FileSystem              ( FileSystem(..) )
import           Headroom.IO.KVStore                 ( KVStore
                                                     , StorePath(..)
                                                     , sqliteKVStore
                                                     )
import           Headroom.IO.Network                 ( Network )
import           Headroom.Meta                       ( cacheFileName
                                                     , globalConfigDirName
                                                     , productInfo
                                                     , webRepo
                                                     )
import           Headroom.Meta.Version               ( Version
                                                     , printVersionP
                                                     )
import           Headroom.UI.Message                 ( messageInfo )
import           Headroom.Updater                    ( UpdaterError(..)
                                                     , checkUpdates
                                                     )
import           RIO
import           RIO.FilePath                        ( (</>) )
import qualified RIO.Text                           as T


-- | Bootstrap environment, containing pieces shared between all commands.
data BootstrapEnv = BootstrapEnv
  { beGlobalConfig :: GlobalConfig -- ^ loaded global configuration
  }


-- | Runs /RIO/ application using provided environment data and flag
-- whether to run in debug mode.
runRIO' :: (LogFunc -> IO env)
        -- ^ function returning environment data
        -> Bool
        -- ^ whether to run in debug mode
        -> RIO env a
        -- ^ /RIO/ application to execute
        -> IO a
        -- ^ execution result
runRIO' enfFn isDebug logic = do
  defLogOptions <- logOptionsHandle stderr isDebug
  withLogFunc (setLogUseLoc False defLogOptions) $ \logFunc -> do
    env <- liftIO $ enfFn logFunc
    runRIO env logic


-- | Executes the initialization logic that should be performed before any other
-- code is executed. During this bootstrap, for example /global configuration/
-- is initialized and loaded, welcome message is printed to console and updates
-- are checked.
bootstrap :: ( HasRIO FileSystem env
             , HasRIO KVStore env
             , HasRIO Network env
             , HasLogFunc env
             )
          => RIO env BootstrapEnv
bootstrap = do
  welcomeMessage
  initGlobalConfigIfNeeded
  globalConfig@GlobalConfig {..} <- loadGlobalConfig
  catch (checkUpdates gcUpdates) onError >>= \case
    Nothing         -> pure ()
    Just newVersion -> displayUpdate newVersion
  pure BootstrapEnv { beGlobalConfig = globalConfig }
 where
  onError err = do
    logWarn . display . T.pack $ displayException (err :: UpdaterError)
    pure Nothing


-- | Shared /SQLite/-based 'KVStore'.
globalKVStore :: (HasRIO FileSystem env) => RIO env (KVStore (RIO env))
globalKVStore = do
  FileSystem {..} <- viewL
  userDir         <- fsGetUserDirectory
  pure
    .   sqliteKVStore
    .   StorePath
    .   T.pack
    $   userDir
    </> globalConfigDirName
    </> cacheFileName


------------------------------  PRIVATE FUNCTIONS  -----------------------------

welcomeMessage :: HasLogFunc env => RIO env ()
welcomeMessage = logInfo . display $ productInfo


displayUpdate :: (HasRIO FileSystem env, HasLogFunc env)
              => Version
              -> RIO env ()
displayUpdate version = do
  configPath <- globalConfigPath
  logInfo . display . messageInfo $ message configPath
 where
  message configPath = [iii|
      New version #{printVersionP version} is available for download, you can
      get it from #{webRepo}.\n\t
      Tired of seeing this message? You can change the behaviour in global
      config file here:\n\t
      #{configPath}
    |]