content: {-# LANGUAGE CPP #-}
  #if !MIN_VERSION_directory(1,3,8)
  {-# LANGUAGE Safe #-}
  #endif
  
  module System.Log.FastLogger.File (
      FileLogSpec (..),
      TimedFileLogSpec (..),
      check,
      rotate,
      prefixTime,
  ) where
  
  import Data.ByteString.Char8 (unpack)
  import System.Directory (
      doesDirectoryExist,
      doesFileExist,
      getPermissions,
      renameFile,
      writable,
   )
  import System.FilePath (dropFileName, takeDirectory, takeFileName, (</>))
  
  import System.Log.FastLogger.Imports
  import System.Log.FastLogger.Types (FormattedTime, TimeFormat)
  
  -- | The spec for logging files
  data FileLogSpec = FileLogSpec
      { log_file :: FilePath
      , log_file_size :: Integer
      -- ^ Max log file size (in bytes) before requiring rotation.
      , log_backup_number :: Int
      -- ^ Max number of rotated log files to keep around before overwriting the oldest one.
      }
  
  -- | The spec for time based rotation. It supports post processing of log files. Does
  -- not delete any logs. Example:
  --
  -- @
  -- timeRotate fname = LogFileTimedRotate
  --                (TimedFileLogSpec fname timeFormat sametime compressFile)
  --                defaultBufSize
  --    where
  --        timeFormat = "%FT%H%M%S"
  --        sametime = (==) `on` C8.takeWhile (/='T')
  --        compressFile fp = void . forkIO $
  --            callProcess "tar" [ "--remove-files", "-caf", fp <> ".gz", fp ]
  -- @
  data TimedFileLogSpec = TimedFileLogSpec
      { timed_log_file :: FilePath
      -- ^ base file path
      , timed_timefmt :: TimeFormat
      -- ^ time format to prepend
      , timed_same_timeframe :: FormattedTime -> FormattedTime -> Bool
      -- ^ function that compares two
      --   formatted times as specified by
      --   timed_timefmt and decides if a
      --   new rotation is supposed to
      --   begin
      , timed_post_process :: FilePath -> IO ()
      -- ^ processing function called asynchronously after a file is added to the rotation
      }
  
  -- | Checking if a log file can be written.
  check :: FilePath -> IO ()
  check file = do
      dirExist <- doesDirectoryExist dir
      unless dirExist $ fail $ dir ++ " does not exist or is not a directory."
      dirPerm <- getPermissions dir
      unless (writable dirPerm) $ fail $ dir ++ " is not writable."
      exist <- doesFileExist file
      when exist $ do
          perm <- getPermissions file
          unless (writable perm) $ fail $ file ++ " is not writable."
    where
      dir = takeDirectory file
  
  -- | Rotating log files.
  rotate :: FileLogSpec -> IO ()
  rotate spec = mapM_ move srcdsts
    where
      path = log_file spec
      n = log_backup_number spec
      dsts' = reverse . ("" :) . map (('.' :) . show) $ [0 .. n - 1]
      dsts = map (path ++) dsts'
      srcs = drop 1 dsts
      srcdsts = zip srcs dsts
      move (src, dst) = do
          exist <- doesFileExist src
          when exist $ renameFile src dst
  
  -- | Prefix file name with formatted time
  prefixTime :: FormattedTime -> FilePath -> FilePath
  prefixTime time path = dropFileName path </> unpack time ++ "-" ++ takeFileName path
  