{-|
Module      : Hoyo.Env
Copyright   : (c) Frederick Pringle, 2023
License     : BSD-3-Clause
Maintainer  : freddyjepringle@gmail.com

The read-only hoyo environment.
-}

module Hoyo.Env (
  -- * Hoyo config
  Env (..)
  , initEnv
  , getEnv
  , writeEnv
  , readEnv

  -- ** Default file paths
  , defaultBookmarksPath
  , defaultConfigPath
  ) where

import Control.Monad.Catch
import Control.Monad.Except

import Hoyo.Bookmark
import Hoyo.Config
import Hoyo.Internal.Types
import Hoyo.Utils

import Lens.Micro.Extras

import System.Directory
import System.FilePath

-- | Write an 'Env' to file.
writeEnv :: (MonadIO m, MonadCatch m) => Env -> m ()
writeEnv :: Env -> m ()
writeEnv Env
env = do
  m () -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ FilePath -> Bookmarks -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> Bookmarks -> m ()
encodeBookmarksFile (Getting FilePath Env FilePath -> Env -> FilePath
forall a s. Getting a s a -> s -> a
view Getting FilePath Env FilePath
Lens' Env FilePath
bookmarksPath Env
env) (Getting Bookmarks Env Bookmarks -> Env -> Bookmarks
forall a s. Getting a s a -> s -> a
view Getting Bookmarks Env Bookmarks
Lens' Env Bookmarks
bookmarks Env
env)
  m (Either HoyoException ()) -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m (Either HoyoException ()) -> m ())
-> m (Either HoyoException ()) -> m ()
forall a b. (a -> b) -> a -> b
$ FilePath -> Config -> m (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> Config -> m (Either HoyoException ())
encodeConfigFile (Getting FilePath Env FilePath -> Env -> FilePath
forall a s. Getting a s a -> s -> a
view Getting FilePath Env FilePath
Lens' Env FilePath
configPath Env
env) (Getting Config Env Config -> Env -> Config
forall a s. Getting a s a -> s -> a
view Getting Config Env Config
Lens' Env Config
config Env
env)

-- | Read an 'Env' from a file.
readEnv :: (MonadIO m, MonadCatch m) => FilePath -> FilePath -> m (Either HoyoException Env)
readEnv :: FilePath -> FilePath -> m (Either HoyoException Env)
readEnv FilePath
bFp FilePath
sFp = do
  Either HoyoException Bookmarks
bs <- FilePath -> m (Either HoyoException Bookmarks)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> m (Either HoyoException Bookmarks)
decodeBookmarksFile FilePath
bFp
  Either HoyoException Config
se <- FilePath -> m (Either HoyoException Config)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> m (Either HoyoException Config)
decodeConfigFile FilePath
sFp
  case (Either HoyoException Bookmarks
bs, Either HoyoException Config
se) of
    (Right Bookmarks
b, Right Config
s) -> Either HoyoException Env -> m (Either HoyoException Env)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either HoyoException Env -> m (Either HoyoException Env))
-> Either HoyoException Env -> m (Either HoyoException Env)
forall a b. (a -> b) -> a -> b
$ Env -> Either HoyoException Env
forall a b. b -> Either a b
Right (Bookmarks -> FilePath -> Config -> FilePath -> Env
Env Bookmarks
b FilePath
bFp Config
s FilePath
sFp)
    (Left HoyoException
e, Right Config
_)  -> Either HoyoException Env -> m (Either HoyoException Env)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either HoyoException Env -> m (Either HoyoException Env))
-> Either HoyoException Env -> m (Either HoyoException Env)
forall a b. (a -> b) -> a -> b
$ HoyoException -> Either HoyoException Env
forall a b. a -> Either a b
Left HoyoException
e
    (Right Bookmarks
_, Left HoyoException
e)  -> Either HoyoException Env -> m (Either HoyoException Env)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either HoyoException Env -> m (Either HoyoException Env))
-> Either HoyoException Env -> m (Either HoyoException Env)
forall a b. (a -> b) -> a -> b
$ HoyoException -> Either HoyoException Env
forall a b. a -> Either a b
Left HoyoException
e
    (Left HoyoException
e1, Left HoyoException
e2) -> Either HoyoException Env -> m (Either HoyoException Env)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either HoyoException Env -> m (Either HoyoException Env))
-> Either HoyoException Env -> m (Either HoyoException Env)
forall a b. (a -> b) -> a -> b
$ HoyoException -> Either HoyoException Env
forall a b. a -> Either a b
Left (HoyoException -> Either HoyoException Env)
-> HoyoException -> Either HoyoException Env
forall a b. (a -> b) -> a -> b
$ HoyoException
e1 HoyoException -> HoyoException -> HoyoException
forall a. Semigroup a => a -> a -> a
<> HoyoException
e2

-- | Given a file path, make sure that its directory exists.
initPath :: MonadIO m => FilePath -> m ()
initPath :: FilePath -> m ()
initPath FilePath
fp' = do
  FilePath
fp <- IO FilePath -> m FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FilePath -> m FilePath) -> IO FilePath -> m FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> IO FilePath
makeAbsolute FilePath
fp'
  let dir :: FilePath
dir = FilePath -> FilePath
takeDirectory FilePath
fp
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> FilePath -> IO ()
createDirectoryIfMissing Bool
True FilePath
dir

-- | Given a filepath for the bookmarks file and a filepath for the config file,
-- initialize the respective TOMLs at those locations.
initEnv :: (MonadIO m, MonadCatch m) => FilePath -> FilePath -> m ()
initEnv :: FilePath -> FilePath -> m ()
initEnv FilePath
bFp FilePath
sFp = do
  FilePath -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> m ()
initPath FilePath
sFp
  FilePath -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> m ()
initPath FilePath
bFp
  Bookmarks
bms <- [DefaultBookmark] -> m Bookmarks
forall (m :: * -> *). MonadIO m => [DefaultBookmark] -> m Bookmarks
bookmarksFromDefault ([DefaultBookmark] -> m Bookmarks)
-> [DefaultBookmark] -> m Bookmarks
forall a b. (a -> b) -> a -> b
$ Getting [DefaultBookmark] Config [DefaultBookmark]
-> Config -> [DefaultBookmark]
forall a s. Getting a s a -> s -> a
view Getting [DefaultBookmark] Config [DefaultBookmark]
Lens' Config [DefaultBookmark]
defaultBookmarks Config
defaultConfig
  let env :: Env
env = Bookmarks -> FilePath -> Config -> FilePath -> Env
Env Bookmarks
bms FilePath
bFp Config
defaultConfig FilePath
sFp
  Env -> m ()
forall (m :: * -> *). (MonadIO m, MonadCatch m) => Env -> m ()
writeEnv Env
env

-- | If the bookmarks path doesn't exist, try to create it.
--
-- Returns the newly created 'Bookmarks' object, or the result of parsing
-- the file if it already existed.
initBookmarksIfNotExists :: (MonadIO m, MonadCatch m, MonadError HoyoException m) => Config -> FilePath -> m Bookmarks
initBookmarksIfNotExists :: Config -> FilePath -> m Bookmarks
initBookmarksIfNotExists Config
cfg FilePath
fp' = do
  FilePath
fp <- IO FilePath -> m FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FilePath -> m FilePath) -> IO FilePath -> m FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> IO FilePath
makeAbsolute FilePath
fp'
  Bool
ex <- IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> IO Bool
doesFileExist FilePath
fp
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
ex (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    FilePath -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> m ()
initPath FilePath
fp
    Bookmarks
bms <- [DefaultBookmark] -> m Bookmarks
forall (m :: * -> *). MonadIO m => [DefaultBookmark] -> m Bookmarks
bookmarksFromDefault ([DefaultBookmark] -> m Bookmarks)
-> [DefaultBookmark] -> m Bookmarks
forall a b. (a -> b) -> a -> b
$ Getting [DefaultBookmark] Config [DefaultBookmark]
-> Config -> [DefaultBookmark]
forall a s. Getting a s a -> s -> a
view Getting [DefaultBookmark] Config [DefaultBookmark]
Lens' Config [DefaultBookmark]
defaultBookmarks Config
cfg
    FilePath -> Bookmarks -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> Bookmarks -> m ()
encodeBookmarksFile FilePath
fp Bookmarks
bms
  FilePath -> m (Either HoyoException Bookmarks)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> m (Either HoyoException Bookmarks)
decodeBookmarksFile FilePath
fp m (Either HoyoException Bookmarks)
-> (Either HoyoException Bookmarks -> m Bookmarks) -> m Bookmarks
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Either HoyoException Bookmarks -> m Bookmarks
forall e (m :: * -> *) a. MonadError e m => Either e a -> m a
liftEither

-- | If the config path doesn't exist, try to create it.
--
-- Returns the newly created 'Config' object, or the result of parsing
-- the file if it already existed.
initConfigIfNotExists :: (MonadIO m, MonadCatch m, MonadError HoyoException m) => FilePath -> m Config
initConfigIfNotExists :: FilePath -> m Config
initConfigIfNotExists FilePath
fp' = do
  FilePath
fp <- IO FilePath -> m FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FilePath -> m FilePath) -> IO FilePath -> m FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> IO FilePath
makeAbsolute FilePath
fp'
  Bool
exists <- IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> IO Bool
doesFileExist FilePath
fp
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
exists (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    FilePath -> m ()
forall (m :: * -> *). MonadIO m => FilePath -> m ()
initPath FilePath
fp
    m (Either HoyoException ()) -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m (Either HoyoException ()) -> m ())
-> m (Either HoyoException ()) -> m ()
forall a b. (a -> b) -> a -> b
$ FilePath -> Config -> m (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> Config -> m (Either HoyoException ())
encodeConfigFile FilePath
fp Config
defaultConfig
  FilePath -> m (Either HoyoException Config)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
FilePath -> m (Either HoyoException Config)
decodeConfigFile FilePath
fp m (Either HoyoException Config)
-> (Either HoyoException Config -> m Config) -> m Config
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Either HoyoException Config -> m Config
forall e (m :: * -> *) a. MonadError e m => Either e a -> m a
liftEither

-- | If the environment files have not been created yet, do so.
--
-- Return the 'Env' object.
initEnvIfNotExists :: (MonadIO m, MonadCatch m, MonadError HoyoException m) => FilePath -> FilePath -> m Env
initEnvIfNotExists :: FilePath -> FilePath -> m Env
initEnvIfNotExists FilePath
bFp FilePath
sFp = do
  Config
cfg <- FilePath -> m Config
forall (m :: * -> *).
(MonadIO m, MonadCatch m, MonadError HoyoException m) =>
FilePath -> m Config
initConfigIfNotExists FilePath
sFp
  Bookmarks
bms <- Config -> FilePath -> m Bookmarks
forall (m :: * -> *).
(MonadIO m, MonadCatch m, MonadError HoyoException m) =>
Config -> FilePath -> m Bookmarks
initBookmarksIfNotExists Config
cfg FilePath
bFp
  Env -> m Env
forall (m :: * -> *) a. Monad m => a -> m a
return (Env -> m Env) -> Env -> m Env
forall a b. (a -> b) -> a -> b
$ Bookmarks -> FilePath -> Config -> FilePath -> Env
Env Bookmarks
bms FilePath
bFp Config
cfg FilePath
sFp

-- | Retrieve an 'Env' from given bookmark- and config- file locations.
getEnv :: (MonadIO m, MonadCatch m) => FilePath -> FilePath -> m (Either HoyoException Env)
getEnv :: FilePath -> FilePath -> m (Either HoyoException Env)
getEnv FilePath
bFp' FilePath
sFp' = do
  FilePath
sFp <- IO FilePath -> m FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (FilePath -> IO FilePath
makeAbsolute FilePath
sFp')
  FilePath
bFp <- IO FilePath -> m FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (FilePath -> IO FilePath
makeAbsolute FilePath
bFp')
  ExceptT HoyoException m Env -> m (Either HoyoException Env)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT HoyoException m Env -> m (Either HoyoException Env))
-> ExceptT HoyoException m Env -> m (Either HoyoException Env)
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> ExceptT HoyoException m Env
forall (m :: * -> *).
(MonadIO m, MonadCatch m, MonadError HoyoException m) =>
FilePath -> FilePath -> m Env
initEnvIfNotExists FilePath
bFp FilePath
sFp

-- | The default path for the hoyo config. Usually $HOME\/.config\/hoyo\/config.toml
defaultConfigPath :: IO FilePath
defaultConfigPath :: IO FilePath
defaultConfigPath = XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgConfig FilePath
"hoyo/config.toml"

-- |The default path for hoyo bookmarks. Usually $HOME\/.local\/share\/hoyo\/config.toml
defaultBookmarksPath :: IO FilePath
defaultBookmarksPath :: IO FilePath
defaultBookmarksPath = XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgData FilePath
"hoyo/bookmarks.toml"