This is part 3 of a series where we develop a file hosting web application. We ended the previous entry with the ability to upload files, but only their names were stored. Today we are going to store file contents as well. A summary page for each file will display the contents if possible. The ability to download files from the server will be added next time. # Storing File Contents We need a place to store file contents. One way to go about doing this would be to create real files on the server's hard drive. We would recommend this approach if we were storing many files, keeping them for a long period of time, or wanted to have access to them from outside of the application. To keep things simple we'll just keep the file contents in memory along with information about them. This means that our application will reset it's data each time it runs. The application state is currently just a list of filenames. Let's extend this to an association list of file names to contents. Lazy bytestrings are the most convenient type for our use. Other types such as strict bytestrings or vectors of bytes would have worked as well. ``` active haskell web {-# START_FILE Dispatch.hs #-} -- show -- /show {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} module Dispatch where import Yesod import Foundation import Handler.Home mkYesodDispatch "App" resourcesApp {-# START_FILE Foundation.hs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -- show module Foundation where import Control.Concurrent.STM {-hi-}import Data.ByteString.Lazy (ByteString){-/hi-} import Data.Text (Text) import Yesod -- data App = App (TVar [Text]) {-hi-}data App = App (TVar [(Text, ByteString)]){-/hi-} instance Yesod App instance RenderMessage App FormMessage where renderMessage _ _ = defaultFormMessage mkYesodData "App" $(parseRoutesFile "config/routes") getList :: Handler [Text] getList = do App tstate <- getYesod -- liftIO $ readTVarIO tstate {-hi-} state <- liftIO $ readTVarIO tstate return $ map fst state{-/hi-} -- addFile :: App -> Text -> Handler () {-hi-}addFile :: App -> (Text, ByteString) -> Handler (){-/hi-} addFile (App tstore) op = liftIO . atomically $ do modifyTVar tstore $ \ ops -> op : ops -- /show {-# START_FILE Main.hs #-} -- show -- /show {-# LANGUAGE OverloadedStrings #-} module Main where import Control.Concurrent.STM import Yesod import Dispatch () import Foundation main :: IO () main = do tfilenames <- atomically $ newTVar [] warpEnv $ App tfilenames {-# START_FILE Handler/Home.hs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -- show module Handler.Home where {-hi-}import Data.Conduit import Data.Conduit.Binary{-/hi-} import Data.Default import Yesod import Yesod.Default.Util import Control.Monad.Trans.Resource (runResourceT) import Foundation getHomeR :: Handler Html getHomeR = do (formWidget, formEncType) <- generateFormPost uploadForm filenames <- getList defaultLayout $ do setTitle "File Processor" $(widgetFileNoReload def "home") postHomeR :: Handler Html postHomeR = do ((result, _), _) <- runFormPost uploadForm case result of FormSuccess fi -> do app <- getYesod -- addFile app $ fileName fi {-hi-} fileBytes <- runResourceT $ fileSource fi $$ sinkLbs addFile app (fileName fi, fileBytes){-/hi-} _ -> return () redirect HomeR uploadForm = renderDivs $ fileAFormReq "file" -- /show {-# START_FILE templates/home.hamlet #-} -- show -- /show

Previously submitted files $if null filenames

No files have been uploaded yet. $else