{-# 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 (when, guard) import Control.Applicative ((<$>), (<*>), (<*)) 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 "" 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