-------------------------------------------------------------------------------- {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Hakyll.Core.Provider.Internal ( ResourceInfo (..) , Provider (..) , newProvider , resourceList , resourceExists , resourceFilePath , resourceString , resourceLBS , resourceModified , resourceModificationTime ) where -------------------------------------------------------------------------------- import Control.Applicative ((<$>), (<*>)) import Control.DeepSeq (NFData (..), deepseq) import Control.Monad (forM) import Data.Binary (Binary (..)) import qualified Data.ByteString.Lazy as BL import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (fromMaybe) import Data.Monoid (mempty) import Data.Set (Set) import qualified Data.Set as S import Data.Time (Day (..), UTCTime (..), secondsToDiffTime) import Data.Typeable (Typeable) import System.Directory (getModificationTime) import System.FilePath (addExtension, ()) -------------------------------------------------------------------------------- #if !MIN_VERSION_directory(1,2,0) import Data.Time (readTime) import System.Locale (defaultTimeLocale) import System.Time (formatCalendarTime, toCalendarTime) #endif -------------------------------------------------------------------------------- import Hakyll.Core.Identifier import Hakyll.Core.Store (Store) import qualified Hakyll.Core.Store as Store import Hakyll.Core.Util.File -------------------------------------------------------------------------------- -- | 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` () -------------------------------------------------------------------------------- -- | Responsible for retrieving and listing resources data Provider = Provider { -- Top of the provided directory providerDirectory :: FilePath , -- | A list of all files found providerFiles :: Map Identifier ResourceInfo , -- | A list of the files from the previous run providerOldFiles :: Map Identifier ResourceInfo , -- | Underlying persistent store for caching providerStore :: Store } deriving (Show) -------------------------------------------------------------------------------- -- | Create a resource provider newProvider :: Store -- ^ Store to use -> (FilePath -> IO Bool) -- ^ Should we ignore this file? -> FilePath -- ^ Search directory -> IO Provider -- ^ Resulting provider newProvider store ignore directory = do 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 -------------------------------------------------------------------------------- resourceList :: Provider -> [Identifier] resourceList = M.keys . providerFiles -------------------------------------------------------------------------------- -- | Check if a given resource exists resourceExists :: Provider -> Identifier -> Bool resourceExists provider = (`M.member` providerFiles provider) . setVersion Nothing -------------------------------------------------------------------------------- resourceFilePath :: Provider -> Identifier -> FilePath resourceFilePath p i = providerDirectory p toFilePath i -------------------------------------------------------------------------------- -- | Get the raw body of a resource as string resourceString :: Provider -> Identifier -> IO String resourceString p i = readFile $ resourceFilePath p i -------------------------------------------------------------------------------- -- | Get the raw body of a resource of a lazy bytestring resourceLBS :: Provider -> Identifier -> IO BL.ByteString resourceLBS p i = BL.readFile $ resourceFilePath p i -------------------------------------------------------------------------------- -- | A resource is modified if it or its metadata has changed resourceModified :: Provider -> Identifier -> Bool resourceModified p r = case (ri, oldRi) of (Nothing, _) -> False (Just _, Nothing) -> True (Just n, Just o) -> resourceInfoModified n > resourceInfoModified o || resourceInfoMetadata n /= resourceInfoMetadata o where normal = setVersion Nothing r ri = M.lookup normal (providerFiles p) oldRi = M.lookup normal (providerOldFiles 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