    {-# LANGUAGE ScopedTypeVariables #-}

    {- |

    This is the only other module aside from "Config.Dyre" which needs
    to be imported specially. It contains functions for restarting the
    program (which, usefully, will cause a recompile if the config has
    been changed), as well as saving and restoring state across said
    restarts.

    The impossibly simple function arguments are a consequence of a
    little cheating we do using the "System.IO.Storage" library. Of
    course, we can't use the stored data unless something else put
    it there, so this module will probably explode horribly if used
    outside of a program whose recompilation is managed by Dyre.

    The functions for saving and loading state come in two variants:
    one which uses the 'Read' and 'Show' typeclasses, and one which
    uses "Data.Binary" to serialize it. The 'Read' and 'Show' versions
    are much easier to use thanks to automatic deriving, but the
    binary versions offer more control over saving and loading, as
    well as probably being a bit faster.

    -}
    module Config.Dyre.Relaunch
      ( relaunchMaster
      , relaunchWithTextState
      , relaunchWithBinaryState
      , saveTextState
      , saveBinaryState
      , restoreTextState
      , restoreBinaryState
      ) where

    import Data.Maybe           ( fromMaybe )
    import System.IO            ( writeFile, readFile )
    import Data.Binary          ( Binary, encodeFile, decodeFile )
    import Control.Exception    ( try, SomeException )
    import System.FilePath      ( (</>) )
    import System.Directory     ( getTemporaryDirectory )

    import System.IO.Storage    ( putValue )
    import Config.Dyre.Options  ( getMasterBinary, getStatePersist )
    import Config.Dyre.Compat   ( customExec, getPIDString )

    -- | Just relaunch the master binary. We don't have any important
    --   state to worry about. (Or, like when @relaunchWith\<X\>State@ calls
    --   it, we're managing state on our own). It takes an argument which
    --   can optionally specify a new set of arguments. If it is given a
    --   value of 'Nothing', the current value of 'System.Environment.getArgs' will be used.
    relaunchMaster :: Maybe [String] -> IO ()
    relaunchMaster :: Maybe [FilePath] -> IO ()
    relaunchMaster Maybe [FilePath]
    otherArgs = do
        masterPath <- (Maybe FilePath -> FilePath) -> IO (Maybe FilePath) -> IO FilePath
    forall a b. (a -> b) -> IO a -> IO b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (FilePath -> Maybe FilePath -> FilePath
    forall a. a -> Maybe a -> a
    fromMaybe (FilePath -> Maybe FilePath -> FilePath)
    -> FilePath -> Maybe FilePath -> FilePath
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath
    forall a. HasCallStack => FilePath -> a
    error FilePath
    "'dyre' data-store doesn't exist (in Config.Dyre.Relaunch.relaunchMaster)") IO (Maybe FilePath)
    getMasterBinary
        customExec masterPath otherArgs

    -- | Relaunch the master binary, but first preserve the program
    --   state so that we can use the 'restoreTextState' functions to
    --   get it back again later.
    relaunchWithTextState :: Show a => a -> Maybe [String] -> IO ()
    relaunchWithTextState :: forall a. Show a => a -> Maybe [FilePath] -> IO ()
    relaunchWithTextState a
    state Maybe [FilePath]
    otherArgs = do
        a -> IO ()
    forall a. Show a => a -> IO ()
    saveTextState a
    state
        Maybe [FilePath] -> IO ()
    relaunchMaster Maybe [FilePath]
    otherArgs

    -- | Serialize the state for later restoration with 'restoreBinaryState',
    --   and then relaunch the master binary.
    relaunchWithBinaryState :: Binary a => a -> Maybe [String] -> IO ()
    relaunchWithBinaryState :: forall a. Binary a => a -> Maybe [FilePath] -> IO ()
    relaunchWithBinaryState a
    state Maybe [FilePath]
    otherArgs = do
        a -> IO ()
    forall a. Binary a => a -> IO ()
    saveBinaryState a
    state
        Maybe [FilePath] -> IO ()
    relaunchMaster Maybe [FilePath]
    otherArgs

    -- | Calculate the path that will be used for saving the state.
    --   The path used to load the state, meanwhile, is passed to the
    --   program with the '--dyre-persist-state=<path>' flag.
    genStatePath :: IO FilePath
    genStatePath :: IO FilePath
    genStatePath = do
        pidString <- IO FilePath
    getPIDString
        tempDir   <- getTemporaryDirectory
        let statePath = FilePath
    tempDir FilePath -> FilePath -> FilePath
    </> FilePath
    pidString FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    ".state"
        putValue "dyre" "persistState" statePath
        return statePath

    -- | Serialize a state as text, for later loading with the
    --   'restoreTextState' function.
    saveTextState :: Show a => a -> IO ()
    saveTextState :: forall a. Show a => a -> IO ()
    saveTextState a
    state = do
        statePath <- IO FilePath
    genStatePath
        writeFile statePath . show $ state

    -- | Serialize a state as binary data, for later loading with
    --   the 'restoreBinaryState' function.
    saveBinaryState :: Binary a => a -> IO ()
    saveBinaryState :: forall a. Binary a => a -> IO ()
    saveBinaryState a
    state = do
        statePath <- IO FilePath
    genStatePath
        encodeFile statePath . Just $ state

    -- | Restore state which has been serialized through the
    --   'saveTextState' function. Takes a default which is
    --   returned if the state doesn't exist.
    restoreTextState :: Read a => a -> IO a
    restoreTextState :: forall a. Read a => a -> IO a
    restoreTextState a
    d = do
        statePath <- IO (Maybe FilePath)
    getStatePersist
        case statePath of
             Maybe FilePath
    Nothing -> a -> IO a
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return a
    d
             Just FilePath
    sp -> do
                 stateData <- FilePath -> IO FilePath
    readFile FilePath
    sp
                 result <- try $ readIO stateData
                 case result of
                      Left  (SomeException
    _ :: SomeException) -> a -> IO a
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return a
    d
                      Right                    a
    v -> a -> IO a
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return a
    v

    -- | Restore state which has been serialized through the
    --   'saveBinaryState' function. Takes a default which is
    --   returned if the state doesn't exist.
    restoreBinaryState :: Binary a => a -> IO a
    restoreBinaryState :: forall a. Binary a => a -> IO a
    restoreBinaryState a
    d = do
        statePath <- IO (Maybe FilePath)
    getStatePersist
        case statePath of
             Maybe FilePath
    Nothing -> a -> IO a
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return a
    d
             Just FilePath
    sp -> do state <- FilePath -> IO (Maybe a)
    forall a. Binary a => FilePath -> IO a
    decodeFile FilePath
    sp
                           return $ fromMaybe d state