aboutsummaryrefslogtreecommitdiff
path: root/Utility/DBus.hs
blob: 5b04703013173d72eaded1773e3102797b75f9bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{- DBus utilities
 -
 - Copyright 2012 Joey Hess <id@joeyh.name>
 -
 - License: BSD-2-clause
 -}

{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}

module Utility.DBus where

import Utility.PartialPrelude
import Utility.Exception

import DBus.Client
import DBus
import Data.Maybe
import Control.Concurrent
import Control.Exception as E

type ServiceName = String

listServiceNames :: Client -> IO [ServiceName]
listServiceNames client = do
	reply <- callDBus client "ListNames" []
	return $ fromMaybe [] $ fromVariant =<< headMaybe (methodReturnBody reply)

callDBus :: Client -> MemberName -> [Variant] -> IO MethodReturn
callDBus client name params = call_ client $
	(methodCall "/org/freedesktop/DBus" "org.freedesktop.DBus" name)
		{ methodCallDestination = Just "org.freedesktop.DBus"
		, methodCallBody = params
		}

{- Connects to the bus, and runs the client action.
 - 
 - Throws a ClientError, and closes the connection if it fails to
 - process an incoming message, or if the connection is lost.
 - Unlike DBus's usual interface, this error is thrown at the top level,
 - rather than inside the clientThreadRunner, so it can be caught, and
 - runClient re-run as needed. -}
runClient :: IO (Maybe Address) -> (Client -> IO ()) -> IO ()
runClient getaddr clientaction = do
	env <- getaddr
	case env of
		Nothing -> throwIO (clientError "runClient: unable to determine DBUS address")
		Just addr -> do
			{- The clientaction will set up listeners, which
			 - run in a different thread. We block while
			 - they're running, until our threadrunner catches
			 - a ClientError, which it will put into the MVar
			 - to be rethrown here. -}
			mv <- newEmptyMVar
			let tr = threadrunner (putMVar mv)
			let opts = defaultClientOptions { clientThreadRunner = tr }
			client <- connectWith opts addr
			clientaction client
			e <- takeMVar mv
			disconnect client
			throw e
  where
	threadrunner storeerr io = loop
	  where
		loop = catchClientError (io >> loop) storeerr

{- Connects to the bus, and runs the client action.
 -
 - If the connection is lost, runs onretry, which can do something like
 - a delay, or printing a warning, and has a state value (useful for
 - exponential backoff). Once onretry returns, the connection is retried.
 -}
persistentClient :: IO (Maybe Address) -> v -> (SomeException -> v -> IO v) -> (Client -> IO ()) -> IO ()
persistentClient getaddr v onretry clientaction =
	{- runClient can fail with not just ClientError, but also other
	 - things, if dbus is not running. Let async exceptions through. -}
	runClient getaddr clientaction `catchNonAsync` retry
  where
	retry e = do
		v' <- onretry e v
		persistentClient getaddr v' onretry clientaction

{- Catches only ClientError -}
catchClientError :: IO () -> (ClientError -> IO ()) -> IO ()
catchClientError io handler =
	either handler return =<< (E.try io :: IO (Either ClientError ()))