diff options
author | joe <joe@jerkface.net> | 2017-01-22 05:31:14 -0500 |
---|---|---|
committer | joe <joe@jerkface.net> | 2017-01-22 05:31:14 -0500 |
commit | 8cf4de73d77197032fd8ebfc4e4f3a00b287e0e7 (patch) | |
tree | 6ee5d529caf714851223d2da9f22eb1510d5cfee /examples/dht.hs | |
parent | 1c8cbe8fc66466936b4f889b3893ca3c23478631 (diff) |
New: DHT deamon and command-line interface.
Diffstat (limited to 'examples/dht.hs')
-rw-r--r-- | examples/dht.hs | 80 |
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 #-} | ||
2 | import Control.Monad | ||
3 | import Control.Monad.Fix | ||
4 | import Control.Monad.IO.Class | ||
5 | import Data.Char | ||
6 | import Network.Socket as Socket | ||
7 | import System.Console.Haskeline | ||
8 | import System.Environment | ||
9 | import System.Exit | ||
10 | import System.IO | ||
11 | import System.IO.Unsafe | ||
12 | import 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. | ||
16 | hReadDigit :: Handle -> IO (Maybe Char) | ||
17 | hReadDigit 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. | ||
23 | hReadInt :: Handle -> IO Int | ||
24 | hReadInt 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. | ||
37 | readResponse :: Handle -> IO String | ||
38 | readResponse 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. | ||
43 | sendCommand :: Handle -> String -> InputT IO () | ||
44 | sendCommand 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". | ||
50 | interactiveMode :: Handle -> InputT IO () -> InputT IO () | ||
51 | interactiveMode 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 | |||
58 | main :: IO () | ||
59 | main = 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 | ||