dbus-core-0.9.3: hs/DBus/Connection.hs
{-# LANGUAGE OverloadedStrings #-}
-- Copyright (C) 2009-2011 John Millikin <jmillikin@gmail.com>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module DBus.Connection
( Connection
, ConnectionError
, connect
, disconnect
, send
, receive
-- * Authentication
, Mechanism
, mechanism
, external
-- * Transports
, Transport
, Socket
, transport
, socket
, socketPut
, socketGet
, unix
, tcp
) where
import Control.Concurrent
import Control.Monad (when)
import Data.IORef
import DBus.Address
import DBus.Connection.Authentication
import DBus.Connection.Error
import DBus.Connection.Transport
import DBus.Message.Internal
import DBus.Wire
import DBus.Wire.Internal (unmarshalMessageM)
-- | A 'Connection' is an opaque handle to an open D‐Bus channel, with
-- an internal state for maintaining the current message serial.
data Connection = Connection
{ connectionAddress :: Address
, connectionSocket :: Socket
, connectionSerial :: IORef Serial
, connectionReadLock :: MVar ()
, connectionWriteLock :: MVar ()
}
-- | Open a connection to some address, using a given authentication
-- mechanism. If the connection fails, a 'ConnectionError' will be thrown.
connect :: [Transport] -> [Mechanism] -> Address -> IO Connection
connect transports mechanisms addr = do
msock <- connectTransport transports addr
sock <- case msock of
Just s -> return s
Nothing -> connectionError (concat
[ "Unknown address method: "
, show (addressMethod addr)
])
authed <- authenticate sock mechanisms
when (not authed)
(connectionError "Authentication failed")
serial <- newIORef (Serial 1)
readLock <- newMVar ()
writeLock <- newMVar ()
return (Connection addr sock serial
readLock writeLock)
-- | Close an open connection. Once closed, the 'Connection' is no longer
-- valid and must not be used.
disconnect :: Connection -> IO ()
disconnect = socketClose . connectionSocket
instance Show Connection where
showsPrec _ x =
showString "<Connection " .
shows (connectionAddress x) .
showString ">"
-- | Send a single message, with a generated 'Serial'. The second parameter
-- exists to prevent race conditions when registering a reply handler; it
-- receives the serial the message /will/ be sent with, before it’s
-- actually sent.
--
-- Only one message may be sent at a time; if multiple threads attempt to
-- send messages in parallel, one will block until after the other has
-- finished.
send :: Message msg => Connection -> msg -> (Serial -> IO a) -> IO (Either MarshalError a)
send connection msg io = do
serial <- nextSerial connection
case marshalMessage LittleEndian serial msg of
Right bytes -> do
let sock = connectionSocket connection
let lock = connectionWriteLock connection
result <- io serial
withMVar lock (\_ -> socketPut sock bytes)
return (Right result)
Left err -> return (Left err)
nextSerial :: Connection -> IO Serial
nextSerial connection = atomicModifyIORef
(connectionSerial connection)
(\serial@(Serial x) -> (Serial (x + 1), serial))
-- | Receive the next message from the connection, blocking until one is
-- available.
--
-- Only one message may be received at a time; if multiple threads attempt
-- to receive messages in parallel, one will block until after the other has
-- finished.
receive :: Connection -> IO (Either UnmarshalError ReceivedMessage)
receive connection = do
let sock = connectionSocket connection
let lock = connectionReadLock connection
withMVar lock (\_ -> unmarshalMessageM (socketGet sock))