hakyll/src/Hakyll/Core/Provider/Internal.hs

203 lines
7.8 KiB
Haskell
Raw Normal View History

2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
2013-02-09 14:11:40 +00:00
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2012-11-18 20:56:52 +00:00
module Hakyll.Core.Provider.Internal
2013-02-09 14:11:40 +00:00
( ResourceInfo (..)
, Provider (..)
2012-11-18 20:56:52 +00:00
, newProvider
2012-11-08 11:45:26 +00:00
, resourceList
, resourceExists
2012-11-19 13:59:55 +00:00
, resourceFilePath
2012-11-08 11:45:26 +00:00
, resourceString
, resourceLBS
2013-02-09 14:11:40 +00:00
, resourceModified
, resourceModificationTime
2012-11-08 11:45:26 +00:00
) where
--------------------------------------------------------------------------------
2013-02-09 14:11:40 +00:00
import Control.Applicative ((<$>), (<*>))
import Control.DeepSeq (NFData (..), deepseq)
import Control.Monad (forM)
import Data.Binary (Binary (..))
2012-11-19 13:59:55 +00:00
import qualified Data.ByteString.Lazy as BL
import Data.Map (Map)
import qualified Data.Map as M
2013-02-09 14:11:40 +00:00
import Data.Maybe (fromMaybe)
import Data.Monoid (mempty)
2012-11-19 13:59:55 +00:00
import Data.Set (Set)
import qualified Data.Set as S
2013-02-09 14:11:40 +00:00
import Data.Time (Day (..), UTCTime (..),
secondsToDiffTime)
import Data.Typeable (Typeable)
import System.Directory (getModificationTime)
2012-11-19 13:59:55 +00:00
import System.FilePath (addExtension, (</>))
2012-11-08 11:45:26 +00:00
2013-02-09 14:11:40 +00:00
--------------------------------------------------------------------------------
#if !MIN_VERSION_directory(1,2,0)
import Data.Time (readTime)
import System.Locale (defaultTimeLocale)
import System.Time (formatCalendarTime, toCalendarTime)
#endif
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
2012-11-19 13:59:55 +00:00
import Hakyll.Core.Identifier
2013-02-09 14:11:40 +00:00
import Hakyll.Core.Store (Store)
import qualified Hakyll.Core.Store as Store
2012-11-08 11:45:26 +00:00
import Hakyll.Core.Util.File
2013-02-09 14:11:40 +00:00
--------------------------------------------------------------------------------
-- | Because UTCTime doesn't have a Binary instance...
newtype BinaryTime = BinaryTime {unBinaryTime :: UTCTime}
deriving (Eq, NFData, Ord, Show, Typeable)
--------------------------------------------------------------------------------
instance Binary BinaryTime where
put (BinaryTime (UTCTime (ModifiedJulianDay d) dt)) =
put d >> put (floor dt :: Integer)
get = fmap BinaryTime $ UTCTime
<$> (ModifiedJulianDay <$> get)
<*> (secondsToDiffTime <$> get)
--------------------------------------------------------------------------------
data ResourceInfo = ResourceInfo
{ resourceInfoModified :: BinaryTime
, resourceInfoMetadata :: Maybe Identifier
} deriving (Show, Typeable)
--------------------------------------------------------------------------------
instance Binary ResourceInfo where
put (ResourceInfo mtime meta) = put mtime >> put meta
get = ResourceInfo <$> get <*> get
--------------------------------------------------------------------------------
instance NFData ResourceInfo where
rnf (ResourceInfo mtime meta) = rnf mtime `seq` rnf meta `seq` ()
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
-- | Responsible for retrieving and listing resources
2012-11-18 20:56:52 +00:00
data Provider = Provider
2012-11-19 13:59:55 +00:00
{ -- Top of the provided directory
2013-02-09 14:11:40 +00:00
providerDirectory :: FilePath
2012-11-19 13:59:55 +00:00
, -- | A list of all files found
2013-02-09 14:11:40 +00:00
providerFiles :: Map Identifier ResourceInfo
, -- | A list of the files from the previous run
providerOldFiles :: Map Identifier ResourceInfo
2012-11-08 11:45:26 +00:00
, -- | Underlying persistent store for caching
2013-02-09 14:11:40 +00:00
providerStore :: Store
2012-11-08 11:45:26 +00:00
}
--------------------------------------------------------------------------------
-- | Create a resource provider
2012-11-18 20:56:52 +00:00
newProvider :: Store -- ^ Store to use
-> (FilePath -> Bool) -- ^ Should we ignore this file?
-> FilePath -- ^ Search directory
-> IO Provider -- ^ Resulting provider
newProvider store ignore directory = do
2013-02-09 14:11:40 +00:00
list <- map fromFilePath <$> getRecursiveContents ignore directory
let universe = S.fromList list
files <- fmap (maxmtime . M.fromList) $ forM list $ \identifier -> do
rInfo <- getResourceInfo directory universe identifier
return (identifier, rInfo)
-- Get the old files from the store, and then immediately replace them by
-- the new files.
oldFiles <- fromMaybe mempty . Store.toMaybe <$> Store.get store oldKey
oldFiles `deepseq` Store.set store oldKey files
return $ Provider directory files oldFiles store
where
oldKey = ["Hakyll.Core.Provider.Internal.newProvider", "oldFiles"]
-- Update modified if metadata is modified
maxmtime files = flip M.map files $ \rInfo@(ResourceInfo mtime meta) ->
let metaMod = fmap resourceInfoModified $ meta >>= flip M.lookup files
in rInfo {resourceInfoModified = maybe mtime (max mtime) metaMod}
--------------------------------------------------------------------------------
getResourceInfo :: FilePath -> Set Identifier -> Identifier -> IO ResourceInfo
getResourceInfo directory universe identifier = do
mtime <- fileModificationTime $ directory </> toFilePath identifier
return $ ResourceInfo (BinaryTime mtime) $
if mdRsc `S.member` universe then Just mdRsc else Nothing
where
mdRsc = fromFilePath $ flip addExtension "metadata" $ toFilePath identifier
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
2012-11-18 20:56:52 +00:00
resourceList :: Provider -> [Identifier]
2013-02-09 14:11:40 +00:00
resourceList = M.keys . providerFiles
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
2012-11-12 15:10:06 +00:00
-- | Check if a given resource exists
2012-11-18 20:56:52 +00:00
resourceExists :: Provider -> Identifier -> Bool
2012-11-09 15:34:45 +00:00
resourceExists provider =
2013-02-09 14:11:40 +00:00
(`M.member` providerFiles provider) . setVersion Nothing
2012-11-08 11:45:26 +00:00
2012-11-19 13:59:55 +00:00
--------------------------------------------------------------------------------
resourceFilePath :: Provider -> Identifier -> FilePath
resourceFilePath p i = providerDirectory p </> toFilePath i
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
-- | Get the raw body of a resource as string
2012-11-19 13:59:55 +00:00
resourceString :: Provider -> Identifier -> IO String
resourceString p i = readFile $ resourceFilePath p i
2012-11-08 11:45:26 +00:00
--------------------------------------------------------------------------------
-- | Get the raw body of a resource of a lazy bytestring
2012-11-19 13:59:55 +00:00
resourceLBS :: Provider -> Identifier -> IO BL.ByteString
resourceLBS p i = BL.readFile $ resourceFilePath p i
2013-02-09 14:11:40 +00:00
--------------------------------------------------------------------------------
-- | A resource is modified if it or its metadata has changed
resourceModified :: Provider -> Identifier -> Bool
resourceModified p r = case (ri, oldRi) of
(Nothing, _) -> True
(Just _, Nothing) -> True
(Just n, Just o) -> resourceInfoModified n > resourceInfoModified o
where
ri = M.lookup (setVersion Nothing r) (providerFiles p)
oldRi = ri >>= resourceInfoMetadata >>= flip M.lookup (providerFiles p)
--------------------------------------------------------------------------------
resourceModificationTime :: Provider -> Identifier -> UTCTime
resourceModificationTime p i =
case M.lookup (setVersion Nothing i) (providerFiles p) of
Just ri -> unBinaryTime $ resourceInfoModified ri
Nothing -> error $
"Hakyll.Core.Provider.Internal.resourceModificationTime: " ++
"resource " ++ show i ++ " does not exist"
--------------------------------------------------------------------------------
fileModificationTime :: FilePath -> IO UTCTime
fileModificationTime fp = do
#if MIN_VERSION_directory(1,2,0)
getModificationTime fp
#else
ct <- toCalendarTime =<< getModificationTime fp
let str = formatCalendarTime defaultTimeLocale "%s" ct
return $ readTime defaultTimeLocale "%s" str
#endif