50 lines
2.0 KiB
Haskell
50 lines
2.0 KiB
Haskell
module Foundation.Yesod.StaticContent
|
|
( addStaticContent
|
|
) where
|
|
|
|
import Import.NoFoundation hiding (addStaticContent)
|
|
|
|
import Foundation.Type
|
|
|
|
import qualified Database.Memcached.Binary.IO as Memcached
|
|
|
|
import qualified Data.ByteString.Lazy as Lazy
|
|
import qualified Data.ByteString.Base64.URL as Base64 (encodeUnpadded)
|
|
import Data.ByteArray (convert)
|
|
import Crypto.Hash (SHAKE256)
|
|
import Crypto.Hash.Conduit (sinkHash)
|
|
import Data.Bits (Bits(zeroBits))
|
|
|
|
import qualified Data.Conduit.Combinators as C
|
|
|
|
|
|
addStaticContent :: Text
|
|
-> Text
|
|
-> Lazy.ByteString
|
|
-> HandlerFor UniWorX (Maybe (Either Text (Route UniWorX, [(Text, Text)])))
|
|
addStaticContent ext _mime content = do
|
|
UniWorX{appWidgetMemcached, appSettings'} <- getYesod
|
|
for ((,) <$> appWidgetMemcached <*> appWidgetMemcachedConf appSettings') $ \(mConn, WidgetMemcachedConf{ widgetMemcachedConf = MemcachedConf { memcachedExpiry }, widgetMemcachedBaseUrl }) -> do
|
|
let expiry = maybe 0 ceiling memcachedExpiry
|
|
touch = liftIO $ Memcached.touch expiry (encodeUtf8 $ pack fileName) mConn
|
|
add = liftIO $ Memcached.add zeroBits expiry (encodeUtf8 $ pack fileName) content mConn
|
|
absoluteLink = unpack widgetMemcachedBaseUrl </> fileName
|
|
catchIf Memcached.isKeyNotFound touch . const $
|
|
handleIf Memcached.isKeyExists (const $ return ()) add
|
|
return . Left $ pack absoluteLink
|
|
where
|
|
-- Generate a unique filename based on the content itself, this is used
|
|
-- for deduplication so a collision resistant hash function is required
|
|
--
|
|
-- SHA-3 (SHAKE256) seemed to be a future-proof choice
|
|
--
|
|
-- Length of hash is 144 bits ~~instead of MD5's 128, so as to avoid
|
|
-- padding after base64-conversion~~ for backwards compatability
|
|
fileName = (<.> unpack ext)
|
|
. unpack
|
|
. decodeUtf8
|
|
. Base64.encodeUnpadded
|
|
. (convert :: Digest (SHAKE256 144) -> ByteString)
|
|
. runConduitPure
|
|
$ C.sourceLazy content .| sinkHash
|