{-
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 <http://www.gnu.org/licenses/>.
-}
{- |
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