{- Pontarius Media Server - The XPMN Media Server Copyright © 2010-2011 Jon Kristensen This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . -} {- | Module : $Header$ Description : Handles an XPMN XMPP session. Copyright : Copyright © 2010-2011 Jon Kristensen License : AGPL-3 Maintainer : info@pontarius.org Stability : unstable Portability : portable This module is not yet finished. Not all features mentioned below are implemented. This module wraps an XMPP client that handles the Extended Personal Media Network (XPMN) XMPP session for XPMN-enabled devices (where the word `device' refers to a logical software entity in the extended media network) as well as the command and event system of XPMN. As such, this module communicates with both the XMPP server (over XML stanzas) as well as with the XPMN device application. In terms of XMPP client capabilities, this module implements XEP-0030 (Service Discovery) and a subset of XEP-0060 (Publish-Subscribe). It is assumed that you have a basic understanding of XMPP, XPMN and the command and event mechanism of XPMN. If this is not the case, you might want to check out http://www.xpmn.org/ for more information. That being said, we have tried to hide as many of the XMPP-related details as possible in order to make use of this module both simple and efficient. This module has to be initialized (having its initial state (variables and service discovery capabilities) set) and connected (being "on-line" on the XMPP account) before it can do anything useful. This is done like this: [...] Note the [...] (channel?) which is used to [...]. Whenever connected, the client will forward any possibly state-changing information to your application. Here is one example of how you can catch it: [...] If you would like to go off-line, just send the following message: [...] -} module XPMN where import qualified Data.Map as Map import qualified Data.Char as Char -- A Jabber ID, which can be either bare (localpart@domain.tld or domain.tld) -- or full (localpart@domain.tld/resource or domain.tld/resource). type JID = String -- Affiliations MUST be based on bare JID (localpart@domain.tld or -- domain.tld). Publisher, PublishOnly, Member and Outcast are RECOMMENDED but -- are not necessary for our implementation. `NotAffiliated' corresponds to -- `None' in XEP-0060. data SubscriptionAffiliation = Owner | NotAffiliated deriving (Show) -- As subscriptions are immediately accepted or denied, the Pending and -- Unconfigured states are unnecessary for our implementation. `NotSubscribed' -- corresponds to `None' in XEP-0060. data SubscriptionState = Subscribed | NotSubscribed deriving (Show) -- TODO: Format of subscription identifiers? type SubscriptionID = String type Subscription = (SubscriptionAffiliation, SubscriptionState, SubscriptionID) type Subscriptions = Map.Map JID Subscription -- An XPMN variable has a String name as well as either a String value (in the -- case of a simple variable) or an XML value (in the case of a complex -- variable). TODO: XMLElem instead of Integer. type VariableName = String data VariableValue = Simple String | Complex [Integer] deriving (Show) type Variables = Map.Map VariableName VariableValue -- Internal node names are assumed to be lower-case. type NodeName = String -- TODO: Record? type Node = (NodeName, Subscriptions, Variables) type Nodes = Map.Map NodeName Node data State = State { nodes :: Nodes } deriving (Show) newState :: State newState = State { nodes = Map.empty } -- TODO: Generate subscription identifier. newNode :: NodeName -> JID -> Node newNode nodeName owner = (nodeName, subscriptions, variables) where subscriptions = Map.singleton owner (Owner, Subscribed, "") variables = Map.empty addNode :: Node -> Nodes -> Either Nodes String addNode node @ (nodeName, subscriptions, variables) nodes | nodeName `Map.member` nodes = Right "Already Exists" | otherwise = Left $ Map.insert nodeName node nodes -- TODO: Generate subscription identifier. subscribe :: JID -> Node -> Either Node String subscribe jid (node_name, subscriptions, variables) | jid `Map.member` subscriptions = Right "Already Subscribed" | otherwise = Left (node_name, subscriptions', variables) where subscriptions' = Map.insert jid (NotAffiliated, Subscribed, "") subscriptions