-- | -- -- Copyright: -- This file is part of the package vimeta. It is subject to the -- license terms in the LICENSE file found in the top-level -- directory of this distribution and at: -- -- https://github.com/pjones/vimeta -- -- No part of this package, including this file, may be copied, -- modified, propagated, or distributed except according to the terms -- contained in the LICENSE file. -- -- License: BSD-2-Clause module Vimeta.Core.Vimeta ( Vimeta (..), Context (..), MonadIO, throwError, runIO, runIOE, tmdb, verbose, execVimetaWithContext, execVimeta, runVimeta, ) where import Byline (BylineT, MonadByline, runBylineT) import Control.Monad.Catch import Control.Monad.Except import qualified Data.Text.IO as Text import GHC.IO.Encoding (setLocaleEncoding, utf8) import qualified Network.API.TheMovieDB as TMDb import Network.HTTP.Client (Manager, newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) import Vimeta.Core.Cache import Vimeta.Core.Config data Context = Context { ctxManager :: Manager, ctxConfig :: Config, ctxTMDBCfg :: TMDb.Configuration, ctxVerboseH :: Handle } newtype Vimeta m a = Vimeta {unV :: ReaderT Context (BylineT (ExceptT String m)) a} deriving ( Functor, Applicative, Monad, MonadIO, MonadReader Context, MonadError String, MonadByline ) runIO :: (MonadIO m) => IO a -> Vimeta m a runIO io = liftIO (try io) >>= sinkIO where sinkIO :: (Monad m) => Either SomeException a -> Vimeta m a sinkIO (Left e) = throwError (show e) sinkIO (Right a) = return a runIOE :: (MonadIO m) => IO (Either String a) -> Vimeta m a runIOE io = runIO io >>= either (throwError . show) return -- | Run a 'TheMovieDB' operation. tmdb :: (MonadIO m) => TMDb.TheMovieDB a -> Vimeta m a tmdb t = do context' <- ask let manager = ctxManager context' key = configTMDBKey (ctxConfig context') settings = TMDb.defaultSettings key result <- liftIO (TMDb.runTheMovieDBWithManager manager settings t) case result of Left e -> throwError (show e) Right r -> return r verbose :: (MonadIO m) => Text -> Vimeta m () verbose msg = do context <- ask let okay = configVerbose (ctxConfig context) || configDryRun (ctxConfig context) when okay $ liftIO $ Text.hPutStrLn (ctxVerboseH context) msg loadTMDBConfig :: (MonadIO m) => Manager -> TMDb.Settings -> ExceptT String m TMDb.Configuration loadTMDBConfig manager settings = do result <- cacheTMDBConfig ( liftIO $ TMDb.runTheMovieDBWithManager manager settings TMDb.config ) case result of Left e -> throwError (show e) Right c -> return c -- | Very primitive way of running a 'Vimeta' value with the given 'Context'. -- Mostly useful for running vimeta action within another vimeta -- action. execVimetaWithContext :: (MonadIO m, MonadMask m) => Context -> Vimeta m a -> m (Either String a) execVimetaWithContext context vimeta = unV vimeta & (`runReaderT` context) & runBylineT & (>>= maybe (throwError "EOF") pure) & runExceptT -- | Force the current process to use UTF-8 for output. forceUTF8 :: IO () forceUTF8 = setLocaleEncoding utf8 -- | Run a 'Vimeta' operation after loading the configuration file -- from disk. execVimeta :: (MonadIO m, MonadMask m) => -- | Modify configuration before running. (Config -> Config) -> -- | The Vimeta value to execute. Vimeta m a -> -- | The result. m (Either String a) execVimeta cf vimeta = runExceptT $ do liftIO forceUTF8 config <- cf <$> readConfig manager <- liftIO $ newManager tlsManagerSettings tc <- loadTMDBConfig manager (TMDb.defaultSettings (configTMDBKey config)) ExceptT $ execVimetaWithContext (Context manager config tc stdout) vimeta -- | Simple wrapper around 'execVimeta'. runVimeta :: (MonadIO m, MonadMask m) => Vimeta m a -> m (Either String a) runVimeta = execVimeta id