summaryrefslogtreecommitdiff
path: root/examples/dht.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-01-22 05:31:14 -0500
committerjoe <joe@jerkface.net>2017-01-22 05:31:14 -0500
commit8cf4de73d77197032fd8ebfc4e4f3a00b287e0e7 (patch)
tree6ee5d529caf714851223d2da9f22eb1510d5cfee /examples/dht.hs
parent1c8cbe8fc66466936b4f889b3893ca3c23478631 (diff)
New: DHT deamon and command-line interface.
Diffstat (limited to 'examples/dht.hs')
-rw-r--r--examples/dht.hs80
1 files changed, 80 insertions, 0 deletions
diff --git a/examples/dht.hs b/examples/dht.hs
new file mode 100644
index 00000000..feeee9ff
--- /dev/null
+++ b/examples/dht.hs
@@ -0,0 +1,80 @@
1{-# LANGUAGE NondecreasingIndentation #-}
2import Control.Monad
3import Control.Monad.Fix
4import Control.Monad.IO.Class
5import Data.Char
6import Network.Socket as Socket
7import System.Console.Haskeline
8import System.Environment
9import System.Exit
10import System.IO
11import System.IO.Unsafe
12import qualified Data.ByteString as B
13
14-- | Reads one character. If it is not a digit,
15-- then it is discarded and 'Nothing' is returned.
16hReadDigit :: Handle -> IO (Maybe Char)
17hReadDigit h = do c <- hGetChar h
18 return $ guard (isDigit c) >> pure c
19
20-- | Expected input: "nnn:..."
21-- Here we read the digit sequence "nnn" and drop the colon
22-- as it is the first non-digit.
23hReadInt :: Handle -> IO Int
24hReadInt h = do
25 nstr <- fix $ \readDigits ->
26 maybe (return []) -- dropped non-digit character
27 (($ unsafeInterleaveIO readDigits) . fmap . (:))
28 =<< hReadDigit h
29 readIO nstr :: IO Int
30
31
32-- | Read a length prefixed string from a handle.
33-- The format is "nnn:..." where /nnn/ is an ascii-encoded character count
34-- and /.../ is the sequence of characters
35--
36-- Note: The first byte after the count is ignored and discarded.
37readResponse :: Handle -> IO String
38readResponse h = do
39 n <- hReadInt h
40 sequence $ replicate n (hGetChar h)
41
42-- | Send a command to the dhtd daemon and then print the response.
43sendCommand :: Handle -> String -> InputT IO ()
44sendCommand h cmd = do resp <- liftIO $ do hPutStrLn h cmd
45 readResponse h
46 outputStrLn resp
47
48-- | Get one line of input and send it to the daemon, then run the
49-- passed continuation if it wasn't "quit".
50interactiveMode :: Handle -> InputT IO () -> InputT IO ()
51interactiveMode h repl = do
52 minput <- getInputLine "dht> "
53 case minput of
54 Nothing -> return ()
55 Just "quit" -> sendCommand h "quit" >> return ()
56 Just cmd -> sendCommand h cmd >> repl
57
58main :: IO ()
59main = do
60 -- Open the control socket to the daemon.
61 h <- liftIO $ handle (\e -> do hPutStrLn stderr (show (e ::IOError))
62 exitFailure)
63 $ do sock <- socket AF_UNIX Stream defaultProtocol
64 connect sock (SockAddrUnix "dht.sock")
65 socketToHandle sock ReadWriteMode
66
67 -- Haskeline's default looks only at our stdin and not our stdout.
68 -- That's a bad idea because we can take input from the command line.
69 behavior <- do
70 useTerminal <- and <$> mapM hIsTerminalDevice [stdin,stdout]
71 return $ if useTerminal then preferTerm else useFileHandle stdin
72
73 runInputTBehaviorWithPrefs behavior defaultPrefs defaultSettings $ do
74
75 -- A command may be specified on the command line
76 -- or else we enter an interactive shell.
77 args <- dropWhile isSpace . unwords <$> liftIO getArgs
78 case args of
79 (_:_) -> sendCommand h args >> sendCommand h "quit"
80 _ -> fix $ interactiveMode h