-- | Manage lambdabot's state files. There are three relevant directories:
--
-- * local: @./State/@ (configurable, see `outputDir`)
-- * home:  @~/.lambdabot/State/@
-- * data:  relative to the data directory of the @lambdabot@ package.
--
-- Files are stored locally if the directory exists; otherwise, in the home
-- directory. When reading a state file, and the file exists in the data
-- directory but nowhere else, then it is picked up from the data directory.

module Lambdabot.File
    ( stateDir
    , findLBFileForReading
    , findLBFileForWriting
    , findOrCreateLBFile
    , findLBFile -- deprecated
    , outputDir
    ) where

import Lambdabot.Config
import Lambdabot.Config.Core
import Lambdabot.Monad
import Lambdabot.Util

import Control.Applicative
import Control.Monad
import System.Directory
import System.FilePath

lambdabot :: FilePath
lambdabot :: FilePath
lambdabot = FilePath
".lambdabot"

-- | Locate state directory. Returns the local directory if it exists,
-- and the home directory otherwise.
stateDir :: LB FilePath
stateDir :: LB FilePath
stateDir = do
    -- look locally
    output <- Config FilePath -> LB FilePath
forall a. Config a -> LB a
forall (m :: * -> *) a. MonadConfig m => Config a -> m a
getConfig Config FilePath
outputDir
    b <- io $ doesDirectoryExist output
    if b then return output else homeDir

homeDir :: LB FilePath
homeDir :: LB FilePath
homeDir = do
    output <- Config FilePath -> LB FilePath
forall a. Config a -> LB a
forall (m :: * -> *) a. MonadConfig m => Config a -> m a
getConfig Config FilePath
outputDir
    home <- io getHomeDirectory
    return $ home </> lambdabot </> output

-- | Look for the file in the local, home, and data directories.
findLBFileForReading :: FilePath -> LB (Maybe FilePath)
findLBFileForReading :: FilePath -> LB (Maybe FilePath)
findLBFileForReading FilePath
f = do
    state <- LB FilePath
stateDir
    home  <- homeDir
    output <- getConfig outputDir
    rodir <- getConfig dataDir
    findFirstFile [state </> f, home </> f, rodir </> output </> f]

-- | Return file name for writing state. The file will reside in the
-- state directory (`stateDir`), and `findLBFileForWriting` ensures that
-- the state directory exists.
findLBFileForWriting :: FilePath -> LB FilePath
findLBFileForWriting :: FilePath -> LB FilePath
findLBFileForWriting FilePath
f = do
    state <- LB FilePath
stateDir
    -- ensure that the directory exists
    io $ createDirectoryIfMissing True state
    success <- io $ doesDirectoryExist state
    when (not success) $ fail $ concat ["Unable to create directory ", state]
    return $ state </> f

findFirstFile :: [FilePath] -> LB (Maybe FilePath)
findFirstFile :: [FilePath] -> LB (Maybe FilePath)
findFirstFile [] = Maybe FilePath -> LB (Maybe FilePath)
forall a. a -> LB a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe FilePath
forall a. Maybe a
Nothing
findFirstFile (FilePath
path:[FilePath]
ps) = do
    b <- IO Bool -> LB Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io (IO Bool -> LB Bool) -> IO Bool -> LB Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> IO Bool
doesFileExist FilePath
path
    if b then return (Just path) else findFirstFile ps

{-# DEPRECATED findLBFile
 "Use `findLBFileForReading` or `findLBFileForWriting` instead" #-}
-- | Try to find a pre-existing file, searching first in the local or home
-- directory (but not in the data directory)
findLBFile :: FilePath -> LB (Maybe String)
findLBFile :: FilePath -> LB (Maybe FilePath)
findLBFile FilePath
f = do
    state <- LB FilePath
stateDir
    home  <- homeDir
    findFirstFile [state </> f, home </> f]

-- | This returns the same file name as `findLBFileForWriting`.
-- If the file does not exist, it is either copied from the data (or home)
-- directory, if a copy is found there; otherwise, an empty file is
-- created instead.
findOrCreateLBFile :: FilePath -> LB String
findOrCreateLBFile :: FilePath -> LB FilePath
findOrCreateLBFile FilePath
f = do
    outFile <- FilePath -> LB FilePath
findLBFileForWriting FilePath
f
    b <- io $ doesFileExist outFile
    when (not b) $ do
        -- the file does not exist; populate it from home or data directory
        b <- findLBFileForReading f
        case b of
            Maybe FilePath
Nothing      -> IO () -> LB ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io (IO () -> LB ()) -> IO () -> LB ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> IO ()
writeFile FilePath
outFile FilePath
""
            Just FilePath
roFile  -> IO () -> LB ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io (IO () -> LB ()) -> IO () -> LB ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> IO ()
copyFile FilePath
roFile FilePath
outFile
    return outFile