module WASH.CGI.Transaction (
T (), init, create, remove, get, set,
with, Control (..),
TCGI ()
) where
import qualified WASH.CGI.BaseCombinators as B
import qualified WASH.CGI.CGIConfig as Conf
import WASH.CGI.CGIMonad
import WASH.CGI.CGI hiding (head, div, span, map)
import WASH.CGI.TCGI (TCGI)
import qualified WASH.CGI.TransactionUtil as TU
import WASH.CGI.TransactionUtil (Control (..))
import WASH.CGI.LogEntry
import WASH.CGI.MakeTypedName
import WASH.CGI.Types
import qualified WASH.CGI.HTMLWrapper as H hiding (map,head)
import qualified WASH.Utility.Auxiliary as Aux
import qualified WASH.Utility.Locking as L
import qualified WASH.Utility.Unique as Unique
import Directory
import IO
import qualified List
import Maybe
import Monad
import Prelude hiding (init)
transactionLock = Conf.transactionDir
newtype T a =
T String
deriving (Read, Show)
init :: (Types a, Read a, Show a) => String -> a -> TCGI b (T a)
init name val =
let v = show val
typedName = makeTypedNameFromVal name val
h = T typedName
in
wrapCGI (\ cgistate ->
case inparm cgistate of
[] ->
do Aux.assertDirectoryExists (List.init Conf.transactionDir) (return ())
let out = outparm cgistate
ev <- try (readValue out typedName)
let newparm =
case ev of
Left _ ->
PAR_CRE_TV typedName v
Right (v', cached) ->
PAR_GET_TV typedName v'
return (h, cgistate { outparm = newparm : out })
PAR_CRE_TV _ _ : rest ->
return (h, cgistate { inparm = rest })
PAR_GET_TV _ _ : rest ->
return (h, cgistate { inparm = rest })
le : rest ->
error ("Transaction.init: got log entry "
++ show le ++
". This should not happen")
)
get :: (Read a, Show a) => T a -> TCGI b a
get (T typedName) =
wrapCGI (\ cgistate ->
case inparm cgistate of
[] ->
let out = outparm cgistate in
do (v, cached) <- readValue out typedName
let newparm | cached = PAR_RESULT v
| otherwise = PAR_GET_TV typedName v
return (read v, cgistate { outparm = newparm : out })
PAR_GET_TV _ v : rest ->
return (read v, cgistate { inparm = rest })
PAR_RESULT v : rest ->
return (read v, cgistate { inparm = rest })
_ ->
error "Transaction.get: this should not happen"
)
set :: (Read a, Show a) => T a -> a -> TCGI b ()
set (T typedName) val =
let v = show val
in
wrapCGI (\ cgistate ->
case inparm cgistate of
[] ->
do let newparm = PAR_SET_TV typedName v
out = outparm cgistate
readValue out typedName
return ((), cgistate { outparm = newparm : outparm cgistate })
PAR_SET_TV _ _ : rest ->
return ((), cgistate { inparm = rest })
_ ->
error "Transaction.set: this should not happen"
)
create :: (Read a, Show a, Types a) => a -> TCGI b (T a)
create val =
let v = show val
obtainUniqueHandle =
do name <- Unique.inventStdKey
let typedName = makeTypedNameFromVal name val
h = T typedName
L.obtainLock transactionLock
conflict <- reallyExists typedName
unless conflict $ reallyWrite typedName v
L.releaseLock transactionLock
if conflict then obtainUniqueHandle else return h
in
do wrapCGI $ \ cgistate ->
case inparm cgistate of
[] ->
do Aux.assertDirectoryExists (List.init Conf.transactionDir) (return ())
h@(T typedName) <- obtainUniqueHandle
return (h, cgistate { outparm = PAR_CRE_TV typedName v : outparm cgistate })
PAR_CRE_TV typedName _ : rest ->
return (T typedName, cgistate { inparm = rest })
_ ->
error "Transaction.create: this should not happen"
remove :: (Types a) => T a -> TCGI b ()
remove (T typedName) =
wrapCGI (\ cgistate ->
case inparm cgistate of
[] ->
let out = outparm cgistate in
do readValue out typedName
return ((), cgistate { outparm = PAR_REM_TV typedName : out })
PAR_REM_TV _ : rest ->
return ((), cgistate { inparm = rest })
_ ->
error "Transaction.remove: this should not happen"
)
class CGIMonad cgi => WithMonad cgi where
with :: (Read result, Show result) =>
result
-> (result -> cgi ())
-> (TU.Control (TCGI result) result -> (TCGI result) ())
-> cgi ()
instance WithMonad CGI where
with result onResult fun =
TU.withCGI commitFromLog result onResult fun
instance WithMonad (TCGI x) where
with result onResult fun =
TU.withTCGI (const $ return True) result onResult fun
readValue :: [PARAMETER] -> String -> IO (String, Bool)
readValue [] n =
do v' <- reallyRead n
return (v', False)
readValue (PAR_SET_TV n' v':rest) n =
if n==n'
then return (v', True)
else readValue rest n
readValue (PAR_GET_TV n' v':rest) n =
if n==n'
then return (v', True)
else readValue rest n
readValue (PAR_CRE_TV n' v':rest) n =
if n==n'
then return (v', True)
else readValue rest n
readValue (PAR_REM_TV n':rest) n =
if n==n'
then fail ("Transactional variable " ++ n ++ " has vanished")
else readValue rest n
readValue (PAR_TRANS stid:rest) n =
do v' <- reallyRead n
return (v', False)
readValue (_:rest) n =
readValue rest n
data TV_DESC
= TV_DESC { tv_name :: String
, tv_old :: Maybe (Maybe String)
, tv_new :: Maybe (Maybe String)
}
deriving Show
getDescriptors :: [PARAMETER] -> [TV_DESC]
getDescriptors logEntries =
foldr f [] logEntries
where
f (PAR_GET_TV n v) r = doRead n v r
f (PAR_SET_TV n v) r = doWrite n v r
f (PAR_CRE_TV n v) r = doCreate n v r
f (PAR_REM_TV n) r = doRemove n r
f _ r = r
doCreate n v ds =
TV_DESC {tv_name = n, tv_old = Just Nothing, tv_new = Just (Just v)}
: ds
doRemove n ds =
doProcess g n ds
where
g (TV_DESC { tv_old = Just Nothing } : rest) =
rest
g (tvd : rest) =
tvd { tv_new = Just Nothing } : rest
g [] =
[TV_DESC {tv_name = n, tv_old = Nothing, tv_new = Just Nothing}]
doProcess g n ds =
f ds
where
f [] =
g []
f ds@(d':ds') =
if tv_name d' == n then g ds else d' : f ds'
doRead n v ds =
doProcess h n ds
where
h [] =
[TV_DESC {tv_name = n, tv_old = Just (Just v), tv_new = Nothing }]
h (tvd : rest) =
tvd : rest
doWrite n v ds =
doProcess h n ds
where
h [] =
[TV_DESC {tv_name = n, tv_old = Nothing, tv_new = Just (Just v) }]
h (tvd : rest) =
tvd { tv_new = Just (Just v) } : rest
commitFromLog :: [PARAMETER] -> IO Bool
commitFromLog =
tryToCommit . getDescriptors
tryToCommit :: [TV_DESC] -> IO Bool
tryToCommit ds =
if checkOnlyReads ds then return True else
do L.obtainLock transactionLock
oldValuesPreserved <- checkOldValuesPreserved ds
when oldValuesPreserved (writeNewValues ds)
L.releaseLock transactionLock
return oldValuesPreserved
checkOldValuesPreserved :: [TV_DESC] -> IO Bool
checkOldValuesPreserved [] =
return True
checkOldValuesPreserved (d:ds) =
do b <- checkOldValuePreserved d
if b then checkOldValuesPreserved ds
else return False
checkOldValuePreserved :: TV_DESC -> IO Bool
checkOldValuePreserved d =
do
let n = tv_name d
varExists <- reallyExists n
case tv_old d of
Nothing ->
return varExists
Just Nothing ->
return (not varExists)
Just (Just ov) ->
if varExists
then
do cv <- reallyRead n
return (cv == ov)
else
return False
writeNewValues :: [TV_DESC] -> IO ()
writeNewValues ds =
mapM_ g ds
where
g d =
let n = tv_name d in
case tv_new d of
Nothing ->
return ()
Just Nothing ->
reallyRemove n
Just (Just nv) ->
reallyWrite n nv
checkOnlyReads :: [TV_DESC] -> Bool
checkOnlyReads ds =
and (map wasNotWritten ds)
where
wasNotWritten d =
isNothing (tv_new d)
reallyRead :: String -> IO String
reallyRead n =
let fileName = Conf.transactionDir ++ n in
Aux.readFileStrictly fileName
reallyWrite :: String -> String -> IO ()
reallyWrite n v =
let fileName = Conf.transactionDir ++ n in
writeFile fileName v
reallyExists :: String -> IO Bool
reallyExists n =
let fileName = Conf.transactionDir ++ n in
doesFileExist fileName
reallyRemove :: String -> IO ()
reallyRemove n =
let fileName = Conf.transactionDir ++ n in
removeFile fileName