bustle-0.4.3: Bustle/Loader/OldSkool.hs
{-# LANGUAGE OverloadedStrings #-}
{-
Bustle.Loader.OldSkool: reads the output of bustle-dbus-monitor
Copyright © 2008–2011 Collabora Ltd.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-}
module Bustle.Loader.OldSkool
( readLog
, senderWhenDisconnected
)
where
import Bustle.Types
import qualified DBus as D
import Text.ParserCombinators.Parsec hiding (Parser)
import Data.Map (Map)
import Data.Maybe (isJust)
import qualified Data.Map as Map
import Control.Monad (ap, when, guard)
import Control.Applicative ((<$>))
infixl 4 <*
(<*) :: Monad m => m a -> m b -> m a
m <* n = do ret <- m; n; return ret
infixl 4 <*>
(<*>) :: Monad m => m (a -> b) -> m a -> m b
(<*>) = ap
type Parser a = GenParser Char (Map (TaggedBusName, Serial) (Detailed Message)) a
t :: Parser Char
t = char '\t'
nameChars :: Parser String
nameChars = many1 (noneOf "\t\n")
-- this should be
-- nameChars = many1 (oneOf "._-" <|> alphaNum)
-- but making it more tolerant lets us shoehorn misc into this field until the
-- log format is less shit.
parseUniqueName :: Parser UniqueName
parseUniqueName = do
char ':'
rest <- nameChars
case D.parseBusName (':':rest) of
Just n -> return $ UniqueName n
Nothing -> fail $ "':" ++ rest ++ "' is not a valid unique name"
<?> "unique name"
-- FIXME: this shouldn't exist.
senderWhenDisconnected :: D.BusName
senderWhenDisconnected = D.busName_ "org.freedesktop.DBus.Local"
parseMissingName :: Parser OtherName
parseMissingName = do
none
return $ OtherName senderWhenDisconnected
parseSpecifiedOtherName :: Parser OtherName
parseSpecifiedOtherName = do
x <- nameChars
case D.parseBusName x of
Just n -> return $ OtherName n
Nothing -> fail $ "'" ++ x ++ "' is not a valid name"
parseOtherName :: Parser OtherName
parseOtherName = parseMissingName <|> parseSpecifiedOtherName
<?>
"non-unique name"
parseBusName :: Parser TaggedBusName
parseBusName = (fmap U parseUniqueName) <|> (fmap O parseOtherName)
parseSerial :: Parser Serial
parseSerial = read <$> many1 digit <?> "serial"
parseTimestamp :: Parser Microseconds
parseTimestamp = do
seconds <- i
t
µs <- i
return $ µsFromPair seconds µs
where i = read <$> many1 digit <?> "timestamp"
none :: Parser (Maybe a)
none = do
string "<none>"
return Nothing
pathify :: String -> D.ObjectPath
pathify s = case D.parseObjectPath s of
Just p -> p
Nothing -> D.objectPath_ "/unparseable/object/path"
interfacify :: String -> Maybe D.InterfaceName
interfacify = D.parseInterfaceName
memberNamify :: String -> D.MemberName
memberNamify s = case D.parseMemberName s of
Just m -> m
Nothing -> D.memberName_ "UnparseableMemberName"
entireMember :: Parser Member
entireMember = do
let p = pathify <$> many1 (oneOf "/_" <|> alphaNum) <?> "path"
i = none <|> fmap interfacify (many1 (oneOf "._" <|> alphaNum)) <?> "iface"
m = memberNamify <$> many1 (oneOf "_" <|> alphaNum) <?> "membername"
Member <$> p <* t <*> i <* t <*> m
<?> "member"
addPendingCall :: Detailed Message -> Parser ()
addPendingCall dm = updateState $ Map.insert (sender m, serial m) dm
where
m = deEvent dm
findPendingCall :: TaggedBusName -> Serial -> Parser (Maybe (Detailed Message))
findPendingCall dest s = do
pending <- getState
let key = (dest, s)
ret = Map.lookup key pending
when (isJust ret) $ updateState (Map.delete key)
return ret
methodCall :: Parser DetailedEvent
methodCall = do
char 'c'
t
µs <- parseTimestamp
t
m <- MethodCall <$> parseSerial <* t
<*> parseBusName <* t <*> parseBusName <* t <*> entireMember
let dm = Detailed µs m Nothing
addPendingCall dm
return $ fmap MessageEvent dm
<?> "method call"
parseReturnOrError :: String
-> (Maybe (Detailed Message) -> TaggedBusName -> TaggedBusName -> Message)
-> Parser DetailedEvent
parseReturnOrError prefix constructor = do
string prefix <* t
ts <- parseTimestamp <* t
parseSerial <* t
replySerial <- parseSerial <* t
s <- parseBusName <* t
d <- parseBusName
call <- findPendingCall d replySerial
-- If we can see a call, use its sender and destination as the destination
-- and sender for the reply. This might prove unnecessary in the event of
-- moving the name collapsing into the UI.
let (s', d') = case call of
Just (Detailed _ m _) -> (destination m, sender m)
Nothing -> (s, d)
message = constructor call s' d'
return $ Detailed ts (MessageEvent message) Nothing
<?> "method return or error"
methodReturn, parseError :: Parser DetailedEvent
methodReturn = parseReturnOrError "r" MethodReturn <?> "method return"
parseError = parseReturnOrError "err" Error <?> "error"
signal :: Parser DetailedEvent
signal = do
string "sig"
t
µs <- parseTimestamp
t
-- Ignore serial
m <- Signal <$> (parseSerial >> t >> parseBusName) <* t
<*> return Nothing
<*> entireMember
return $ Detailed µs (MessageEvent m) Nothing
<?> "signal"
method :: Parser DetailedEvent
method = char 'm' >> (methodCall <|> methodReturn)
<?> "method call or return"
noName :: Parser ()
noName = char '!' >> return ()
<?> "the empty name '!'"
perhaps :: Parser a -> Parser (Maybe a)
perhaps act = (noName >> return Nothing) <|> fmap Just act
sameUnique :: UniqueName -> UniqueName -> Parser ()
sameUnique u u' = guard (u == u')
<?> "owner to be " ++ unUniqueName u ++ ", not " ++ unUniqueName u'
atLeastOne :: OtherName -> Parser a
atLeastOne n = fail ""
<?> unOtherName n ++ " to gain or lose an owner"
nameOwnerChanged :: Parser DetailedEvent
nameOwnerChanged = do
string "nameownerchanged"
t
ts <- parseTimestamp
t
n <- parseBusName
t
m <- parseNOCDetails n
return $ Detailed ts (NOCEvent m) Nothing
parseNOCDetails :: TaggedBusName
-> Parser NOC
parseNOCDetails n =
case n of
U u -> do
old <- perhaps parseUniqueName
case old of
Nothing -> do
t
u' <- parseUniqueName
sameUnique u u'
return $ Connected u
Just u' -> do
sameUnique u u'
t
noName
return $ Disconnected u
O o -> do
old <- perhaps parseUniqueName
t
new <- perhaps parseUniqueName
c <- case (old, new) of
(Nothing, Nothing) -> atLeastOne o
(Just a, Nothing) -> return $ Released a
(Nothing, Just b) -> return $ Claimed b
(Just a, Just b) -> return $ Stolen a b
return $ NameChanged o c
event :: Parser DetailedEvent
event = method <|> signal <|> nameOwnerChanged <|> parseError
events :: Parser [DetailedEvent]
events = sepEndBy event (char '\n') <* eof
readLog :: String -> Either ParseError [DetailedEvent]
readLog filename = runParser events Map.empty "" filename
-- vim: sw=2 sts=2