packages feed

mattermost-api 40700.0.0 → 40800.0.0

raw patch · 16 files changed

+279/−177 lines, 16 filesdep +resource-poolPVP ok

version bump matches the API change (PVP)

Dependencies added: resource-pool

API changes (from Hackage documentation)

- Network.Mattermost.Util: dropTrailingChar :: ByteString -> ByteString
- Network.Mattermost.Util: instance Network.Stream.Stream Network.Mattermost.Util.MMConn
+ Network.Mattermost: ConnectionPoolConfig :: Int -> NominalDiffTime -> Int -> ConnectionPoolConfig
+ Network.Mattermost: [cpIdleConnTimeout] :: ConnectionPoolConfig -> NominalDiffTime
+ Network.Mattermost: [cpMaxConnCount] :: ConnectionPoolConfig -> Int
+ Network.Mattermost: [cpStripesCount] :: ConnectionPoolConfig -> Int
+ Network.Mattermost: data ConnectionPoolConfig
+ Network.Mattermost: defaultConnectionPoolConfig :: ConnectionPoolConfig
+ Network.Mattermost: mmCloseSession :: Session -> IO ()
+ Network.Mattermost.Lenses: cdConnectionPoolL :: Lens' ConnectionData (Pool MMConn)
+ Network.Mattermost.Types: ConnectionPoolConfig :: Int -> NominalDiffTime -> Int -> ConnectionPoolConfig
+ Network.Mattermost.Types: [cpIdleConnTimeout] :: ConnectionPoolConfig -> NominalDiffTime
+ Network.Mattermost.Types: [cpMaxConnCount] :: ConnectionPoolConfig -> Int
+ Network.Mattermost.Types: [cpStripesCount] :: ConnectionPoolConfig -> Int
+ Network.Mattermost.Types: createPool :: Hostname -> Port -> ConnectionContext -> ConnectionPoolConfig -> IO (Pool MMConn)
+ Network.Mattermost.Types: data ConnectionPoolConfig
+ Network.Mattermost.Types: defaultConnectionPoolConfig :: ConnectionPoolConfig
+ Network.Mattermost.Types: destroyConnectionData :: ConnectionData -> IO ()
+ Network.Mattermost.Types: emptyPostProps :: PostProps
+ Network.Mattermost.Types.Internal: MMConn :: Connection -> IORef Bool -> MMConn
+ Network.Mattermost.Types.Internal: [cdConnectionPool] :: ConnectionData -> Pool MMConn
+ Network.Mattermost.Types.Internal: [connConnected] :: MMConn -> IORef Bool
+ Network.Mattermost.Types.Internal: [fromMMConn] :: MMConn -> Connection
+ Network.Mattermost.Types.Internal: closeMMConn :: MMConn -> IO ()
+ Network.Mattermost.Types.Internal: data MMConn
+ Network.Mattermost.Types.Internal: dropTrailingChar :: ByteString -> ByteString
+ Network.Mattermost.Types.Internal: instance Network.Stream.Stream Network.Mattermost.Types.Internal.MMConn
+ Network.Mattermost.Types.Internal: isConnected :: MMConn -> IO Bool
+ Network.Mattermost.Types.Internal: maxLineLength :: Int
+ Network.Mattermost.Types.Internal: newMMConn :: Connection -> IO MMConn
- Network.Mattermost: initConnectionData :: Hostname -> Port -> IO ConnectionData
+ Network.Mattermost: initConnectionData :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData
- Network.Mattermost: initConnectionDataInsecure :: Hostname -> Port -> IO ConnectionData
+ Network.Mattermost: initConnectionDataInsecure :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData
- Network.Mattermost: mkConnectionData :: Hostname -> Port -> ConnectionContext -> ConnectionData
+ Network.Mattermost: mkConnectionData :: Hostname -> Port -> Pool MMConn -> ConnectionContext -> ConnectionData
- Network.Mattermost.Types: initConnectionData :: Hostname -> Port -> IO ConnectionData
+ Network.Mattermost.Types: initConnectionData :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData
- Network.Mattermost.Types: initConnectionDataInsecure :: Hostname -> Port -> IO ConnectionData
+ Network.Mattermost.Types: initConnectionDataInsecure :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData
- Network.Mattermost.Types: mkConnectionData :: Hostname -> Port -> ConnectionContext -> ConnectionData
+ Network.Mattermost.Types: mkConnectionData :: Hostname -> Port -> Pool MMConn -> ConnectionContext -> ConnectionData
- Network.Mattermost.Types: mkConnectionDataInsecure :: Hostname -> Port -> ConnectionContext -> ConnectionData
+ Network.Mattermost.Types: mkConnectionDataInsecure :: Hostname -> Port -> Pool MMConn -> ConnectionContext -> ConnectionData
- Network.Mattermost.Types.Internal: ConnectionData :: Hostname -> Port -> AutoClose -> ConnectionContext -> Maybe Token -> Maybe Logger -> Bool -> ConnectionData
+ Network.Mattermost.Types.Internal: ConnectionData :: Hostname -> Port -> AutoClose -> Pool MMConn -> ConnectionContext -> Maybe Token -> Maybe Logger -> Bool -> ConnectionData
- Network.Mattermost.Util: mkConnection :: ConnectionData -> IO Connection
+ Network.Mattermost.Util: mkConnection :: ConnectionContext -> Hostname -> Port -> Bool -> IO MMConn

Files

CHANGELOG.md view
@@ -1,4 +1,14 @@ +40800.0.0+=========++ * Fixed the URI for the mmUnflagPost API call.+ * Some JSON instances now more precisely handle missing optional+   fields.+ * mattermost-api now supports connection pooling with persistent server+   connections. A connection pool configuration is required to connect+   to Mattermost servers (thanks to Abhinav Sarkar)+ 40700.0.0 ========= 
examples/GetChannels.hs view
@@ -21,9 +21,8 @@ main :: IO () main = do   config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd = mkConnectionData (configHostname config)-                            (fromIntegral (configPort config)) ctx+  cd <- initConnectionData (configHostname config)+                           (fromIntegral (configPort config)) defaultConnectionPoolConfig    let login = Login { username = configUsername config                     , password = configPassword config
examples/GetPosts.hs view
@@ -98,13 +98,11 @@   opts <- foldl (>>=) (return defaultOptions) actions    config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd'      = mkConnectionData (configHostname config)-                                 (fromIntegral (configPort config))-                                 ctx-      login   = Login { username = configUsername config-                      , password = configPassword config-                      }+  cd'    <- initConnectionData (configHostname config) (fromIntegral (configPort config))+                               defaultConnectionPoolConfig+  let login = Login { username = configUsername config+                    , password = configPassword config+                    }       cd = if optLogging opts              then cd' `withLogger` mmLoggerDebugErr              else cd'
examples/GetTeams.hs view
@@ -18,10 +18,9 @@ main :: IO () main = do   config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd = mkConnectionData (configHostname config)-                            (fromIntegral (configPort config))-                            ctx+  cd <- initConnectionData (configHostname config) (fromIntegral (configPort config))+                           defaultConnectionPoolConfig+   let login = Login { username = configUsername config                     , password = configPassword config                     }
examples/GetWebsocketConnection.hs view
@@ -53,13 +53,12 @@   opts <- foldl (>>=) (return defaultOptions) actions    config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd      = mkConnectionData (configHostname config)-                                 (fromIntegral (configPort config))-                                 ctx-      login   = Login { username = configUsername config-                      , password = configPassword config-                      }+  cd <- initConnectionData (configHostname config) (fromIntegral (configPort config))+                           defaultConnectionPoolConfig++  let login = Login { username = configUsername config+                    , password = configPassword config+                    }    (session, mmUser) <- join (hoistE <$> mmLogin cd login)   when (optVerbose opts) $ do
examples/MakePost.hs view
@@ -74,13 +74,12 @@   opts <- foldl (>>=) (return defaultOptions) actions    config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd      = mkConnectionData (configHostname config)-                                 (fromIntegral (configPort config))-                                 ctx-      login   = Login { username = configUsername config-                      , password = configPassword config-                      }+  cd <- initConnectionData (configHostname config) (fromIntegral (configPort config))+                           defaultConnectionPoolConfig++  let login = Login { username = configUsername config+                    , password = configPassword config+                    }    (session, mmUser) <- join (hoistE <$> mmLogin cd login)   when (optVerbose opts) $ do
examples/ShowRawEvents.hs view
@@ -63,13 +63,12 @@   opts <- foldl (>>=) (return defaultOptions) actions    config <- getConfig -- see LocalConfig import-  ctx    <- initConnectionContext-  let cd      = mkConnectionData (configHostname config)-                  (fromIntegral (configPort config))-                  ctx-      login   = Login { username = configUsername config-                      , password = configPassword config-                      }+  cd     <- initConnectionData (configHostname config) (fromIntegral (configPort config))+                               defaultConnectionPoolConfig++  let login = Login { username = configUsername config+                    , password = configPassword config+                    }    (session, mmUser) <- join (hoistE <$> mmLogin cd login)   when (optVerbose opts) $ do
mattermost-api.cabal view
@@ -1,5 +1,5 @@ name:                mattermost-api-version:             40700.0.0+version:             40800.0.0 synopsis:            Client API for Mattermost chat system description:         Client API for Mattermost chat system license:             BSD3@@ -45,6 +45,7 @@                      , aeson >= 1.0.0.0                      , connection                      , memory <0.14.3+                     , resource-pool >= 0.2.3                      -- To prevent broken websockets versions from using                      -- incompatible versions of binary (for details, see                      -- https://github.com/matterhorn-chat/mattermost-api/issues/36):
src/Network/Mattermost.hs view
@@ -6,7 +6,8 @@   -- ** Mattermost-Related Types (deprecated: use Network.Mattermost.Types instead)   -- n.b. the deprecation notice is in that haddock header because we're   -- still waiting for https://ghc.haskell.org/trac/ghc/ticket/4879 ...-  Login(..)+  ConnectionPoolConfig(..)+, Login(..) , Hostname , Port , ConnectionData@@ -55,9 +56,11 @@ -- * Typeclasses , HasId(..) -- * HTTP API Functions+, defaultConnectionPoolConfig , mkConnectionData , initConnectionData , initConnectionDataInsecure+, mmCloseSession , mmLogin , mmCreateDirect , mmCreateChannel@@ -1079,3 +1082,6 @@                         in throwIO $ MattermostServerError newMsg                     _ -> throwIO $ httpExc             _ -> throwIO $ httpExc++mmCloseSession :: Session -> IO ()+mmCloseSession (Session cd _) = destroyConnectionData cd
src/Network/Mattermost/Connection.hs view
@@ -1,17 +1,23 @@+{-# LANGUAGE ScopedTypeVariables #-} module Network.Mattermost.Connection where   import           Control.Arrow (left)-import           Control.Exception (throwIO)+import           Control.Exception (throwIO, IOException, try, throwIO)+import           Control.Monad (when)+import           Data.Monoid ((<>))+import           Data.Pool (destroyAllResources) import qualified Data.Aeson as A import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL+import           Data.Char (toLower) import qualified Data.List as List import qualified Data.Text as T import qualified Network.HTTP.Base as HTTP import qualified Network.HTTP.Headers as HTTP import qualified Network.HTTP.Stream as HTTP import qualified Network.URI as URI+import           System.IO.Error (isEOFError)  import Network.Mattermost.Exceptions import Network.Mattermost.Types@@ -73,25 +79,73 @@     y <- value     return (y) -doRequest :: HTTP.RequestMethod -> String -> B.ByteString -> Session -> IO HTTP.Response_String-doRequest method uri payload (Session cd token) = do+doRequest :: Session+          -> HTTP.RequestMethod+          -> String+          -> B.ByteString+          -> IO HTTP.Response_String+doRequest (Session cd token) = submitRequest cd (Just token)++doUnauthRequest :: ConnectionData+                -> HTTP.RequestMethod+                -> String+                -> B.ByteString+                -> IO HTTP.Response_String+doUnauthRequest cd = submitRequest cd Nothing++submitRequest :: ConnectionData+              -> Maybe Token+              -> HTTP.RequestMethod+              -> String+              -> B.ByteString+              -> IO HTTP.Response_String+submitRequest cd mToken method uri payload = do   path <- mmPath ("/api/v4" ++ uri)-  rawResponse <- withConnection cd $ \con -> do-    let contentLength = B.length payload-        request = HTTP.Request-          { HTTP.rqURI = path-          , HTTP.rqMethod = method-          , HTTP.rqHeaders =-            [ HTTP.mkHeader HTTP.HdrAuthorization ("Bearer " ++ getTokenString token)-            , HTTP.mkHeader HTTP.HdrHost          (T.unpack $ cdHostname cd)-            , HTTP.mkHeader HTTP.HdrUserAgent     HTTP.defaultUserAgent-            , HTTP.mkHeader HTTP.HdrContentType   "application/json"-            , HTTP.mkHeader HTTP.HdrContentLength (show contentLength)-            ] ++ autoCloseToHeader (cdAutoClose cd)-          , HTTP.rqBody    = B.unpack payload-          }-    runLogger cd "doRequest" (HttpRequest method uri Nothing)-    HTTP.simpleHTTP_ con request+  let contentLength = B.length payload+      authHeader =+          case mToken of+              Nothing -> []+              Just token -> [HTTP.mkHeader HTTP.HdrAuthorization ("Bearer " ++ getTokenString token)]++      request = HTTP.Request+        { HTTP.rqURI = path+        , HTTP.rqMethod = method+        , HTTP.rqHeaders =+          authHeader <>+          [ HTTP.mkHeader HTTP.HdrHost          (T.unpack $ cdHostname cd)+          , HTTP.mkHeader HTTP.HdrUserAgent     HTTP.defaultUserAgent+          , HTTP.mkHeader HTTP.HdrContentType   "application/json"+          , HTTP.mkHeader HTTP.HdrContentLength (show contentLength)+          ] ++ autoCloseToHeader (cdAutoClose cd)+        , HTTP.rqBody    = B.unpack payload+        }++      go = withConnection cd $ \con -> do+          runLogger cd "submitRequest" (HttpRequest method uri Nothing)+          result <- HTTP.simpleHTTP_ con request+          case result of+              Left e -> return $ Left e+              Right response -> do+                  when (shouldClose response) $ closeMMConn con+                  return $ Right response++  rawResponse <- do+      -- Try to submit the request. If we got an EOF exception, that+      -- means that the connection pool contained a connection that+      -- had been severed since it was last used. That means it's+      -- very likely that the pool has other stale connections in it,+      -- so we destroy all idle connections in the pool and try the+      -- request one more time. All other errors and exceptions are just+      -- propagated.+      resp :: Either IOException (Either HTTP.ConnError HTTP.Response_String)+           <- try go+      case resp of+          Left e | isEOFError e -> do+              destroyAllResources (cdConnectionPool cd)+              go+          Left e -> throwIO e+          Right result -> return result+   rsp <- hoistE (left ConnectionException rawResponse)   case HTTP.rspCode rsp of     (2, _, _) -> return rsp@@ -102,6 +156,11 @@         Left _ ->           throwIO (HTTPResponseException ("Server returned unexpected " ++ show code ++ " response")) +shouldClose :: HTTP.Response_String -> Bool+shouldClose r =+    let isConnClose (HTTP.Header HTTP.HdrConnection v) = (toLower <$> v) == "close"+        isConnClose _ = False+    in any isConnClose $ HTTP.rspHeaders r  mkQueryString :: [Maybe (String, String)] -> String mkQueryString ls =@@ -121,7 +180,7 @@   -> Session   -> IO o inPost uri payload k session =-  doRequest HTTP.POST uri payload session >>= k+  doRequest session HTTP.POST uri payload >>= k  inPut   :: String@@ -130,7 +189,7 @@   -> Session   -> IO o inPut uri payload k session =-  doRequest HTTP.PUT uri payload session >>= k+  doRequest session HTTP.PUT uri payload >>= k  inGet   :: String@@ -139,7 +198,7 @@   -> Session   -> IO o inGet uri payload k session =-  doRequest HTTP.GET uri payload session >>= k+  doRequest session HTTP.GET uri payload >>= k  inDelete   :: String@@ -148,28 +207,4 @@   -> Session   -> IO o inDelete uri payload k session =-  doRequest HTTP.DELETE uri payload session >>= k----doUnauthRequest :: HTTP.RequestMethod -> String -> B.ByteString -> ConnectionData -> IO HTTP.Response_String-doUnauthRequest method uri payload cd = do-  path <- mmPath ("/api/v4" ++ uri)-  rawResponse <- withConnection cd $ \con -> do-    let contentLength = B.length payload-        request = HTTP.Request-          { HTTP.rqURI = path-          , HTTP.rqMethod = method-          , HTTP.rqHeaders =-            [ HTTP.mkHeader HTTP.HdrHost          (T.unpack $ cdHostname cd)-            , HTTP.mkHeader HTTP.HdrUserAgent     HTTP.defaultUserAgent-            , HTTP.mkHeader HTTP.HdrContentType   "application/json"-            , HTTP.mkHeader HTTP.HdrContentLength (show contentLength)-            ] ++ autoCloseToHeader (cdAutoClose cd)-          , HTTP.rqBody    = B.unpack payload-          }-    HTTP.simpleHTTP_ con request-  rsp <- hoistE (left ConnectionException rawResponse)-  case HTTP.rspCode rsp of-    (2, _, _) -> return rsp-    code -> throwIO (HTTPResponseException ("Server returned unexpected " ++ show code ++ " response"))+  doRequest session HTTP.DELETE uri payload >>= k
src/Network/Mattermost/Endpoints.hs view
@@ -23,7 +23,7 @@  mmLogin :: ConnectionData -> Login -> IO (Either LoginFailureException (Session, User)) mmLogin cd login = do-  rsp <- doUnauthRequest HTTP.POST "/users/login" (jsonBody login) cd+  rsp <- doUnauthRequest cd HTTP.POST "/users/login" (jsonBody login)   case HTTP.rspCode rsp of     (2, _, _) -> do       token <- mmGetHeader rsp (HTTP.HdrCustom "Token")@@ -35,7 +35,7 @@  mmInitialUser :: ConnectionData -> UsersCreate -> IO User mmInitialUser cd users = do-  rsp <- doUnauthRequest HTTP.POST "/users" (jsonBody users) cd+  rsp <- doUnauthRequest cd HTTP.POST "/users" (jsonBody users)   case HTTP.rspCode rsp of     (2, _, _) -> mmGetJSONBody "User" rsp     _ -> error ("Server returned unexpected " ++ show (HTTP.rspCode rsp) ++ " response")@@ -4011,4 +4011,4 @@         , flaggedPostId     = pId         , flaggedPostStatus = False         }-  in inPut (printf "/users/%s/preferences" uId) (jsonBody [body]) noResponse+  in inPost (printf "/users/%s/preferences/delete" uId) (jsonBody [body]) noResponse
src/Network/Mattermost/Types.hs view
@@ -24,18 +24,24 @@                                   , typeMismatch                                   ) import qualified Data.HashMap.Strict as HM+import           Data.Maybe (fromMaybe) import           Data.Monoid ( (<>) )+import qualified Data.Pool as Pool import           Data.Ratio ( (%) ) import           Data.Sequence (Seq) import qualified Data.Sequence as S+import           Data.Time (NominalDiffTime) import           Data.Text (Text) import qualified Data.Text as T import           Data.Time.Clock ( getCurrentTime ) import           Data.Time.Clock.POSIX ( posixSecondsToUTCTime                                        , utcTimeToPOSIXSeconds )-import           Network.Connection (ConnectionContext, initConnectionContext)+import           Network.Connection ( ConnectionContext+                                    , initConnectionContext+                                    ) import           Network.Mattermost.Types.Base import           Network.Mattermost.Types.Internal+import           Network.Mattermost.Util (mkConnection)  runLogger :: ConnectionData -> String -> LogEventType -> IO () runLogger ConnectionData { cdLogger = Just l } n ev =@@ -49,45 +55,66 @@ maybeFail p = (Just <$> p) <|> (return Nothing)  -- | Creates a structure representing a TLS connection to the server.-mkConnectionData :: Hostname -> Port -> ConnectionContext -> ConnectionData-mkConnectionData host port ctx = ConnectionData-  { cdHostname      = host-  , cdPort          = port-  , cdConnectionCtx = ctx-  , cdAutoClose     = Yes-  , cdToken         = Nothing-  , cdLogger        = Nothing-  , cdUseTLS        = True+mkConnectionData :: Hostname -> Port -> Pool.Pool MMConn -> ConnectionContext -> ConnectionData+mkConnectionData host port pool ctx = ConnectionData+  { cdHostname       = host+  , cdPort           = port+  , cdConnectionCtx  = ctx+  , cdAutoClose      = No+  , cdConnectionPool = pool+  , cdToken          = Nothing+  , cdLogger         = Nothing+  , cdUseTLS         = True   }  -- | Plaintext HTTP instead of a TLS connection.-mkConnectionDataInsecure :: Hostname -> Port -> ConnectionContext -> ConnectionData-mkConnectionDataInsecure host port ctx = ConnectionData-  { cdHostname      = host-  , cdPort          = port-  , cdConnectionCtx = ctx-  , cdAutoClose     = Yes-  , cdToken         = Nothing-  , cdLogger        = Nothing-  , cdUseTLS        = False+mkConnectionDataInsecure :: Hostname -> Port -> Pool.Pool MMConn -> ConnectionContext -> ConnectionData+mkConnectionDataInsecure host port pool ctx = ConnectionData+  { cdHostname       = host+  , cdPort           = port+  , cdConnectionCtx  = ctx+  , cdAutoClose      = No+  , cdConnectionPool = pool+  , cdToken          = Nothing+  , cdLogger         = Nothing+  , cdUseTLS         = False   } -initConnectionData :: Hostname -> Port -> IO ConnectionData-initConnectionData host port = do-  ctx <- initConnectionContext-  return (mkConnectionData host port ctx)+createPool :: Hostname -> Port -> ConnectionContext -> ConnectionPoolConfig -> IO (Pool.Pool MMConn)+createPool host port ctx cpc =+  Pool.createPool (mkConnection ctx host port True) closeMMConn+                  (cpStripesCount cpc) (cpIdleConnTimeout cpc) (cpMaxConnCount cpc) -initConnectionDataInsecure :: Hostname -> Port -> IO ConnectionData-initConnectionDataInsecure host port = do-  ctx <- initConnectionContext-  return (mkConnectionDataInsecure host port ctx)+initConnectionData :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData+initConnectionData host port cpc = do+  ctx  <- initConnectionContext+  pool <- createPool host port ctx cpc+  return (mkConnectionData host port pool ctx) +initConnectionDataInsecure :: Hostname -> Port -> ConnectionPoolConfig -> IO ConnectionData+initConnectionDataInsecure host port cpc = do+  ctx  <- initConnectionContext+  pool <- createPool host port ctx cpc+  return (mkConnectionDataInsecure host port pool ctx)++destroyConnectionData :: ConnectionData -> IO ()+destroyConnectionData = Pool.destroyAllResources . cdConnectionPool+ withLogger :: ConnectionData -> Logger -> ConnectionData withLogger cd logger = cd { cdLogger = Just logger }  noLogger :: ConnectionData -> ConnectionData noLogger cd = cd { cdLogger = Nothing } +data ConnectionPoolConfig = ConnectionPoolConfig+  { cpStripesCount    :: Int+  , cpIdleConnTimeout :: NominalDiffTime+  , cpMaxConnCount    :: Int+  }++defaultConnectionPoolConfig :: ConnectionPoolConfig+defaultConnectionPoolConfig = ConnectionPoolConfig 1 30 5+ data Session = Session   { sessConn :: ConnectionData   , sessTok  :: Token@@ -649,6 +676,16 @@   , postPropsOldHeader        :: Maybe Text   } deriving (Read, Show, Eq) +emptyPostProps :: PostProps+emptyPostProps+  = PostProps+  { postPropsOverrideIconUrl  = Nothing+  , postPropsOverrideUsername = Nothing+  , postPropsAttachments      = Nothing+  , postPropsNewHeader        = Nothing+  , postPropsOldHeader        = Nothing+  }+ instance A.FromJSON PostProps where   parseJSON = A.withObject "Props" $ \v -> do     postPropsOverrideIconUrl  <- v .:? "override_icon_url"@@ -757,9 +794,9 @@   parseJSON = A.withObject "Post" $ \v -> do     postPendingPostId <- maybeFail (v .: "pending_post_id")     postOriginalId    <- maybeFail (v .: "original_id")-    postProps         <- v .: "props"+    postProps         <- fromMaybe emptyPostProps <$> v .: "props"     postRootId        <- maybeFail (v .: "root_id")-    postFileIds       <- (v .: "file_ids") <|> (return mempty)+    postFileIds       <- v .:? "file_ids" .!= mempty     postId            <- v .: "id"     postType          <- v .: "type"     postMessage       <- v .: "message"@@ -771,7 +808,7 @@     postCreateAt      <- timeFromServer <$> v .: "create_at"     postParentId      <- maybeFail (v .: "parent_id")     postChannelId     <- v .: "channel_id"-    postHasReactions  <- (v .: "has_reactions") <|> (return False)+    postHasReactions  <- v .:? "has_reactions" .!= False     return Post { .. }  instance A.ToJSON Post where@@ -880,7 +917,7 @@     fileInfoMimeType   <- o .: "mime_type"     fileInfoWidth      <- o .:? "width"     fileInfoHeight     <- o .:? "height"-    fileInfoHasPreview <- (o .: "has_preview_image") <|> pure False+    fileInfoHasPreview <- o .:? "has_preview_image" .!= False     return FileInfo { .. }  --
src/Network/Mattermost/Types/Internal.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-} -- | The types defined in this module are exported to facilitate -- efforts such as QuickCheck and other instrospection efforts, but -- users are advised to avoid using these types wherever possible:@@ -7,8 +9,14 @@  module Network.Mattermost.Types.Internal where -import Network.Connection (ConnectionContext)+import Control.Monad (when)+import Data.Pool (Pool)+import qualified Network.Connection as C+import Control.Exception (finally)+import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Network.HTTP.Headers (Header, HeaderName(..), mkHeader)+import qualified Network.HTTP.Stream as HTTP+import qualified Data.ByteString.Char8 as B import Network.Mattermost.Types.Base  data Token = Token String@@ -17,10 +25,6 @@ getTokenString :: Token -> String getTokenString (Token s) = s --- For now we don't support or expose the ability to reuse connections,--- but we have this field in case we want to support that in the future.--- Doing so will require some modifications to withConnection (and uses).--- Note: don't export this until we support connection reuse. data AutoClose = No | Yes   deriving (Read, Show, Eq, Ord) @@ -30,14 +34,52 @@ autoCloseToHeader No  = [] autoCloseToHeader Yes = [mkHeader HdrConnection "Close"] +data MMConn = MMConn { fromMMConn :: C.Connection+                     , connConnected :: IORef Bool+                     } +closeMMConn :: MMConn -> IO ()+closeMMConn c = do+    conn <- readIORef $ connConnected c+    when conn $+        C.connectionClose (fromMMConn c)+            `finally` (writeIORef (connConnected c) False)++newMMConn :: C.Connection -> IO MMConn+newMMConn c = do+    v <- newIORef True+    return $ MMConn c v++isConnected :: MMConn -> IO Bool+isConnected = readIORef . connConnected++maxLineLength :: Int+maxLineLength = 2^(16::Int)++-- | HTTP ends newlines with \r\n sequence, but the 'connection' package doesn't+-- know this so we need to drop the \r after reading lines. This should only be+-- needed in your compatibility with the HTTP library.+dropTrailingChar :: B.ByteString -> B.ByteString+dropTrailingChar bs | not (B.null bs) = B.init bs+dropTrailingChar _ = ""++-- | This instance allows us to use 'simpleHTTP' from 'Network.HTTP.Stream' with+-- connections from the 'connection' package.+instance HTTP.Stream MMConn where+  readLine   con       = Right . B.unpack . dropTrailingChar <$> C.connectionGetLine maxLineLength (fromMMConn con)+  readBlock  con n     = Right . B.unpack <$> C.connectionGetExact (fromMMConn con) n+  writeBlock con block = Right <$> C.connectionPut (fromMMConn con) (B.pack block)+  close      con       = C.connectionClose (fromMMConn con)+  closeOnEnd _   _     = return ()+ data ConnectionData   = ConnectionData-  { cdHostname      :: Hostname-  , cdPort          :: Port-  , cdAutoClose     :: AutoClose-  , cdConnectionCtx :: ConnectionContext-  , cdToken         :: Maybe Token-  , cdLogger        :: Maybe Logger-  , cdUseTLS        :: Bool+  { cdHostname       :: Hostname+  , cdPort           :: Port+  , cdAutoClose      :: AutoClose+  , cdConnectionPool :: Pool MMConn+  , cdConnectionCtx  :: C.ConnectionContext+  , cdToken          :: Maybe Token+  , cdLogger         :: Maybe Logger+  , cdUseTLS         :: Bool   }
src/Network/Mattermost/Util.hs view
@@ -6,29 +6,27 @@ , noteE , hoistE , (~=)-, dropTrailingChar , withConnection , mkConnection , connectionGetExact ) where +import           Control.Exception (finally, onException) import           Data.Char ( toUpper ) import qualified Data.ByteString.Char8 as B import qualified Data.Text as T  import           Control.Exception ( Exception-                                   , throwIO-                                   , bracket )+                                   , throwIO )+import           Data.Pool (takeResource, putResource, destroyResource) import           Network.Connection ( Connection+                                    , ConnectionContext                                     , ConnectionParams(..)                                     , TLSSettings(..)                                     , connectionGet-                                    , connectionGetLine-                                    , connectionPut-                                    , connectionClose                                     , connectTo )-import qualified Network.HTTP.Stream as HTTP +import           Network.Mattermost.Types.Base import           Network.Mattermost.Types.Internal  -- | This unwraps a 'Maybe' value, throwing a provided exception@@ -53,49 +51,29 @@ (~=) :: String -> String -> Bool a ~= b = map toUpper a == map toUpper b --- | HTTP ends newlines with \r\n sequence, but the 'connection' package doesn't--- know this so we need to drop the \r after reading lines. This should only be--- needed in your compatibility with the HTTP library.-dropTrailingChar :: B.ByteString -> B.ByteString-dropTrailingChar bs | not (B.null bs) = B.init bs-dropTrailingChar _ = ""---- | Creates a new connection to 'Hostname' from an already initialized 'ConnectionContext'.--- Internally it uses 'bracket' to cleanup the connection.+-- | Creates a new connection to 'Hostname' from an already initialized+-- 'ConnectionContext'. withConnection :: ConnectionData -> (MMConn -> IO a) -> IO a-withConnection cd action =-  bracket (MMConn <$> mkConnection cd)-          (connectionClose . fromMMConn)-          action--maxLineLength :: Int-maxLineLength = 2^(16::Int)--newtype MMConn = MMConn { fromMMConn :: Connection }---- | This instance allows us to use 'simpleHTTP' from 'Network.HTTP.Stream' with--- connections from the 'connection' package.-instance HTTP.Stream MMConn where-  readLine   con       = Right . B.unpack . dropTrailingChar <$> connectionGetLine maxLineLength (fromMMConn con)-  readBlock  con n     = Right . B.unpack <$> connectionGetExact (fromMMConn con) n-  writeBlock con block = Right <$> connectionPut (fromMMConn con) (B.pack block)-  close      con       = connectionClose (fromMMConn con)-  closeOnEnd _   _     = return ()-+withConnection cd action = do+    (conn, lp) <- takeResource (cdConnectionPool cd)+    (action conn `onException` closeMMConn conn) `finally` do+        c <- isConnected conn+        if c then+             putResource lp conn else+             destroyResource (cdConnectionPool cd) lp conn  -- | Creates a connection from a 'ConnectionData' value, returning it. It --   is the user's responsibility to close this appropriately.-mkConnection :: ConnectionData -> IO Connection-mkConnection cd = do-  connectTo (cdConnectionCtx cd) $ ConnectionParams-    { connectionHostname  = T.unpack $ cdHostname cd-    , connectionPort      = fromIntegral (cdPort cd)-    , connectionUseSecure = if cdUseTLS cd+mkConnection :: ConnectionContext -> Hostname -> Port -> Bool -> IO MMConn+mkConnection connectionCtx hostname port useTLS = do+  newMMConn =<< (connectTo connectionCtx $ ConnectionParams+    { connectionHostname  = T.unpack hostname+    , connectionPort      = fromIntegral port+    , connectionUseSecure = if useTLS                                then Just (TLSSettingsSimple False False False)                                else Nothing     , connectionUseSocks  = Nothing-    }-+    })  -- | Get exact count of bytes from a connection. --
src/Network/Mattermost/WebSocket.hs view
@@ -110,8 +110,8 @@                 -> (MMWebSocket -> IO ())                 -> IO () mmWithWebSocket (Session cd (Token tk)) recv body = do-  con <- mkConnection cd-  stream <- connectionToStream con+  con <- mkConnection (cdConnectionCtx cd) (cdHostname cd) (cdPort cd) (cdUseTLS cd)+  stream <- connectionToStream $ fromMMConn con   health <- newIORef 0   myId <- myThreadId   let doLog = runLogger cd "websocket"
test/Tests/Util.hs view
@@ -330,8 +330,8 @@  connectFromConfig :: TestConfig -> IO ConnectionData connectFromConfig cfg =-  initConnectionDataInsecure (configHostname cfg)-                             (fromIntegral (configPort cfg))+  initConnectionDataInsecure (configHostname cfg) (fromIntegral (configPort cfg))+                             defaultConnectionPoolConfig  getConnection :: TestM ConnectionData getConnection = gets tsConnectionData