diff options
-rw-r--r-- | Codec/LineReady.hs | 23 | ||||
-rw-r--r-- | Codec/SafeBlob.hs | 22 | ||||
-rw-r--r-- | KikiD/GetLine.hs | 18 | ||||
-rw-r--r-- | KikiD/Message.hs | 35 | ||||
-rw-r--r-- | KikiD/PortServer.hs | 43 | ||||
-rw-r--r-- | kiki.cabal | 3 | ||||
-rw-r--r-- | kikid.hs | 15 |
7 files changed, 66 insertions, 93 deletions
diff --git a/Codec/LineReady.hs b/Codec/LineReady.hs new file mode 100644 index 0000000..ca2cde3 --- /dev/null +++ b/Codec/LineReady.hs | |||
@@ -0,0 +1,23 @@ | |||
1 | module Codec.LineReady where | ||
2 | |||
3 | import qualified Data.ByteString.Char8 as B | ||
4 | import Data.Monoid | ||
5 | import Data.List (foldl') | ||
6 | import Data.Maybe | ||
7 | |||
8 | toLineReady :: B.ByteString -> B.ByteString | ||
9 | toLineReady blob = | ||
10 | let as = zip [0..] (B.unpack blob) | ||
11 | bs = filter ((=='\n') . snd) as | ||
12 | is = map fst bs | ||
13 | in B.pack (show is) <> foldl' (replaceCharStrIndex '#') blob is <> B.singleton '\n' | ||
14 | |||
15 | replaceCharStrIndex :: Char -> B.ByteString -> Int -> B.ByteString | ||
16 | replaceCharStrIndex c str i = a <> B.singleton c <> B.drop 1 b | ||
17 | where (a,b) = B.splitAt i str | ||
18 | |||
19 | fromLineReady :: B.ByteString -> B.ByteString | ||
20 | fromLineReady str = foldl' (replaceCharStrIndex '\n') (B.drop 1 str') is | ||
21 | where is = map fst . mapMaybe B.readInt $ | ||
22 | B.groupBy (\c d -> (c/=',')&&(d/=',')) ls | ||
23 | (ls,str') = B.break (==']') (B.tail str) | ||
diff --git a/Codec/SafeBlob.hs b/Codec/SafeBlob.hs deleted file mode 100644 index a6db80e..0000000 --- a/Codec/SafeBlob.hs +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | {-# LANGUAGE ViewPatterns #-} | ||
2 | module Codec.SafeBlob where | ||
3 | |||
4 | import qualified Data.ByteString.Char8 as B | ||
5 | import Data.Monoid | ||
6 | import Data.List (foldl') | ||
7 | import Data.Maybe | ||
8 | |||
9 | toSafe :: B.ByteString -> B.ByteString | ||
10 | toSafe blob = let as = zip [0..] (B.unpack blob) | ||
11 | bs = filter ((=='\n') . snd) as | ||
12 | is = map fst bs | ||
13 | in B.pack (show is) <> foldl' (replaceCharStrIndex '#') blob is | ||
14 | |||
15 | replaceCharStrIndex :: Char -> B.ByteString -> Int -> B.ByteString | ||
16 | replaceCharStrIndex c str i = a <> B.singleton c <> b | ||
17 | where (a,B.uncons -> Just (_,b)) = B.splitAt i str | ||
18 | |||
19 | fromSafe str = foldl' (replaceCharStrIndex '\n') (B.drop 1 str') is | ||
20 | where is = map fst . mapMaybe B.readInt $ | ||
21 | B.groupBy (\c d -> (c/=',')&&(d/=',')) ls | ||
22 | (ls,str') = B.break (==']') (B.tail str) | ||
diff --git a/KikiD/GetLine.hs b/KikiD/GetLine.hs deleted file mode 100644 index 8af5dc6..0000000 --- a/KikiD/GetLine.hs +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | module KikiD.GetLine where | ||
2 | |||
3 | import Control.Monad | ||
4 | import Data.Serialize | ||
5 | import qualified Data.ByteString as BS | ||
6 | import qualified Data.ByteString.Lazy as L | ||
7 | import Data.Monoid | ||
8 | import Data.Binary.Builder | ||
9 | |||
10 | getLine :: Get BS.ByteString | ||
11 | getLine = getWords empty | ||
12 | where | ||
13 | getWords b = do | ||
14 | w <- getWord8 | ||
15 | let x = singleton w | ||
16 | if (w == 10 || w == 0) | ||
17 | then return $ BS.concat . L.toChunks . toLazyByteString $ b <> x | ||
18 | else getWords (b <> x) | ||
diff --git a/KikiD/Message.hs b/KikiD/Message.hs index 5b642f3..cd3ee71 100644 --- a/KikiD/Message.hs +++ b/KikiD/Message.hs | |||
@@ -1,22 +1,37 @@ | |||
1 | {-# LANGUAGE DoAndIfThenElse #-} | ||
1 | module KikiD.Message where | 2 | module KikiD.Message where |
2 | 3 | ||
3 | import Data.Serialize | 4 | import Data.Serialize as Cereal |
4 | import qualified KikiD.GetLine | ||
5 | import qualified Data.ByteString.Char8 as B | 5 | import qualified Data.ByteString.Char8 as B |
6 | import Data.Monoid | 6 | import Data.Monoid |
7 | import Text.Read | 7 | import Text.Read |
8 | import Data.Char (ord) | 8 | import Data.Char (ord,chr) |
9 | import Control.Monad | 9 | import Control.Monad |
10 | import Data.Bytes.Serial as R | ||
11 | import Data.Bytes.Put as Put | ||
12 | import Data.Bytes.Get as Get | ||
13 | import Codec.LineReady | ||
14 | import Control.Monad.Loops | ||
10 | 15 | ||
11 | data KikiDMessage = TODO deriving (Show,Read) | 16 | data KikiDMessage = TODO deriving (Show,Read) |
12 | 17 | ||
13 | instance Serialize KikiDMessage where | 18 | instance Serialize KikiDMessage where |
14 | put m = mapM_ (putWord8 . fromIntegral . ord) "TODO" | 19 | put m = mapM_ (Cereal.putWord8 . fromIntegral . ord) "TO\nO" |
15 | -- putByteString . B.pack $ show m ++ "\n" | 20 | -- putByteString . B.pack $ show m ++ "\n" |
16 | get = do | 21 | get = do |
17 | t <- getWord8 | 22 | t <- Cereal.getWord8 |
18 | o <- getWord8 | 23 | o <- Cereal.getWord8 |
19 | d <- getWord8 | 24 | d <- Cereal.getWord8 |
20 | o <- getWord8 | 25 | o <- Cereal.getWord8 |
21 | return TODO | 26 | let s = map (chr . fromIntegral) [t,o,d,o] |
22 | 27 | if "TO\nO" == s | |
28 | then return TODO | ||
29 | else fail ("Could not decode message: " ++ show s) | ||
30 | |||
31 | instance Serial KikiDMessage where | ||
32 | serialize m = Put.putByteString . toLineReady . Cereal.encode $ m | ||
33 | deserialize = do | ||
34 | xs <- unfoldWhileM (/= '\n') (fmap (chr . fromIntegral) Get.getWord8) | ||
35 | case (Cereal.decode . fromLineReady $ B.pack xs) of | ||
36 | Left str -> fail str | ||
37 | Right x -> return x | ||
diff --git a/KikiD/PortServer.hs b/KikiD/PortServer.hs index 31101a7..b42e340 100644 --- a/KikiD/PortServer.hs +++ b/KikiD/PortServer.hs | |||
@@ -22,28 +22,13 @@ import Control.Monad.Loops | |||
22 | import KikiD.Multiplex (pipeTransHookMicroseconds) | 22 | import KikiD.Multiplex (pipeTransHookMicroseconds) |
23 | import Control.Exception | 23 | import Control.Exception |
24 | import Control.Concurrent.Async | 24 | import Control.Concurrent.Async |
25 | import Data.Serialize | 25 | import Data.Bytes.Serial as R |
26 | import Data.Bytes.Put as Put | ||
26 | 27 | ||
27 | import Control.Arrow (second) | 28 | import Control.Arrow (second) |
28 | --import qualified Merv.GetLine as MG | ||
29 | 29 | ||
30 | {-instance Serialize IRC.Message where | ||
31 | put = putByteString . IRC.encode | ||
32 | get = do | ||
33 | x <- MG.getLine | ||
34 | case IRC.decode x of | ||
35 | Just x -> return x | ||
36 | Nothing -> fail ("IRC PARSE ERROR:'" <> B.unpack x <> "'") | ||
37 | 30 | ||
38 | 31 | createTCPPortListener :: Serial a => PortNumber -> B.ByteString -> Int -> Int -> Int | |
39 | createIRCPortListener :: PortNumber -> B.ByteString -> Int -> Int -> Int | ||
40 | -> TBMQueue (ThreadId,TBMQueue IRC.Message) -> TBMQueue IRC.Message -> IO () | ||
41 | createIRCPortListener port name delay qsize maxconns postNewTChans outq = | ||
42 | createTCPPortListener port name delay qsize maxconns postNewTChans outq ircReact | ||
43 | |||
44 | -} | ||
45 | |||
46 | createTCPPortListener :: Serialize a => PortNumber -> B.ByteString -> Int -> Int -> Int | ||
47 | -> TBMQueue (ThreadId,TBMQueue a) -> TBMQueue a | 32 | -> TBMQueue (ThreadId,TBMQueue a) -> TBMQueue a |
48 | -> (Handle -> TBMQueue a -> IO ()) -> IO () | 33 | -> (Handle -> TBMQueue a -> IO ()) -> IO () |
49 | createTCPPortListener port name delay qsize maxconns postNewTChans outq react = | 34 | createTCPPortListener port name delay qsize maxconns postNewTChans outq react = |
@@ -65,7 +50,7 @@ createTCPPortListener port name delay qsize maxconns postNewTChans outq react = | |||
65 | sockAcceptLoop sock name delay qsize postNewTChans outq react | 50 | sockAcceptLoop sock name delay qsize postNewTChans outq react |
66 | ) | 51 | ) |
67 | 52 | ||
68 | sockAcceptLoop :: Serialize a => Socket -> B.ByteString -> Int -> Int -> TBMQueue (ThreadId,TBMQueue a) -> TBMQueue a | 53 | sockAcceptLoop :: Serial a => Socket -> B.ByteString -> Int -> Int -> TBMQueue (ThreadId,TBMQueue a) -> TBMQueue a |
69 | -> (Handle -> TBMQueue a -> IO ()) -> IO () | 54 | -> (Handle -> TBMQueue a -> IO ()) -> IO () |
70 | sockAcceptLoop listenSock name delay qsize postNewTChans outq react = | 55 | sockAcceptLoop listenSock name delay qsize postNewTChans outq react = |
71 | whileM_ (atomically $ fmap not (isClosedTBMQueue postNewTChans)) $ do | 56 | whileM_ (atomically $ fmap not (isClosedTBMQueue postNewTChans)) $ do |
@@ -98,7 +83,7 @@ sockAcceptLoop listenSock name delay qsize postNewTChans outq react = | |||
98 | waitBoth async1 async2 | 83 | waitBoth async1 async2 |
99 | ) | 84 | ) |
100 | 85 | ||
101 | runConn :: Serialize a => Handle -> B.ByteString -> TBMQueue a -> TBMQueue a -> Int | 86 | runConn :: Serial a => Handle -> B.ByteString -> TBMQueue a -> TBMQueue a -> Int |
102 | -> (Handle -> TBMQueue a -> IO ()) -> IO () | 87 | -> (Handle -> TBMQueue a -> IO ()) -> IO () |
103 | runConn hdl name q outq delay react = do | 88 | runConn hdl name q outq delay react = do |
104 | --send sock (encode (Message Nothing "NOTICE" ["*", ("Hi " <> name <> "!\n")])) | 89 | --send sock (encode (Message Nothing "NOTICE" ["*", ("Hi " <> name <> "!\n")])) |
@@ -115,7 +100,7 @@ runConn hdl name q outq delay react = do | |||
115 | whileM_ pending $ do | 100 | whileM_ pending $ do |
116 | m <- atomically (readTBMQueue q) | 101 | m <- atomically (readTBMQueue q) |
117 | case m of | 102 | case m of |
118 | Just m -> B.hPutStrLn hdl (encode m) | 103 | Just m -> B.hPutStrLn hdl (runPutS $ R.serialize m) |
119 | -- Nothing means the Queue is closed and empty, so dont loop | 104 | -- Nothing means the Queue is closed and empty, so dont loop |
120 | Nothing -> return () | 105 | Nothing -> return () |
121 | threadDelay delay | 106 | threadDelay delay |
@@ -127,19 +112,3 @@ runConn hdl name q outq delay react = do | |||
127 | (whileM_ (atomically . fmap not $ isClosedTBMQueue outq) $ react hdl outq ) | 112 | (whileM_ (atomically . fmap not $ isClosedTBMQueue outq) $ react hdl outq ) |
128 | 113 | ||
129 | 114 | ||
130 | {- | ||
131 | ircReact hdl outq = do | ||
132 | line <- B.hGetLine hdl | ||
133 | -- debugging | ||
134 | dir <- getAppUserDataDirectory "merv" | ||
135 | tid <- myThreadId | ||
136 | let bQuit = (B.isPrefixOf "/quit") line | ||
137 | appendFile (dir </> "xdebug") | ||
138 | (printf "%s:%s\n(bQuit=%s) %s\n" (show tid) (show line) (show bQuit) (show $ IRC.parseMessage line)) | ||
139 | -- end debugging | ||
140 | case IRC.decode line of | ||
141 | Just (IRC.msg_command -> "QUIT") -> atomically $ closeTBMQueue outq | ||
142 | Just m -> atomically $ writeTBMQueue outq m | ||
143 | Nothing | "/q" `B.isPrefixOf` line -> atomically $ closeTBMQueue outq | ||
144 | _ -> return undefined | ||
145 | -} | ||
@@ -45,7 +45,8 @@ Executable kikid | |||
45 | monad-loops -any, | 45 | monad-loops -any, |
46 | HTTP -any, | 46 | HTTP -any, |
47 | stm >= 2.3, | 47 | stm >= 2.3, |
48 | cereal -any | 48 | cereal -any, |
49 | bytes -any | ||
49 | 50 | ||
50 | library | 51 | library |
51 | exposed-modules: KeyRing | 52 | exposed-modules: KeyRing |
@@ -16,8 +16,11 @@ import Control.Monad.Loops | |||
16 | import Control.Exception | 16 | import Control.Exception |
17 | import Data.Monoid | 17 | import Data.Monoid |
18 | import qualified Data.ByteString.Char8 as B | 18 | import qualified Data.ByteString.Char8 as B |
19 | import Data.Serialize | 19 | --import Data.Serialize |
20 | import qualified Data.Map as M | 20 | import qualified Data.Map as M |
21 | import qualified Data.Bytes.Serial as Bytes | ||
22 | import qualified Data.Bytes.Get as Bytes | ||
23 | --import qualified Data.Bytes.Put as Bytes | ||
21 | 24 | ||
22 | -- TODO: Set this in config file | 25 | -- TODO: Set this in config file |
23 | port = 9800 | 26 | port = 9800 |
@@ -64,7 +67,7 @@ kikidMain _ = do | |||
64 | atomically $ closeTBMQueue newchans | 67 | atomically $ closeTBMQueue newchans |
65 | atomically $ closeTBMQueue incomming | 68 | atomically $ closeTBMQueue incomming |
66 | 69 | ||
67 | data ClientState = CState {cs_queue :: TBMQueue KikiDMessage} | 70 | data ClientState = CState {csQueue :: TBMQueue KikiDMessage} |
68 | type ClientID = ThreadId | 71 | type ClientID = ThreadId |
69 | threadIdToClient = id | 72 | threadIdToClient = id |
70 | 73 | ||
@@ -96,12 +99,14 @@ purgeClosedConnections quit currentClients = whileM_ (fmap not quit) $ do | |||
96 | handleMessage hdl outq = do | 99 | handleMessage hdl outq = do |
97 | line <- B.hGetLine hdl | 100 | line <- B.hGetLine hdl |
98 | tid <- myThreadId | 101 | tid <- myThreadId |
99 | case (decode line :: Either String KikiDMessage) of | 102 | case (Bytes.runGetS Bytes.deserialize (B.snoc line '\n') :: Either String KikiDMessage) of |
100 | Right _ -> | 103 | Right msg -> do |
101 | syslog Notice ("Message decoded on thread=" <> show tid) | 104 | syslog Notice ("Message decoded on thread=" <> show tid) |
105 | syslog Notice ("Message: " <> show msg) | ||
102 | Left str -> do | 106 | Left str -> do |
107 | syslog Notice ("ERROR: " <> show line) | ||
103 | syslog Notice ("ERROR: Unable to decode message on thread=" <> show tid) | 108 | syslog Notice ("ERROR: Unable to decode message on thread=" <> show tid) |
104 | syslog Notice str | 109 | syslog Notice ("ERROR: " ++ str) |
105 | 110 | ||
106 | consumeMessage currentClients msg = void $ syslog Notice ("Recieved Message: " ++ show msg) | 111 | consumeMessage currentClients msg = void $ syslog Notice ("Recieved Message: " ++ show msg) |
107 | -- TODO: Do more here... | 112 | -- TODO: Do more here... |