New serversession-backend-acid-state package.
This commit is contained in:
parent
c44e5c6103
commit
bac54a5c70
20
serversession-backend-acid-state/LICENSE
Normal file
20
serversession-backend-acid-state/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015 Felipe Lessa
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
6
serversession-backend-acid-state/README.md
Normal file
6
serversession-backend-acid-state/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# serversession-backend-acid-state
|
||||
|
||||
This is the storage backend for `serversession` using
|
||||
`acid-state`. Please
|
||||
[read the main README file](https://github.com/yesodweb/serversession/blob/master/README.md)
|
||||
for general information about the serversession packages.
|
||||
@ -0,0 +1,37 @@
|
||||
name: serversession-backend-acid-state
|
||||
version: 1.0
|
||||
license: MIT
|
||||
license-file: LICENSE
|
||||
author: Felipe Lessa <felipe.lessa@gmail.com>
|
||||
maintainer: Felipe Lessa <felipe.lessa@gmail.com>
|
||||
synopsis: Storage backend for serversession using acid-state.
|
||||
category: Web
|
||||
stability: Stable
|
||||
cabal-version: >= 1.8
|
||||
build-type: Simple
|
||||
homepage: https://github.com/yesodweb/serversession
|
||||
description: API docs and the README are available at <http://www.stackage.org/package/serversession-backend-acid-state-sql>
|
||||
extra-source-files: README.md
|
||||
|
||||
library
|
||||
hs-source-dirs: src
|
||||
build-depends:
|
||||
base == 4.*
|
||||
, acid-state == 0.12.*
|
||||
, containers
|
||||
, mtl
|
||||
, safecopy == 0.8.*
|
||||
|
||||
, serversession == 1.0.*
|
||||
exposed-modules:
|
||||
Web.ServerSession.Backend.Acid
|
||||
Web.ServerSession.Backend.Acid.Internal
|
||||
extensions:
|
||||
DeriveDataTypeable
|
||||
TemplateHaskell
|
||||
TypeFamilies
|
||||
ghc-options: -Wall
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
location: https://github.com/yesodweb/serversession
|
||||
@ -0,0 +1,20 @@
|
||||
-- | Storage backend for @serversession@ using @acid-state@.
|
||||
--
|
||||
-- In order to use this backend, just open the 'AcidState'. For
|
||||
-- example:
|
||||
--
|
||||
-- @
|
||||
-- import Data.Acid
|
||||
-- import Web.ServerSession.Backend.Acid
|
||||
--
|
||||
-- makeSessionStorage :: IO 'AcidStorage'
|
||||
-- makeSessionStorage =
|
||||
-- 'AcidStorage' \<$\> openLocalState 'emptyState'
|
||||
-- @
|
||||
module Web.ServerSession.Backend.Acid
|
||||
( AcidStorage(..)
|
||||
, emptyState
|
||||
, ServerSessionAcidState
|
||||
) where
|
||||
|
||||
import Web.ServerSession.Backend.Acid.Internal
|
||||
@ -0,0 +1,165 @@
|
||||
-- | Internal module exposing the guts of the package. Use at
|
||||
-- your own risk. No API stability guarantees apply.
|
||||
module Web.ServerSession.Backend.Acid.Internal
|
||||
( SessionIdToSession
|
||||
, AuthIdToSessionId
|
||||
, ServerSessionAcidState(..)
|
||||
, emptyState
|
||||
, removeSessionFromAuthId
|
||||
, nothingfy
|
||||
|
||||
, getSession
|
||||
, deleteSession
|
||||
, deleteAllSessionsOfAuthId
|
||||
, insertSession
|
||||
, replaceSession
|
||||
|
||||
, GetSession
|
||||
, DeleteSession
|
||||
, DeleteAllSessionsOfAuthId
|
||||
, InsertSession
|
||||
, ReplaceSession
|
||||
|
||||
, AcidStorage(..)
|
||||
) where
|
||||
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.Reader (ask)
|
||||
import Control.Monad.State (get, modify')
|
||||
import Data.Acid (AcidState, Query, Update, makeAcidic, query, update)
|
||||
import Data.SafeCopy (deriveSafeCopy, base)
|
||||
import Data.Typeable (Typeable)
|
||||
|
||||
import qualified Data.Map.Strict as M
|
||||
import qualified Data.Set as S
|
||||
import qualified Web.ServerSession.Core as SS
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
||||
-- | Map from session IDs to sessions. The most important map,
|
||||
-- allowing us efficient access to a session given its ID.
|
||||
type SessionIdToSession = M.Map SS.SessionId SS.Session
|
||||
|
||||
|
||||
-- | Map from auth IDs to session IDs. Allow us to invalidate
|
||||
-- all sessions of given user without having to iterate through
|
||||
-- the whole 'SessionIdToSession' map.
|
||||
type AuthIdToSessionId = M.Map SS.AuthId (S.Set SS.SessionId)
|
||||
|
||||
|
||||
-- | The current sessions.
|
||||
--
|
||||
-- Besides the obvious map from session IDs to sessions, we also
|
||||
-- maintain a map of auth IDs to session IDs. This allow us to
|
||||
-- quickly invalidate all sessions of a given user.
|
||||
data ServerSessionAcidState =
|
||||
ServerSessionAcidState
|
||||
{ sessionIdToSession :: !SessionIdToSession
|
||||
, authIdToSessionId :: !AuthIdToSessionId
|
||||
} deriving (Show, Typeable)
|
||||
|
||||
deriveSafeCopy 0 'base ''SS.SessionId -- dangerous!
|
||||
deriveSafeCopy 0 'base ''SS.Session -- dangerous!
|
||||
deriveSafeCopy 0 'base ''ServerSessionAcidState
|
||||
|
||||
|
||||
-- | Empty 'ServerSessionAcidState' used to bootstrap the 'AcidState'.
|
||||
emptyState :: ServerSessionAcidState
|
||||
emptyState = ServerSessionAcidState M.empty M.empty
|
||||
|
||||
|
||||
-- | Remove the given 'SessionId' from the set of the given
|
||||
-- 'AuthId' on the map. Does not do anything if no 'AuthId' is
|
||||
-- provided.
|
||||
removeSessionFromAuthId :: SS.SessionId -> Maybe SS.AuthId -> AuthIdToSessionId -> AuthIdToSessionId
|
||||
removeSessionFromAuthId sid = maybe id (M.update (nothingfy . S.delete sid))
|
||||
|
||||
|
||||
-- | Change a 'S.Set' to 'Nothing' if it's 'S.null'.
|
||||
nothingfy :: S.Set a -> Maybe (S.Set a)
|
||||
nothingfy s = if S.null s then Nothing else Just s
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
||||
-- | Get the session for the given session ID.
|
||||
getSession :: SS.SessionId -> Query ServerSessionAcidState (Maybe SS.Session)
|
||||
getSession sid = M.lookup sid . sessionIdToSession <$> ask
|
||||
|
||||
|
||||
-- | Delete the session with given session ID.
|
||||
deleteSession :: SS.SessionId -> Update ServerSessionAcidState ()
|
||||
deleteSession sid = do
|
||||
let removeSession = M.updateLookupWithKey (\_ _ -> Nothing) sid
|
||||
modify' $ \state ->
|
||||
let (oldSession, newSessionIdToSession) = removeSession $ sessionIdToSession state
|
||||
newAuthIdToSessionId = removeSessionFromAuthId sid mauthId $ authIdToSessionId state
|
||||
where mauthId = oldSession >>= SS.sessionAuthId
|
||||
in ServerSessionAcidState newSessionIdToSession newAuthIdToSessionId
|
||||
|
||||
|
||||
-- | Delete all sessions of the given auth ID.
|
||||
deleteAllSessionsOfAuthId :: SS.AuthId -> Update ServerSessionAcidState ()
|
||||
deleteAllSessionsOfAuthId authId = do
|
||||
let removeSession = maybe id (flip M.difference . M.fromSet (const ()))
|
||||
removeAuth = M.updateLookupWithKey (\_ _ -> Nothing) authId
|
||||
modify' $ \state ->
|
||||
let (sessionIds, newAuthIdToSessionId) = removeAuth $ authIdToSessionId state
|
||||
newSessionIdToSession = removeSession sessionIds $ sessionIdToSession state
|
||||
in ServerSessionAcidState newSessionIdToSession newAuthIdToSessionId
|
||||
|
||||
|
||||
-- | Insert a new session.
|
||||
insertSession :: SS.Session -> Update ServerSessionAcidState ()
|
||||
insertSession session = do
|
||||
let insertSess = M.insert sid session
|
||||
insertAuth = maybe id (flip (M.insertWith S.union) (S.singleton sid)) (SS.sessionAuthId session)
|
||||
sid = SS.sessionKey session
|
||||
modify' $ \state ->
|
||||
ServerSessionAcidState
|
||||
(insertSess $ sessionIdToSession state)
|
||||
(insertAuth $ authIdToSessionId state)
|
||||
|
||||
|
||||
-- | Replace the contents of a session.
|
||||
replaceSession :: SS.Session -> Update ServerSessionAcidState ()
|
||||
replaceSession session = do
|
||||
-- Remove the old auth ID from the map if it has changed.
|
||||
let sid = SS.sessionKey session
|
||||
oldSession <- M.lookup sid . sessionIdToSession <$> get
|
||||
let oldAuthId = SS.sessionAuthId =<< oldSession
|
||||
when (oldAuthId /= SS.sessionAuthId session) $
|
||||
modify' $ \state -> state
|
||||
{ authIdToSessionId = removeSessionFromAuthId sid oldAuthId $ authIdToSessionId state
|
||||
}
|
||||
-- Otherwise the operation is the same as inserting.
|
||||
insertSession session
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
||||
makeAcidic ''ServerSessionAcidState ['getSession, 'deleteSession, 'deleteAllSessionsOfAuthId, 'insertSession, 'replaceSession]
|
||||
|
||||
|
||||
-- | Session storage backend using @acid-state@.
|
||||
newtype AcidStorage =
|
||||
AcidStorage
|
||||
{ acidState :: AcidState ServerSessionAcidState
|
||||
-- ^ Open 'AcidState' of server sessions.
|
||||
} deriving (Typeable)
|
||||
|
||||
|
||||
-- | We do not provide any ACID guarantees for different actions
|
||||
-- running inside the same @TransactionM AcidStorage@.
|
||||
instance SS.Storage AcidStorage where
|
||||
type TransactionM AcidStorage = IO
|
||||
runTransactionM = const id
|
||||
getSession (AcidStorage s) = query s . GetSession
|
||||
deleteSession (AcidStorage s) = update s . DeleteSession
|
||||
deleteAllSessionsOfAuthId (AcidStorage s) = update s . DeleteAllSessionsOfAuthId
|
||||
insertSession (AcidStorage s) = update s . InsertSession
|
||||
replaceSession (AcidStorage s) = update s . ReplaceSession
|
||||
Loading…
Reference in New Issue
Block a user