packages feed

hoodle-core-0.15: src/Hoodle/Coroutine/Network.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ScopedTypeVariables #-}

-----------------------------------------------------------------------------
-- |
-- Module      : Hoodle.Coroutine.Network
-- Copyright   : (c) 2013, 2014 Ian-Woo Kim
--
-- License     : BSD3
-- Maintainer  : Ian-Woo Kim <ianwookim@gmail.com>
-- Stability   : experimental
-- Portability : GHC
--
-----------------------------------------------------------------------------

module Hoodle.Coroutine.Network where

import           Control.Applicative
import           Control.Concurrent hiding (yield)
import           Control.Lens 
import           Control.Monad
import           Control.Monad.Loops 
import           Control.Monad.State (get)
import           Control.Monad.Trans
import           Control.Monad.Trans.Maybe (MaybeT(..))
import qualified Data.Binary as Bi 
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
-- import qualified Data.Foldable as F (mapM_)
import           Data.IORef
import           Data.Monoid ((<>),mconcat)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Traversable as Tr
import           Data.Word
import qualified Graphics.UI.Gtk as Gtk
import           Network.Info
import           Network.Simple.TCP
-- 
import           Hoodle.Coroutine.Draw
import           Hoodle.Script.Hook
import           Hoodle.Type.Coroutine
import           Hoodle.Type.Enum
import           Hoodle.Type.Event
import           Hoodle.Type.HoodleState (hookSet)
-- 

server :: (AllEvent -> IO ()) -> HostPreference -> T.Text -> IO ()
server evhandler ip txtorig = do
    ref <- newIORef txtorig
    forever $ listen ip  "4040" $ \(lsock, _) -> 
      accept lsock $ \(sock,addr) -> do
        txt <- readIORef ref
        let bstr = TE.encodeUtf8 txt
            bstr_size :: Word32 = (fromIntegral . B.length) bstr 
            bstr_size_binary = (mconcat . LB.toChunks . Bi.encode) bstr_size
        putStrLn $ "TCP connection established from " ++ show addr
        send sock (bstr_size_binary <> TE.encodeUtf8 txt)
        unfoldM_ $ do
          mbstr <- runMaybeT $ do 
            bstr' <- MaybeT (recv sock 4)
            let getsize :: B.ByteString -> Word32 
                getsize = Bi.decode . LB.fromChunks . return
                size = (fromIntegral . getsize) bstr'

                go s bs = do 
                  liftIO $ putStrLn ("requested size = " ++ show s)
                  bstr1 <- MaybeT (recv sock s)
                  let s' = B.length bstr1 
                  liftIO $ putStrLn ("obtained size = " ++ show s')
                  if s <= s' 
                    then return (bs <> bstr1)
                    else go (s-s') (bs <> bstr1) 
            go size B.empty 
          Tr.forM mbstr $ \bstr' -> do 
            let txt' = TE.decodeUtf8 bstr'
            (evhandler . UsrEv . NetworkProcess . NetworkReceived) txt'
            writeIORef ref txt'
          return mbstr
        putStrLn "FINISHED"


networkTextInputBody :: T.Text -> MainCoroutine (String,ThreadId,MVar ()) -- ^ (ip address,thread id,lock)
networkTextInputBody txt = do
    mipscr <- runMaybeT $ do hkset <- MaybeT (view hookSet <$> lift get)
                             (MaybeT . return)  (getIPaddress hkset) 
    let ipfind = do 
          let ipv4num (IPv4 x) = x 
              ismacnull (MAC a b c d e f) = a == 0 && b == 0 && c == 0 
                                            && d == 0 && e == 0 && f == 0 
          ifcs <- liftIO $ getNetworkInterfaces
          let ifcs2 = Prelude.filter (not . ismacnull . mac) 
                      . Prelude.filter (((/=) 0) . ipv4num . ipv4 ) $ ifcs
          return (if Prelude.null ifcs2 then "127.0.0.1" else (show . ipv4 . head) ifcs2) 
    ip <- maybe ipfind liftIO mipscr 
    doIOaction $ \evhandler -> do  
      done <- newEmptyMVar
      tid <- forkIO (server evhandler (Host ip) txt) 
      (return . UsrEv . NetworkProcess) (NetworkInitialized tid done)
    let go = do 
          r <- nextevent
          case r of
            UpdateCanvas cid -> invalidateInBBox Nothing Efficient cid >> go
            NetworkProcess (NetworkInitialized tid done) -> return (tid,done)
            _ -> go 
    (tid,done) <- go 
    return (ip,tid,done)

networkTextInput :: T.Text -> MainCoroutine (Maybe T.Text)
networkTextInput txt = do 
    (ip,tid,done) <- networkTextInputBody txt
    let ipdialog msg = \_evhandler -> do                  
          dialog <- Gtk.messageDialogNew Nothing [Gtk.DialogModal]
            Gtk.MessageQuestion Gtk.ButtonsOkCancel msg 
          forkIO $ do          
            readMVar done 
            Gtk.dialogResponse dialog Gtk.ResponseOk
          res <- Gtk.dialogRun dialog 
          let b = case res of 
                    Gtk.ResponseOk -> True
                    _ -> False
          Gtk.widgetDestroy dialog 
          return (UsrEv (OkCancel b))
    doIOaction (ipdialog ("networkedit " ++ ip ++ " 4040"))
    --
    let actf t = do 
          r <- nextevent
          case r of 
            UpdateCanvas cid -> invalidateInBBox Nothing Efficient cid 
                                >> actf t
            OkCancel True -> (return . Just) t
            OkCancel False -> return Nothing
            NetworkProcess (NetworkReceived txt') ->  do 
              doIOaction $ \_ -> Gtk.postGUISync (putMVar done ())
                                 >> (return . UsrEv . NetworkProcess) NetworkCloseDialog
              actf txt' 
              
            _ -> actf t
    ntxt <- actf txt
    --   
    doIOaction $ \_evhandler -> do  
      killThread tid
      (return . UsrEv . NetworkProcess) NetworkClosed
    --
    return ntxt