{-# LANGUAGE ScopedTypeVariables #-}{-# LANGUAGE OverloadedStrings #-}-- | Access files on the filesystem.moduleWaiAppStatic.Storage.Filesystem(-- * TypesETagLookup-- * Settings,defaultWebAppSettings,defaultFileServerSettings,webAppSettingsWithLookup)whereimportWaiAppStatic.TypesimportPreludehiding(FilePath)importFilesystem.Path.CurrentOS(FilePath,(</>))importqualifiedFilesystem.Path.CurrentOSasFimportqualifiedFilesystemasFimportData.List(foldl')importControl.Monad(forM)importUtilimportData.ByteString(ByteString)importControl.Exception(SomeException,try)importqualifiedNetwork.WaiasWimportWaiAppStatic.ListingimportNetwork.MimeimportSystem.PosixCompat.Files(fileSize,getFileStatus,modificationTime,isRegularFile)importData.Maybe(catMaybes)importqualifiedCrypto.ConduitimportData.Serialize(encode)importCrypto.Hash.CryptoAPI(MD5)importqualifiedData.ByteString.Base64asB64-- | Construct a new path from a root and some @Pieces@.pathFromPieces::FilePath->Pieces->FilePathpathFromPieces=foldl'(\fpp->fp</>F.fromText(fromPiecep))-- | Settings optimized for a web application. Files will have aggressive-- caching applied and hashes calculated, and indices and listings are disabled.defaultWebAppSettings::FilePath-- ^ root folder to serve from->StaticSettingsdefaultWebAppSettingsroot=StaticSettings{ssLookupFile=webAppLookuphashFileIfExistsroot,ssMkRedirect=defaultMkRedirect,ssGetMimeType=return.defaultMimeLookup.fromPiece.fileName,ssMaxAge=MaxAgeForever,ssListing=Nothing,ssIndices=[],ssRedirectToIndex=False,ssUseHash=True}-- | Settings optimized for a file server. More conservative caching will be-- applied, and indices and listings are enabled.defaultFileServerSettings::FilePath-- ^ root folder to serve from->StaticSettingsdefaultFileServerSettingsroot=StaticSettings{ssLookupFile=fileSystemLookup(fmapJust.hashFile)root,ssMkRedirect=defaultMkRedirect,ssGetMimeType=return.defaultMimeLookup.fromPiece.fileName,ssMaxAge=NoMaxAge,ssListing=JustdefaultListing,ssIndices=mapunsafeToPiece["index.html","index.htm"],ssRedirectToIndex=False,ssUseHash=False}-- | Same as @defaultWebAppSettings@, but additionally uses a specialized-- @ETagLookup@ in place of the standard one. This can allow you to cache your-- hash values, or even precompute them.webAppSettingsWithLookup::FilePath-- ^ root folder to serve from->ETagLookup->StaticSettingswebAppSettingsWithLookupdiretagLookup=(defaultWebAppSettingsdir){ssLookupFile=webAppLookupetagLookupdir}-- | Convenience wrapper for @fileHelper@.fileHelperLR::ETagLookup->FilePath-- ^ file location->Piece-- ^ file name->IOLookupResultfileHelperLRabc=fmap(maybeLRNotFoundLRFile)$fileHelperabc-- | Attempt to load up a @File@ from the given path.fileHelper::ETagLookup->FilePath-- ^ file location->Piece-- ^ file name->IO(MaybeFile)fileHelperhashFuncfpname=doefs<-try$getFileStatus$F.encodeStringfpcaseefsofLeft(_::SomeException)->returnNothingRightfs|isRegularFilefs->return$JustFile{fileGetSize=fromIntegral$fileSizefs,fileToResponse=\sh->W.ResponseFilesh(F.encodeStringfp)Nothing,fileName=name,fileGetHash=hashFuncfp,fileGetModified=Just$modificationTimefs}Right_->returnNothing-- | How to calculate etags. Can perform filesystem reads on each call, or use-- some caching mechanism.typeETagLookup=FilePath->IO(MaybeByteString)-- | More efficient than @fileSystemLookup@ as it only concerns itself with-- finding files, not folders.webAppLookup::ETagLookup->FilePath->Pieces->IOLookupResultwebAppLookuphashFuncprefixpieces=fileHelperLRhashFuncfplastPiecewherefp=pathFromPiecesprefixpieceslastPiece|nullpieces=unsafeToPiece""|otherwise=lastpieces-- | MD5 hash and base64-encode the file contents. Does not check if the file-- exists.hashFile::FilePath->IOByteStringhashFilefp=doh<-Crypto.Conduit.hashFile(F.encodeStringfp)return$B64.encode$encode(h::MD5)hashFileIfExists::ETagLookuphashFileIfExistsfp=dores<-try$hashFilefpreturn$caseresofLeft(_::SomeException)->NothingRightx->JustxisVisible::FilePath->BoolisVisible=go.F.encodeString.F.filenamewherego('.':_)=Falsego""=Falsego_=True-- | Get a proper @LookupResult@, checking if the path is a file or folder.-- Compare with @webAppLookup@, which only deals with files.fileSystemLookup::ETagLookup->FilePath->Pieces->IOLookupResultfileSystemLookuphashFuncprefixpieces=doletfp=pathFromPiecesprefixpiecesfe<-F.isFilefpiffethenfileHelperLRhashFuncfplastPieceelsedode<-F.isDirectoryfpifdethendoentries'<-fmap(filterisVisible)$F.listDirectoryfpentries<-forMentries'$\fp'->doletname=unsafeToPiece$eitheridid$F.toText$F.filenamefp'de'<-F.isDirectoryfp'ifde'thenreturn$Just$Leftnameelsedomfile<-fileHelperhashFuncfp'namecasemfileofNothing->returnNothingJustfile->return$Just$Rightfilereturn$LRFolder$Folder$catMaybesentrieselsereturnLRNotFoundwherelastPiece|nullpieces=unsafeToPiece""|otherwise=lastpieces