diff --git a/app/Main.hs b/app/Main.hs index 37b341e..0b2ccb7 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -2,15 +2,14 @@ module Main ( main - ) -where + ) where import Control.Concurrent ( MVar , newMVar , threadDelay , tryPutMVar ) -import qualified Control.Concurrent.Async as Async +import qualified Control.Concurrent.Async as Async import Control.Monad ( forever ) import Control.Monad.Log ( defaultBatchingOptions , renderWithTimestamp @@ -20,6 +19,7 @@ import Control.Monad.Log ( defaultBatchingOptio import Control.Monad.Reader ( runReaderT ) import Control.Monad.Trans ( liftIO ) import Data.FileEmbed ( embedDir ) +import Data.Foldable ( traverse_ ) import Data.IORef ( IORef , readIORef ) @@ -27,8 +27,7 @@ import Data.Time.Format ( defaultTimeLocale , formatTime , iso8601DateFormat ) -import Lens.Micro.Platform ( (<&>) - , set +import Lens.Micro.Platform ( set , view ) import Network.HTTP.Client.TLS ( newTlsManager ) @@ -56,63 +55,63 @@ import View ( render ) opts :: [OptDescr (Config -> Config)] opts = - [ Option [] ["help"] (NoArg (set cHelp True)) "Show usage info" - , Option [] ["port"] (ReqArg (set cPort . read) "PORT") "Port to run on" + [ Option [] ["help"] (NoArg (set cHelp True)) "Show usage info" + , Option [] ["port"] (ReqArg (set cPort . read) "PORT") "Port to run on" , Option [] ["interval"] (ReqArg (set cInterval . (1000000 *) . read) "INTERVAL (s)") "Update interval" ] +usage :: IO () +usage = putStrLn $ usageInfo "mat-chalmers [OPTION...]" opts + main :: IO () -main = - getArgs - <&> getOpt Permute opts - >>= \case - (_ , _ , _ : _) -> usage - (_ , _ : _, _ ) -> usage - (!confs, _ , _ ) -> do - let config = foldl (flip id) defaultConfig confs - if view cHelp config - then usage - else do - upd <- newMVar () -- putMVar when to update - mgr <- newTlsManager - (viewRef, refreshAction) <- runLoggingT - (runReaderT refresh (ClientContext config mgr)) - print +main = (reifyConfig . getOpt Permute opts <$> getArgs) >>= \case + (_ , _ , _ : _) -> usage + (_ , _ : _, _ ) -> usage + (Config { _cHelp = True }, _ , _ ) -> usage + (config , _ , _ ) -> do + upd <- newMVar () -- putMVar when to update + mgr <- newTlsManager + (viewRef, refreshAction) <- runLoggingT + (runReaderT refresh (ClientContext config mgr)) + print + + -- In the list there are three items running concurrently: + -- 1. Timer that sends a signal to the updater when it's time to update + -- 2. Webserver that serves the menus to the user + -- 3. Updater that fetches new data from the restaurants + Async.runConcurrently $ traverse_ + Async.Concurrently + [ timer upd config + , webserver config viewRef upd + , updater mgr upd refreshAction config + ] + where + timer upd cfg = + forever $ tryPutMVar upd () >> threadDelay (view cInterval cfg) - Async.concurrently_ - (Async.concurrently_ - -- timer - (forever $ tryPutMVar upd () >> threadDelay (view cInterval config)) - -- webserver - (serve config viewRef upd)) - -- updater - (forever - $ withFDHandler defaultBatchingOptions stdout 1.0 80 - $ \logCallback -> - runReaderT (refreshAction upd) (ClientContext config mgr) - `runLoggingT` ( logCallback - . renderWithTimestamp - (formatTime - defaultTimeLocale - (iso8601DateFormat - (Just "%H:%M:%S") - ) - ) - id - )) - where usage = putStrLn $ usageInfo "mat-chalmers [OPTION...]" opts + updater mgr upd refreshAction cfg = + forever + $ withFDHandler defaultBatchingOptions stdout 1.0 80 + $ \logCallback -> runLoggingT + (runReaderT (refreshAction upd) (ClientContext cfg mgr)) + ( logCallback + . renderWithTimestamp + (formatTime defaultTimeLocale + (iso8601DateFormat (Just "%H:%M:%S")) + ) + id + ) -serve +webserver :: Config -> IORef View -- ^ View model -> MVar () -- ^ Update signal -> IO () -serve conf viewRef upd = scotty (view cPort conf) $ do +webserver conf viewRef upd = scotty (view cPort conf) $ do middleware logStdout middleware (static $(embedDir "static")) get "/" ((html . render) =<< liftIO (readIORef viewRef)) get "/r" (liftIO (tryPutMVar upd ()) >> redirect "/") -- force update - diff --git a/src/Config.hs b/src/Config.hs index 3e548cd..4822482 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -4,6 +4,7 @@ module Config where +import Data.Foldable ( foldl' ) import Lens.Micro.Platform -- | Configuration record @@ -12,9 +13,22 @@ data Config = Config , _cNextDayHour :: !Int , _cInterval :: !Int , _cPort :: !Int - } deriving (Eq, Show) + } + deriving (Eq, Show) makeLenses ''Config defaultConfig :: Config defaultConfig = Config False 14 (1000000 * 60 * 30) 5007 + +-- | Create a Config we can touch +-- +-- Yes, this function implementation is overly cute. It uses a lens to update +-- the first field of the tuple by using defaultConfig as first argument to +-- every function in the list. The brain needs to turn inside out a couple of +-- times before it makes sense, so if you're in a hurry, look at the types. +-- +-- TODO: Feel free to bikeshed the function name. +reifyConfig + :: ([Config -> Config], [String], [String]) -> (Config, [String], [String]) +reifyConfig = (& _1 %~ foldl' (flip id) defaultConfig)