Skip to content

Commit 3d090f7

Browse files
committed
Merge branch 'signals'
2 parents 8588e29 + cb895ac commit 3d090f7

File tree

3 files changed

+119
-71
lines changed

3 files changed

+119
-71
lines changed

multiblog.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,21 @@ Executable multiblog
2525
happstack >= 7.0,
2626
happstack-server >= 7.3,
2727
mtl >= 2.1 && < 2.3,
28+
network >= 2.6,
2829
pandoc >= 1.13,
2930
pandoc-types >= 1.12,
3031
shakespeare >= 2.0,
3132
split >= 0.2,
33+
system-argv0 >= 0.1,
34+
system-filepath >= 0.4,
3235
text >= 1.1,
3336
time >= 1.4 && < 1.6,
3437
web-routes >= 0.27,
3538
web-routes-boomerang >= 0.26,
3639
web-routes-happstack >= 0.23,
3740
web-routes-th >= 0.22,
3841
utf8-string >= 0.3,
42+
unix >= 2.7,
3943
yaml >= 0.8,
4044
base >= 4.4 && < 5
4145

src/App.hs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1+
{-# LANGUAGE OverloadedStrings #-}
12
module App where
23

34
import Control.Monad.State
45

6+
import qualified Data.ByteString.Char8 as B
7+
import qualified Data.Text as T
8+
import Data.Time
9+
10+
import Happstack.Server
11+
12+
import Web.Routes
13+
import Web.Routes.Boomerang
14+
import Web.Routes.Happstack
15+
516
import Import
17+
import Language
618
import Models
19+
import Routes
20+
import Utils
21+
import Views
722

823

924
type App = StateT AppState IO
1025

26+
type AppPart a = RouteT Sitemap (ServerPartT App) a
27+
1128
runApp :: App a -> IO a
1229
runApp a = do
1330
loaded <- loadFromDirectory "content"
@@ -16,3 +33,57 @@ runApp a = do
1633
Right appState -> do
1734
print appState
1835
evalStateT a appState
36+
37+
site :: Site Sitemap (ServerPartT App Response)
38+
site = boomerangSiteRouteT handler sitemap
39+
40+
siteHandler :: String -> ServerPartT App Response
41+
siteHandler address = implSite (T.pack address) "" site
42+
43+
handler :: Sitemap -> AppPart Response
44+
handler route = case route of
45+
Index -> index
46+
Yearly y -> yearlyIndex y
47+
Monthly y m -> monthlyIndex y m
48+
Daily d -> dailyIndex d
49+
ArticleView d s -> article d s
50+
MetaView s -> meta s
51+
52+
index :: AppPart Response
53+
index = articleList $ const True
54+
55+
yearlyIndex :: Integer -> AppPart Response
56+
yearlyIndex = articleList . byYear
57+
58+
monthlyIndex :: Integer -> Int -> AppPart Response
59+
monthlyIndex year month = articleList $ byYearMonth year month
60+
61+
dailyIndex :: Day -> AppPart Response
62+
dailyIndex date = articleList $ byDate date
63+
64+
languageHeaderM :: AppPart LanguagePreference
65+
languageHeaderM = do
66+
request <- askRq
67+
let header = getHeader "Accept-Language" request
68+
return $ languageHeader $ liftM B.unpack header
69+
70+
html :: ToMessage a => a -> AppPart Response
71+
html = ok . toResponse
72+
73+
article :: Day -> String -> AppPart Response
74+
article date slug = do
75+
language <- languageHeaderM
76+
a <- onlyOne $ lift $ getFiltered $ byDateSlug date slug
77+
articleDisplay language a >>= html
78+
79+
articleList :: (Article -> Bool) -> AppPart Response
80+
articleList articleFilter = do
81+
articles <- lift $ getFiltered articleFilter
82+
language <- languageHeaderM
83+
articleListDisplay language articles >>= html
84+
85+
meta :: String -> AppPart Response
86+
meta slug = do
87+
language <- languageHeaderM
88+
m <- getMeta slug
89+
metaDisplay language m >>= html

src/Main.hs

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,65 @@
1-
{-# LANGUAGE OverloadedStrings #-}
2-
{-# LANGUAGE TypeOperators #-}
1+
{-# LANGUAGE DeriveDataTypeable #-}
2+
{-# LANGUAGE TypeOperators #-}
33
module Main where
44

5+
import Control.Concurrent
6+
import Control.Exception
57
import Control.Monad
6-
import Control.Monad.State
78

8-
import qualified Data.ByteString.Char8 as B
99
import Data.Maybe
10-
import qualified Data.Text as T
11-
import Data.Time
10+
import Data.Typeable
11+
12+
import Filesystem.Path.CurrentOS
1213

1314
import Happstack.Server
1415

15-
import System.Environment
16+
import Network.Socket
1617

17-
import Web.Routes
18-
import Web.Routes.Boomerang
19-
import Web.Routes.Happstack
18+
import System.Argv0
19+
import System.Environment
20+
import System.Posix.Process
21+
import System.Posix.Signals
2022

2123
import App
22-
import Language
23-
import Models
24-
import Routes
25-
import Utils
26-
import Views
2724

28-
type AppPart a = RouteT Sitemap (ServerPartT App) a
25+
data Reload = Reload
26+
deriving (Show, Typeable)
2927

30-
handler :: Sitemap -> AppPart Response
31-
handler route = case route of
32-
Index -> index
33-
Yearly y -> yearlyIndex y
34-
Monthly y m -> monthlyIndex y m
35-
Daily d -> dailyIndex d
36-
ArticleView d s -> article d s
37-
MetaView s -> meta s
28+
instance Exception Reload
3829

39-
site :: Site Sitemap (ServerPartT App Response)
40-
site = boomerangSiteRouteT handler sitemap
30+
-- Run the site, handling SIGHUP
31+
main :: IO ()
32+
main = do
33+
mainThread <- myThreadId
34+
_ <- installHandler lostConnection
35+
(CatchOnce $ reloadExecutable mainThread) Nothing
36+
runSite
37+
38+
-- Replace the process with a (possibly updated) executable
39+
-- Throw a "Reload" exception to the main thread so it releases
40+
-- its socket first
41+
reloadExecutable :: ThreadId -> IO ()
42+
reloadExecutable mainThread = do
43+
throwTo mainThread Reload
44+
ownPath <- liftM encodeString getArgv0
45+
executeFile ownPath False [] Nothing
4146

4247
siteAddress :: IO String
4348
siteAddress = do
4449
addr <- lookupEnv "SITE_URL"
4550
return $ fromMaybe "http://localhost:8000" addr
4651

47-
main :: IO ()
48-
main = do
49-
address <- siteAddress
50-
listenPort <- lookupEnv "LISTEN_PORT"
51-
let conf = nullConf { port = read $ fromMaybe "8000" listenPort }
52-
simpleHTTP' runApp conf $ implSite (T.pack address) "" site
53-
54-
index :: AppPart Response
55-
index = articleList $ const True
52+
listenPort :: IO Int
53+
listenPort = liftM (read . fromMaybe "8000") (lookupEnv "LISTEN_PORT")
5654

57-
yearlyIndex :: Integer -> AppPart Response
58-
yearlyIndex = articleList . byYear
59-
60-
monthlyIndex :: Integer -> Int -> AppPart Response
61-
monthlyIndex year month = articleList $ byYearMonth year month
62-
63-
dailyIndex :: Day -> AppPart Response
64-
dailyIndex date = articleList $ byDate date
65-
66-
languageHeaderM :: AppPart LanguagePreference
67-
languageHeaderM = do
68-
request <- askRq
69-
let header = getHeader "Accept-Language" request
70-
return $ languageHeader $ liftM B.unpack header
71-
72-
html :: ToMessage a => a -> AppPart Response
73-
html = ok . toResponse
74-
75-
article :: Day -> String -> AppPart Response
76-
article date slug = do
77-
language <- languageHeaderM
78-
-- TODO: onlyOne
79-
a <- onlyOne $ lift $ getFiltered $ byDateSlug date slug
80-
articleDisplay language a >>= html
81-
82-
articleList :: (Article -> Bool) -> AppPart Response
83-
articleList articleFilter = do
84-
articles <- lift $ getFiltered articleFilter
85-
language <- languageHeaderM
86-
articleListDisplay language articles >>= html
87-
88-
meta :: String -> AppPart Response
89-
meta slug = do
90-
language <- languageHeaderM
91-
m <- getMeta slug
92-
metaDisplay language m >>= html
55+
-- Run the actual site
56+
runSite :: IO ()
57+
runSite = do
58+
address <- siteAddress
59+
lport <- listenPort
60+
let conf = nullConf { port = lport }
61+
-- Manually bind the socket to close it on exception
62+
bracket
63+
(bindPort conf)
64+
close
65+
(\sock -> simpleHTTPWithSocket' runApp sock conf $ siteHandler address)

0 commit comments

Comments
 (0)