xi or the fast and furious Haskell

Don’t be confused by the title of this post - I will tell you about my experience in the development of xmpp client xi. The first version of this client was written in Haskell in the shortest time (for me, of cource), and this fact provides the second emotional part of title =)

First of all - xi was inspired by ii irc client. It explains the all of its features, design and main idea. In short - after this post I’m a huge fan of this tool and philosophy.

Second - xi was written in Haskell. I will not explain why =)

Now let’s take a look inside. We can see a lot of dependencies of course - xi uses pontarius xmpp for the XMPP interaction. But there is an interesting hidden trick - we must use this library from the github directly yet, because of an unpleasant bug. This can be done by the cabal sandbox add-source command:

git clone http://github.com/pontarius/pontarius-xmpp .deps/pontarius-xmpp cabal sandbox init cabal sandbox add-source .deps/pontarius-xmpp

Also, if we want to support gmail.com , we must use some extra TLS options:

import Network.TLS import Network.TLS.Extra sess <- session server ( Just ( \ _ -> ( [ plain user Nothing password ]), Nothing )) def { sessionStreamConfiguration = def { tlsParams = defaultParamsClient { pConnectVersion = TLS10 , pAllowedVersions = [ TLS10 , TLS11 , TLS12 ] , pCiphers = ciphersuite_medium } } }

Other important feature is the listening of the file, which will contain a user input. We will use a fsnotify library for these purposes. Michael Snoyman shared the implementation of this feature (he always flying to help, when SO question contains the haskell and conduit keywords =). The main idea is the monitoring file changes by fsnotify , and save the current position in file. There are several disadvanteges with this approach - e.g. we can’t handle a file truncation. But for our purposes we can use files, that will never be truncated.

sourceFileForever :: MonadResource m => FilePath -> Source m ByteString sourceFileForever fp' = bracketP startManager stopManager $ \ manager -> do fp <- liftIO $ canonicalizePath $ decodeString fp' baton <- liftIO newEmptyMVar liftIO $ watchDir manager ( directory fp ) ( const True ) $ \ event -> void $ tryIO $ do fpE <- canonicalizePath $ case event of Added x _ -> x Modified x _ -> x Removed x _ -> x when ( fpE == fp ) $ putMVar baton () consumedRef <- liftIO $ newIORef 0 loop baton consumedRef where loop :: MonadResource m => MVar () -> IORef Integer -> Source m ByteString loop baton consumedRef = forever $ do consumed <- liftIO $ readIORef consumedRef sourceFileRange fp' ( Just consumed ) Nothing $= CL . iterM counter liftIO $ takeMVar baton where counter bs = liftIO $ modifyIORef consumedRef ( + fromIntegral ( S . length bs ))

xi uses the following algorithm:

establish connection

get a user roster and convert it to the internal representation (the ContactList type)

type) create an appropriate directory structure (a separate directory for each contact with in / out )

/ ) for the each input file start a separate thread to monitoring the user input

start a thread for monitoring the incoming messages

Little bit about client details. A Session and ContactList objects have been shared through the Reader monad. For the parsing of configuration file yaml-config library has been used. Also, there is an ability to see an entire xmpp data flow - this requires only the debug mode in configuration.

Client source code hosted on the github, but you should keep in mind, that it’s more prototype, than a completed project. So if you want to improve something - welcome =)