packages feed

conjure-0.1: src/Conjure/UI/Http.hs

module Conjure.UI.Http (httpServer) where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception

import Conjure.Types
import Conjure.Utils
import Conjure.Piecemap
import Conjure.Network.Peer ()
import Conjure.STM.PeerCtrl
import Conjure.Debug

import System.IO
import GHC.Conc (unsafeIOToSTM)
import Data.Map (Map)
import Data.List
import Data.Array.Diff hiding ((!))
import qualified Data.ByteString as BS
import Network                ( PortID(..), PortNumber, listenOn, accept )

import qualified Data.Map as Map
import qualified Data.IntMap as IntMap
import Text.Html

httpServer :: TVar [ConnectedPeer]
          -> TVar (Map BS.ByteString ActiveTorrent)
          -> PortNumber
          -> IO ()
httpServer connectedPeers torrentMap port
    = do sock <- listenOn (PortNumber port)
         debug $ "HTTP UI Server is awaiting connections. Socket: " ++ show sock
         let loop = do (hndle, hostname, prt) <- accept sock
                       debug $ "HTTP UI server got connect from: " ++
                               show hostname ++ ":" ++ show prt ++
                               " (handle: " ++ show hndle ++ ")"
                       forkIO (handleUIrequest connectedPeers torrentMap hndle `finally` hClose hndle)
                       loop
         loop

handleUIrequest :: TVar [ConnectedPeer] -> TVar (Map k ActiveTorrent)
                                               -> Handle
                                               -> IO ()
handleUIrequest connectedPeers torrentMap hndle =
  do request <- hGetLine hndle
     page <- atomically $
             do peers <- readTVar connectedPeers
                torrents <- readTVar torrentMap
                at_rows <- mapM dumpActiveTorrent (zip ones (Map.elems torrents))
                peer_rows <- mapM dumpConnectedPeer (zip ones peers)
                return $ body <<
                         h1 << "Request:" +++ request +++
                         table![border 1] <<
                           ( ( th << "Torrent map:" <-> th << "Connected peers:" )
                             </>
                             ( td![valign "top"] << ( table![border 1] << at_rows ) <->
                               td![valign "top"] << ( table![border 1] <<
                                                      ( (th << "#"                <->
                                                         th << "Choke"            <-> th << "Interest"       <->
                                                         th << "Pending" <->
                                                         th << "Download kb/s"    <-> th << "Upload kb/s"    <->
                                                         th << "Complete"
                                                        )
                                                        </>
                                                        (tr << peer_rows)
                                                      )
                                                    )
                             )
                           )
     hPutStr hndle $ renderHtml page
  where
  ones :: [Int]
  ones = [1..]
  dumpActiveTorrent (n,at) =
    do up <- readTVar $ atUploaded at
       down <- readTVar $ atDownloaded at
       piece_map <- readTVar $ atPiecemap at
       pieces <- do pmap <- readTVar $ atPieces at
                    mapM (\(idx,q) -> do s<-readTVar (pStatus q); return (idx,s)) (IntMap.toList pmap)
       let files = tFiles' (tInfo $ atTorrent at)
       return $ td << ("Torrent number " +++ show n) <->
                  ( ( td << "Our Peer ID:" <-> td << (show $ atPeerId at) <->
                      td << "Uploaded:" <->  td << (show up) <->
                      td << "Downloaded:" <-> td << (show down)
                    )
                    </>
                    ( td <<"Piecemap:"  <-> td << drawPiecemap piece_map "")
                    </>
                    ( td << "Pieces:" <-> td << (foldr (\x y -> x +++ br +++ y) noHtml $ map show pieces))
                    </>
                    ( td << "Files:" <-> files )
                  )

  tFiles' (SingleFile len nme plen _) =
    td << nme <-> td << (show len +++ " bytes") <-> td << (show plen ++ " bytes per piece)")
  tFiles' (MultiFile files nme plen _) =
    (td << nme <-> td << (show plen ++ " bytes per piece)"))
    </>
    (aboves $ map tFile' files)
  tFile' (TorrentFile len path) =
    td << path <-> td << (show len +++ " bytes")

  dumpConnectedPeer (n,cp) =
    do lChoke <- getLocalChoke cp
       rChoke <- getRemoteChoke cp
       lInterest <- getLocalInterest cp
       rInterest <- getRemoteInterest cp
       pending <- readTVar (cpPendingBlocks cp)
       now <- unsafeIOToSTM $ getCurrentTime
       downBps <- if lInterest && not rChoke
                  then fmap (Just .flip div 1024) $ getTiming  now 20000 (cpDownloadTimings cp)
                  else return Nothing
       upBps <- if rInterest && not lChoke
                then fmap (Just .flip div 1024) $ getTiming  now 20000 (cpUploadTimings cp)
                else return Nothing
       piecemap <- readTVar (cpPiecemap cp)
       let complete = fromIntegral (length $ filter id $ elems piecemap) / (fromIntegral (rangeSize $ bounds piecemap) :: Double)
       return $ td << (show n) <->
                td << (show (lChoke,rChoke)) <-> td<< (show (lInterest,rInterest)) <->
                td << (show (Map.size pending)) <->
                td << (show downBps) <->td<<(show upBps)<->
                td << (show (complete * 100) ++ "%")