packages feed

rtorrent-rpc-0.2.0.0: Network/RTorrent/SCGI.hs

{-# LANGUAGE OverloadedStrings #-}

{-# OPTIONS_HADDOCK hide #-}

{-|
Module      : SCGI
Copyright   : (c) Kai Lindholm, 2014
License     : MIT
Maintainer  : megantti@gmail.com
Stability   : experimental

An internal module for establishing a connection with RTorrent.
-}

module Network.RTorrent.SCGI (Headers, Body(..), query) where

import Control.Applicative
import Data.Either (partitionEithers)
import Data.Monoid

import Blaze.ByteString.Builder
import Blaze.ByteString.Builder.Char8
import Blaze.Text
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS

import Network

type Headers = [(ByteString, ByteString)]

data Body = Body {
      headers :: Headers
    , body :: ByteString
} deriving Show

makeRequest :: Body -> ByteString
makeRequest (Body hd bd) = res 
  where
    res = toByteString . mconcat $ [ 
        integral len
      , fromChar ':'
      , fromByteString hdbs
      , fromChar ','
      , fromByteString bd
      ]
    fromBS0 bs = fromWrite $ writeByteString bs <> writeWord8 0
    hdbs = toByteString . mconcat $ 
           (fromBS0 "CONTENT_LENGTH" <> integral (BS.length bd) <> fromWord8 0): 
            map (\(a, b) -> fromBS0 a <> fromBS0 b) hd
    len = BS.length hdbs

parseResponse :: Parser Body
parseResponse = parseBody
  where
    lineParser :: Parser ByteString
    lineParser = 
        mappend <$> A.takeTill (== '\r') <*> (("\r\n" *> pure "") <|> lineParser)
    headerParser = (,) <$> A.takeTill (==  ':') <*> (": " *> lineParser) 

    contentParser = "\r\n" *> A.takeByteString
    parseBody = do
        header <- headerParser
        (moreHeaders, [content]) <- 
            partitionEithers <$> A.many' (A.eitherP headerParser contentParser)
        return $ Body (header : moreHeaders) content

query :: HostName -> Int -> Body -> IO (Either String Body)
query host port queryBody = do
    h <- connectTo host (PortNumber (toEnum port))
    BS.hPut h (makeRequest queryBody) 
    A.parseOnly parseResponse <$> BS.hGetContents h