hoovie-0.1.1: src/Hoovie/SOAP.hs
{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
module Hoovie.SOAP (
hoovieXmlUri,
soapHandler,
staticSendFile
) where
import Text.XML.Light (parseXMLDoc, findElement, QName(..), Element(..), strContent)
import Text.XML.Light.Lexer (XmlSource)
import Data.String (IsString)
import Data.FileEmbed (embedFile)
import Control.Applicative ((<|>))
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import Snap.Core
import Hoovie.Messages
import Hoovie.Monitor
import Hoovie.Util
hoovieXmlUri :: String
hoovieXmlUri = "static/hoovie.xml"
hooviePngUri :: String
hooviePngUri = "static/hoovie.png"
soapHandler :: String -> String -> FilePath -> Snap ()
soapHandler url uuid db =
path "static/UPnP_AV_ConnectionManager_1.0.xml" (staticSendXml $(embedFile "static/UPnP_AV_ConnectionManager_1.0.xml")) <|>
path "static/UPnP_AV_ContentDirectory_1.0.xml" (staticSendXml $(embedFile "static/UPnP_AV_ContentDirectory_1.0.xml")) <|>
path (BC.pack hoovieXmlUri) (staticSendXml $ hoovieXml url uuid hooviePngUri) <|>
path (BC.pack hooviePngUri) (staticSendFile "image/png" $(embedFile "static/hoovie.png")) <|>
subscribeAction uuid <|>
soapAction url db
subscribeAction :: String -> Snap ()
subscribeAction uuid = method (Method "SUBSCRIBE") (
path "upnp/event/connection_manager" (staticSendEvent uuid $ connectionManagerEvent) <|>
path "upnp/event/content_directory" (staticSendEvent uuid $ contentDirectoryEvent))
soapAction :: String -> FilePath -> Snap ()
soapAction url db = method POST $ do
action <- getsRequest (getHeader "SOAPACTION")
case action of
Nothing -> pass
Just a -> handleSoapAction url db a
staticSendEvent :: String -> B.ByteString -> Snap ()
staticSendEvent uuid bytes = do
modifyResponse $ setHeader "SID" (BC.pack $ "uuid:" ++ uuid)
. setHeader "TIMEOUT" "Second-1800"
staticSendXml bytes
staticSendXml :: B.ByteString -> Snap ()
staticSendXml = staticSendFile "text/xml; charset=\"utf-8\""
staticSendFile :: String -> B.ByteString -> Snap ()
staticSendFile contentType bytes = do
modifyResponse $ setContentLength (fromIntegral $ B.length bytes)
. setContentType (BC.pack contentType)
writeBS bytes
handleSoapAction :: (Eq a, Data.String.IsString a) => String -> FilePath -> a -> Snap ()
handleSoapAction _ _ "\"urn:schemas-upnp-org:service:ConnectionManager:1#GetProtocolInfo\"" = staticSendXml $(embedFile "static/get-protocol-info.xml")
handleSoapAction _ _ "\"urn:schemas-upnp-org:service:ContentDirectory:1#GetSortCapabilities\"" = staticSendXml $(embedFile "static/get-sort-capabilities.xml")
handleSoapAction _ _ "\"urn:schemas-upnp-org:service:ContentDirectory:1#GetSearchCapabilities\"" = staticSendXml $(embedFile "static/get-search-capabilities.xml")
handleSoapAction _ _ "\"urn:schemas-upnp-org:service:ContentDirectory:1#GetSystemUpdateID\"" = staticSendXml $(embedFile "static/get-system-update-id.xml")
handleSoapAction url db "\"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"" = do
body <- getRequestBody
case parseBrowseRequest body of
Just (objectId, start, count, browseFlag) -> do
resources <- liftIO $ getResources db
staticSendXml $ browseResponse url resources (if browseFlag == "BrowseMetadata" then BrowseObject else BrowseChildren) objectId start count 1
Nothing -> pass
handleSoapAction _ _ _ = pass
parseBrowseRequest :: XmlSource s => s -> Maybe (String, Int, Int, String)
parseBrowseRequest xml = do
doc <- parseXMLDoc xml
objectId <- getTagValue doc $ QName "ObjectID" Nothing Nothing
startIndexStr <- getTagValue doc $ QName "StartingIndex" Nothing Nothing
reqCountStr <- getTagValue doc $ QName "RequestedCount" Nothing Nothing
browseFlag <- getTagValue doc $ QName "BrowseFlag" Nothing Nothing
-- filter <- getTagValue doc $ QName "Filter" Nothing Nothing
startIndex <- maybeRead startIndexStr
reqCount <- maybeRead reqCountStr
return (objectId, startIndex, reqCount, browseFlag)
getTagValue :: Element -> QName -> Maybe String
getTagValue doc item = case findElement item doc of
Nothing -> Nothing
Just el -> Just $ strContent el