--------------------------------------------------------------------------------
-- | This module provides an wrapper API around the file system which does some
-- caching.
module Hakyll.Core.Provider
    ( -- * Constructing resource providers
      Internal.Provider
    , newProvider

      -- * Querying resource properties
    , Internal.resourceList
    , Internal.resourceExists
    , Internal.resourceFilePath
    , Internal.resourceModified
    , Internal.resourceModificationTime

      -- * Access to raw resource content
    , Internal.resourceString
    , Internal.resourceLBS

      -- * Access to metadata and body content
    , Internal.resourceMetadata
    , Internal.resourceBody
    ) where

--------------------------------------------------------------------------------
import           Control.Monad                      (forM)
import qualified Hakyll.Core.Provider.Internal      as Internal
import qualified Hakyll.Core.Provider.MetadataCache as Internal
import           Hakyll.Core.Store                  (Store)
import           Hakyll.Core.Identifier             (Identifier)


--------------------------------------------------------------------------------
-- | Create a resource provider
newProvider :: Store                                -- ^ Store to use
            -> (FilePath -> IO Bool)                -- ^ Should we ignore this file?
            -> FilePath                             -- ^ Search directory
            -> IO (Internal.Provider, [Identifier]) -- ^ Resulting provider and modified metadata
newProvider :: Store
-> (FilePath -> IO Bool) -> FilePath -> IO (Provider, [Identifier])
newProvider Store
store FilePath -> IO Bool
ignore FilePath
directory = do
  Provider
p <- Store -> (FilePath -> IO Bool) -> FilePath -> IO Provider
Internal.newProvider Store
store FilePath -> IO Bool
ignore FilePath
directory

  let modified :: [Identifier]
modified =
        (Identifier -> Bool) -> [Identifier] -> [Identifier]
forall a. (a -> Bool) -> [a] -> [a]
filter
          (Provider -> Identifier -> Bool
Internal.resourceModified Provider
p)
          (Provider -> [Identifier]
Internal.resourceList Provider
p)

  -- Delete metadata cache where necessary and extract identifiers for which the
  -- metadata was actually modified. That is, exclude identifiers where the body
  -- was modified but the metadata wasn't.
  [Identifier]
modifiedMetadata <- ([[Identifier]] -> [Identifier])
-> IO [[Identifier]] -> IO [Identifier]
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [[Identifier]] -> [Identifier]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (IO [[Identifier]] -> IO [Identifier])
-> IO [[Identifier]] -> IO [Identifier]
forall a b. (a -> b) -> a -> b
$ [Identifier]
-> (Identifier -> IO [Identifier]) -> IO [[Identifier]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [Identifier]
modified ((Identifier -> IO [Identifier]) -> IO [[Identifier]])
-> (Identifier -> IO [Identifier]) -> IO [[Identifier]]
forall a b. (a -> b) -> a -> b
$ \Identifier
identifier -> do
    Maybe Metadata
mbCachedMetadata <- Provider -> Identifier -> IO (Maybe Metadata)
Internal.resourceLookupMetadataCache Provider
p Identifier
identifier
    case Maybe Metadata
mbCachedMetadata of
      Maybe Metadata
Nothing -> [Identifier] -> IO [Identifier]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
      Just Metadata
oldMeta -> do
        Provider -> Identifier -> IO ()
Internal.resourceInvalidateMetadataCache Provider
p Identifier
identifier
        Metadata
newMeta <- Provider -> Identifier -> IO Metadata
Internal.resourceMetadata Provider
p Identifier
identifier
        [Identifier] -> IO [Identifier]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([Identifier] -> IO [Identifier])
-> [Identifier] -> IO [Identifier]
forall a b. (a -> b) -> a -> b
$ if Metadata
oldMeta Metadata -> Metadata -> Bool
forall a. Eq a => a -> a -> Bool
== Metadata
newMeta then [] else [Identifier
identifier]

  (Provider, [Identifier]) -> IO (Provider, [Identifier])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Provider
p, [Identifier]
modifiedMetadata)