Ultimately, the dbus
package only allows you to export
methods of type Method
, which has a methodHandler
field that returns the monadic value:
DBusR Reply === ReaderT Client IO Reply
and there's no room in there for you to squeeze in your own StateT
monad. You could export a Property
instead, but that doesn't help you, since the fields of that type also involve IO
actions to get and set the property.
So, maintaining your state in IO
, most likely as an MVar
, is going to be pretty much unavoidable.
You could try to separate your pure-ish "core" from the IO shell. One way to do it (as per @HTNW's comment) is to write the core in State
:
type Counter = Int
update :: State Counter ()
update = modify (+1)
count :: State Counter Int
count = get
and lift it to IO
with something like:
import Data.Tuple (swap)
runStateIO :: State s a -> MVar s -> IO a
runStateIO act s = modifyMVar s (return . swap . runState act)
main = do
...
s <- newMVar 0
let run act = runStateIO act s
export dbus "/com/example/CounterService"
defaultInterface
{ interfaceName = "com.example.CounterService"
, interfaceMethods =
[ autoMethod "update" (run update)
, autoMethod "count" (run count) ]
}
(I think I'm using a newer version of dbus
here than you, since the API is a little different -- I'm testing with dbus-1.2.16
, FYI.)
One potential drawback is that this is going to lock the state MVar
on every method call, even if the call doesn't need the state or needs only read-only access. DBus services are typically pretty low-traffic with method calls that are intended to complete quickly, so I don't think this is a problem in practice.
Anyway, a here's a full working program, which I tested with:
dbus-send --print-reply --session --dest=com.example /com/example/CounterService com.example.CounterService.update
dbus-send --print-reply --session --dest=com.example /com/example/CounterService com.example.CounterService.count
The program:
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}
import System.IO
import System.Exit
import Data.Int
import DBus.Client
import Data.Tuple
import Control.Concurrent
import Control.Monad.State
type Counter = Int32
update :: State Counter ()
update = modify (+1)
count :: State Counter Int32
count = get
runStateIO :: State s a -> MVar s -> IO a
runStateIO act s = modifyMVar s (return . swap . runState act)
main :: IO ()
main = do
dbus <- connectSession
requestResult <- requestName dbus "com.example" []
when (requestResult /= NamePrimaryOwner) $ do
hPutStrLn stderr "Name "com.example" not available"
exitFailure
s <- newMVar 0
let run act = runStateIO act s
export dbus "/com/example/CounterService"
defaultInterface
{ interfaceName = "com.example.CounterService"
, interfaceMethods =
[ autoMethod "update" (run update)
, autoMethod "count" (run count) ]
}
forever $ threadDelay 60000000