diff options
author | Joey Hess <joey@kitenet.net> | 2012-11-05 15:40:56 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2012-11-05 15:40:56 -0400 |
commit | ffeb353813754e431ed3a6ae80c3d4422792db78 (patch) | |
tree | 8cd9a2044b0493a4e5c5f3648d50e518513190c0 /Assistant/XMPP.hs | |
parent | f3fe98b12f533ccdf34e07b7209cb6a4b329b300 (diff) |
switch to silent chat messages for XMPP pairing
Along the way, significantly cleaned up Assistant.XMPP, and made XMPP
message decoding more efficient.
Diffstat (limited to 'Assistant/XMPP.hs')
-rw-r--r-- | Assistant/XMPP.hs | 202 |
1 files changed, 89 insertions, 113 deletions
diff --git a/Assistant/XMPP.hs b/Assistant/XMPP.hs index e3013a92f..37cf00374 100644 --- a/Assistant/XMPP.hs +++ b/Assistant/XMPP.hs @@ -12,157 +12,133 @@ import Assistant.Types.NetMessager import Assistant.Pairing import Network.Protocol.XMPP +import Data.Text (Text) import qualified Data.Text as T import Data.XML.Types -{- A presence with a git-annex tag in it. -} -gitAnnexPresence :: Element -> Presence -gitAnnexPresence tag = (emptyPresence PresenceAvailable) - { presencePayloads = [extendedAway, tag] } - where - extendedAway = Element (Name (T.pack "show") Nothing Nothing) [] - [NodeContent $ ContentText $ T.pack "xa"] - -{- Does a presence contain a git-annex tag? -} -isGitAnnexPresence :: Presence -> Bool -isGitAnnexPresence p = any isGitAnnexTag (presencePayloads p) - -{- Name of a git-annex tag, in our own XML namespace. +{- Name of the git-annex tag, in our own XML namespace. - (Not using a namespace URL to avoid unnecessary bloat.) -} gitAnnexTagName :: Name gitAnnexTagName = Name (T.pack "git-annex") (Just $ T.pack "git-annex") Nothing +{- Creates a git-annex tag containing a particular attribute and value. -} +gitAnnexTag :: Name -> Text -> Element +gitAnnexTag attr val = Element gitAnnexTagName [(attr, [ContentText val])] [] + isGitAnnexTag :: Element -> Bool isGitAnnexTag t = elementName t == gitAnnexTagName -{- A git-annex tag, to let other clients know we're a git-annex client too. -} -gitAnnexSignature :: Element -gitAnnexSignature = Element gitAnnexTagName [] [] +{- Things that a git-annex tag can inserted into. -} +class GitAnnexTaggable a where + insertGitAnnexTag :: a -> Element -> a -queryAttr :: Name -queryAttr = Name (T.pack "query") Nothing Nothing + extractGitAnnexTag :: a -> Maybe Element -pushAttr :: Name -pushAttr = Name (T.pack "push") Nothing Nothing + hasGitAnnexTag :: a -> Bool + hasGitAnnexTag = isJust . extractGitAnnexTag -pairAttr :: Name -pairAttr = Name (T.pack "pair") Nothing Nothing +instance GitAnnexTaggable Message where + insertGitAnnexTag m e = m { messagePayloads = e : messagePayloads m } + extractGitAnnexTag = headMaybe . filter isGitAnnexTag . messagePayloads -isAttr :: Name -> (Name, [Content]) -> Bool -isAttr attr (k, _) = k == attr +instance GitAnnexTaggable Presence where + -- always mark extended away + insertGitAnnexTag p e = p { presencePayloads = extendedAway : e : presencePayloads p } + extractGitAnnexTag = headMaybe . filter isGitAnnexTag . presencePayloads -getAttr :: Element -> Name -> Maybe T.Text -getAttr (Element _name attrs _nodes) name = - T.concat . map unpack . snd <$> headMaybe (filter (isAttr name) attrs) +{- Gets the attr and value from a git-annex tag. -} +getGitAnnexAttrValue :: GitAnnexTaggable a => a -> Maybe (Name, Text) +getGitAnnexAttrValue a = case extractGitAnnexTag a of + Just (Element _ [(attr, content)] []) -> Just $ + (attr, T.concat $ map unpack content) + _ -> Nothing where unpack (ContentText t) = t unpack (ContentEntity t) = t -uuidSep :: T.Text -uuidSep = T.pack "," +{- A presence with a git-annex tag in it. -} +gitAnnexPresence :: Element -> Presence +gitAnnexPresence = insertGitAnnexTag $ emptyPresence PresenceAvailable -{- git-annex tag with one push attribute per UUID pushed to. -} -encodePushNotification :: [UUID] -> Element -encodePushNotification us = Element gitAnnexTagName - [(pushAttr, [ContentText pushvalue])] [] - where - pushvalue = T.intercalate uuidSep $ - map (T.pack . fromUUID) us +{- A presence with an empty git-annex tag in it, used for letting other + - clients know we're around and are a git-annex client. -} +gitAnnexSignature :: Presence +gitAnnexSignature = gitAnnexPresence $ Element gitAnnexTagName [] [] -decodePushNotification :: Element -> Maybe [UUID] -decodePushNotification (Element name attrs _nodes) - | name == gitAnnexTagName && not (null us) = Just us - | otherwise = Nothing - where - us = map (toUUID . T.unpack) $ - concatMap (T.splitOn uuidSep . T.concat . map fromContent . snd) $ - filter ispush attrs - ispush (k, _) = k == pushAttr - fromContent (ContentText t) = t - fromContent (ContentEntity t) = t +{- A message with a git-annex tag in it. -} +gitAnnexMessage :: Element -> Message +gitAnnexMessage = insertGitAnnexTag silentMessage +{- A notification that we've pushed to some repositories, listing their + - UUIDs. -} pushNotification :: [UUID] -> Presence -pushNotification = gitAnnexPresence . encodePushNotification +pushNotification = gitAnnexPresence . gitAnnexTag pushAttr . encodePushNotification + +pushAttr :: Name +pushAttr = Name (T.pack "push") Nothing Nothing + +uuidSep :: T.Text +uuidSep = T.pack "," + +encodePushNotification :: [UUID] -> Text +encodePushNotification = T.intercalate uuidSep . map (T.pack . fromUUID) + +decodePushNotification :: Text -> [UUID] +decodePushNotification = map (toUUID . T.unpack) . T.splitOn uuidSep {- A request for other git-annex clients to send presence. -} presenceQuery :: Presence -presenceQuery = gitAnnexPresence $ Element gitAnnexTagName - [ (queryAttr, [ContentText T.empty]) ] - [] +presenceQuery = gitAnnexPresence $ gitAnnexTag queryAttr T.empty + +queryAttr :: Name +queryAttr = Name (T.pack "query") Nothing Nothing -isPresenceQuery :: Presence -> Bool -isPresenceQuery p = case filter isGitAnnexTag (presencePayloads p) of - [] -> False - ((Element _name attrs _nodes):_) -> any (isAttr queryAttr) attrs +{- A notification about a stage of pairing. -} +pairingNotification :: PairStage -> UUID -> JID -> JID -> Message +pairingNotification pairstage u tojid fromjid = + (gitAnnexMessage tag) + { messageTo = Just tojid + , messageFrom = Just fromjid + } + where + tag = gitAnnexTag pairAttr $ + encodePairingNotification pairstage u -{- A notification about a stage of pairing, sent as directed presence - - to all clients of a jid. - - - - For PairReq, the directed presence is followed by a second presence - - without the pair notification. This is done because XMPP servers - - resend the last directed presence periodically, which can make - - the pair request alert be re-displayed annoyingly. For PairAck and - - PairDone, that resending is a desirable feature, as it helps ensure - - clients see them. - -} -encodePairingNotification :: PairStage -> UUID -> JID -> JID -> [Presence] -encodePairingNotification pairstage u tojid fromjid - | pairstage == PairReq = [send, clear] - | otherwise = [send] - where - send = directed $ gitAnnexPresence $ Element gitAnnexTagName - [(pairAttr, [ContentText content])] [] - clear = directed $ gitAnnexPresence gitAnnexSignature - - directed p = p - { presenceTo = Just $ baseJID tojid - , presenceFrom = Just fromjid - } - - content = mkPairingContent pairstage u - -{- A notification about a stage of pairing. Sent to self as an XMPP IQ. - - Directed presence is not used for self-messaging presence because - - some XMPP clients seem very confused by it. Google Talk has been - - observed leaking self-directed presence to other friends, seeming - - to think it sets the visible presence. - - - - The pairing info is sent using its id attribute; it also has a git-annex - - tag to identify it as from us. -} -encodeSelfPairingNotification :: PairStage -> UUID -> JID -> JID -> IQ -encodeSelfPairingNotification pairstage u tojid fromjid = (emptyIQ IQGet) - { iqTo = Just tojid - , iqFrom = Just fromjid - , iqID = Just $ mkPairingContent pairstage u - , iqPayload = Just gitAnnexSignature - } - -decodePairingNotification :: Presence -> Maybe NetMessage -decodePairingNotification p = case filter isGitAnnexTag (presencePayloads p) of - [] -> Nothing - (elt:_) -> parsePairingContent (presenceFrom p) =<< getAttr elt pairAttr - -decodeSelfPairingNotification :: IQ -> Maybe NetMessage -decodeSelfPairingNotification iq@(IQ { iqPayload = Just elt }) - | isGitAnnexTag elt = parsePairingContent (iqFrom iq) =<< iqID iq - | otherwise = Nothing -decodeSelfPairingNotification _ = Nothing - -mkPairingContent :: PairStage -> UUID -> T.Text -mkPairingContent pairstage u = T.unwords $ map T.pack +pairAttr :: Name +pairAttr = Name (T.pack "pair") Nothing Nothing + +encodePairingNotification :: PairStage -> UUID -> Text +encodePairingNotification pairstage u = T.unwords $ map T.pack [ show pairstage , fromUUID u ] -parsePairingContent :: Maybe JID -> T.Text -> Maybe NetMessage -parsePairingContent jid t = parse $ words $ T.unpack t +decodePairingNotification :: Text -> Message -> Maybe NetMessage +decodePairingNotification t msg = parse $ words $ T.unpack t where parse [stage, u] = PairingNotification <$> readish stage - <*> (formatJID <$> jid) + <*> (formatJID <$> messageFrom msg) <*> pure (toUUID u) parse _ = Nothing {- The JID without the client part. -} baseJID :: JID -> JID baseJID j = JID (jidNode j) (jidDomain j) Nothing + +{- An XMPP chat message with an empty body. This should not be displayed + - by clients, but can be used for communications. -} +silentMessage :: Message +silentMessage = (emptyMessage MessageChat) + { messagePayloads = [ emptybody ] } + where + emptybody = Element + { elementName = Name (T.pack "body") Nothing Nothing + , elementAttributes = [] + , elementNodes = [] + } + +{- Add to a presence to mark its client as extended away. -} +extendedAway :: Element +extendedAway = Element (Name (T.pack "show") Nothing Nothing) [] + [NodeContent $ ContentText $ T.pack "xa"] |