@@ -1,22 +1,26 @@
11{-# LANGUAGE OverloadedStrings #-}
22module WaiAppStatic.Types
3− ( Pieces
3+ ( -- * Pieces
4+ Piece
45 , toPiece
6+ , fromPiece
57 , unsafeToPiece
8+ , Pieces
69 , toPieces
7− , fromPiece
810 , pieceExtensions
9− , MaxAge (..)
10− , Folder (..)
11− , File (..)
12− , Piece
13− , emptyParentFolder
11+ -- * Mime
1412 , MimeType
1513 , Extension
1614 , MimeMap
15+ -- * File\/folder serving
16+ , MaxAge (..)
17+ , Folder (..)
18+ , File (..)
1719 , LookupResult (..)
18− , StaticSettings (..)
1920 , Listing
21+ -- * Settings
22+ , StaticSettings (..)
23+ , emptyParentFolder
2024 ) where
2125
2226import Data.Text (Text)
@@ -29,10 +33,19 @@ import qualified Data.Map as Map
2933import Blaze.ByteString.Builder (Builder)
3034
3135-- | An individual component of a path, or of a filepath.
36+--
37+-- This is the core type used by wai-app-static for doing lookups. It provides
38+-- a smart constructor to avoid the possibility of constructing unsafe path
39+-- segments (though @unsafeToPiece@ can get around that as necessary).
40+--
41+-- Individual file lookup backends must know how to convert from a @Piece@ to
42+-- their storage system.
3243newtype Piece = Piece { fromPiece :: Text }
3344 deriving (Show, Eq, Ord)
3445
35−-- | Smart constructor for a @Piece@. Won\'t allow unsafe components.
46+-- | Smart constructor for a @Piece@. Won\'t allow unsafe components, such as
47+-- pieces beginning with a period or containing a slash. This /will/, however,
48+-- allow null pieces.
3649toPiece :: Text -> Maybe Piece
3750toPiece t
3851 | T.null t = Just $ Piece t
@@ -40,15 +53,22 @@ toPiece t
4053 | T.any (== '/') t = Nothing
4154 | otherwise = Just $ Piece t
4255
56+-- | Construct a @Piece@ without input validation.
4357unsafeToPiece :: Text -> Piece
4458unsafeToPiece = Piece
4559
60+-- | Call @toPiece@ on a list.
61+--
62+-- > toPieces = mapM toPiece
4663toPieces :: [Text] -> Maybe Pieces
4764toPieces = mapM toPiece
4865
4966-- | Request coming from a user. Corresponds to @pathInfo@.
5067type Pieces = [Piece]
5168
69+-- | Get a list of all of the file name extensions from a piece.
70+--
71+-- > pieceExtensions (unsafeToPiece "foo.tar.gz") == ["tar.gz", "gz"]
5272pieceExtensions :: Piece -> [Extension]
5373pieceExtensions =
5474 go . fromPiece
@@ -64,37 +84,59 @@ data MaxAge = NoMaxAge -- ^ no cache-control set
6484 | MaxAgeSeconds Int -- ^ set to the given number of seconds
6585 | MaxAgeForever -- ^ essentially infinite caching; in reality, probably one year
6686
67−data Folder = Folder -- FIXME revisit this
87+-- | Represent contents of a single folder, which can be itself either a file
88+-- or a folder.
89+data Folder = Folder
6890 { folderName :: Piece
69− , folderContents :: [Either Folder File]
91+ , folderContents :: [Either Folder File] -- FIXME replace Folder with FolderName?
7092 }
7193
94+-- | Information on an individual file.
7295data File = File
73− { fileGetSize :: Int
96+ { -- | Size of file in bytes
97+ fileGetSize :: Int
98+ -- | How to construct a WAI response for this file. Some files are stored
99+ -- on the filesystem and can use @ResponseFile@, while others are stored
100+ -- in memory and should use @ResponseBuilder@.
74101 , fileToResponse :: H.Status -> H.ResponseHeaders -> W.Response
102+ -- | Last component of the filename.
75103 , fileName :: Piece
104+ -- | Calculate a hash of the contents of this file, such as for etag.
76105 , fileGetHash :: IO (Maybe ByteString)
106+ -- | Last modified time, used for both display in listings and if-modified-since.
77107 , fileGetModified :: Maybe EpochTime
78108 }
79109
80−emptyParentFolder :: Folder
81−emptyParentFolder = Folder (Piece "") []
82−
83−type MimeType = ByteString
84−type Extension = Text
85−type MimeMap = Map.Map Extension MimeType
86−
110+-- | Result of looking up a file in some storage backend.
111+--
112+-- The lookup is either a file or folder, or does not exist.
87113data LookupResult = LRFile File
88114 | LRFolder Folder
89115 | LRNotFound
90116
117+-- | How to construct a directory listing page for the given request path and
118+-- the resulting folder.
91119type Listing = Pieces -> Folder -> IO Builder
92120
121+-- | Individual mime type for be served over the wire.
122+type MimeType = ByteString
123+
124+-- | Path extension. May include multiple components, e.g. tar.gz
125+type Extension = Text
126+
127+-- | Maps extensions to mime types.
128+type MimeMap = Map.Map Extension MimeType
129+
130+-- | All of the settings available to users for tweaking wai-app-static.
93131data StaticSettings = StaticSettings
94− { -- | Lookup a single file or folder.
132+ {
133+ -- | Lookup a single file or folder. This is how you can control storage
134+ -- backend (filesystem, embedded, etc) and where to lookup.
95135 ssLookupFile :: Pieces -> IO LookupResult
96136
97− -- | Determine the mime type of the given file.
137+ -- | Determine the mime type of the given file. Note that this function
138+ -- lives in @IO@ in case you want to perform more complicated mimetype
139+ -- analysis, such as via the @file@ utility.
98140 , ssGetMimeType :: File -> IO MimeType
99141
100142 -- | Ordered list of filenames to be used for indices. If the user
@@ -121,3 +163,6 @@ data StaticSettings = StaticSettings
121163 -- FIXME Need clarity on what exactly is going on here.
122164 , ssUseHash :: Bool
123165 }
166+
167+emptyParentFolder :: Folder -- FIXME get rid of this?
168+emptyParentFolder = Folder (Piece "") []