{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

module Simplex.Messaging.Server.StoreLog
  ( StoreLog, -- constructors are not exported
    openWriteStoreLog,
    openReadStoreLog,
    closeStoreLog,
    logCreateQueue,
    logSecureQueue,
    logDeleteQueue,
    readWriteStoreLog,
  )
where

import Control.Applicative (optional, (<|>))
import Control.Monad (unless)
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Bifunctor (first, second)
import Data.ByteString.Base64 (encode)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Either (partitionEithers)
import Data.Functor (($>))
import Data.List (foldl')
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Parsers (base64P, parseAll)
import Simplex.Messaging.Protocol
import Simplex.Messaging.Server.QueueStore (QueueRec (..), QueueStatus (..))
import Simplex.Messaging.Transport (trimCR)
import System.Directory (doesFileExist)
import System.IO

-- | opaque container for file handle with a type-safe IOMode
-- constructors are not exported, openWriteStoreLog and openReadStoreLog should be used instead
data StoreLog (a :: IOMode) where
  ReadStoreLog :: FilePath -> Handle -> StoreLog 'ReadMode
  WriteStoreLog :: FilePath -> Handle -> StoreLog 'WriteMode

data StoreLogRecord
  = CreateQueue QueueRec
  | SecureQueue QueueId SenderPublicKey
  | DeleteQueue QueueId

storeLogRecordP :: Parser StoreLogRecord
storeLogRecordP :: Parser StoreLogRecord
storeLogRecordP =
  "CREATE " Parser ByteString ByteString
-> Parser StoreLogRecord -> Parser StoreLogRecord
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser StoreLogRecord
createQueueP
    Parser StoreLogRecord
-> Parser StoreLogRecord -> Parser StoreLogRecord
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> "SECURE " Parser ByteString ByteString
-> Parser StoreLogRecord -> Parser StoreLogRecord
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser StoreLogRecord
secureQueueP
    Parser StoreLogRecord
-> Parser StoreLogRecord -> Parser StoreLogRecord
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> "DELETE " Parser ByteString ByteString
-> Parser StoreLogRecord -> Parser StoreLogRecord
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> (ByteString -> StoreLogRecord
DeleteQueue (ByteString -> StoreLogRecord)
-> Parser ByteString ByteString -> Parser StoreLogRecord
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser ByteString ByteString
base64P)
  where
    createQueueP :: Parser StoreLogRecord
createQueueP = QueueRec -> StoreLogRecord
CreateQueue (QueueRec -> StoreLogRecord)
-> Parser ByteString QueueRec -> Parser StoreLogRecord
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser ByteString QueueRec
queueRecP
    secureQueueP :: Parser StoreLogRecord
secureQueueP = ByteString -> SenderPublicKey -> StoreLogRecord
SecureQueue (ByteString -> SenderPublicKey -> StoreLogRecord)
-> Parser ByteString ByteString
-> Parser ByteString (SenderPublicKey -> StoreLogRecord)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser ByteString ByteString
base64P Parser ByteString (SenderPublicKey -> StoreLogRecord)
-> Parser ByteString Char
-> Parser ByteString (SenderPublicKey -> StoreLogRecord)
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString Char
A.space Parser ByteString (SenderPublicKey -> StoreLogRecord)
-> Parser ByteString SenderPublicKey -> Parser StoreLogRecord
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser ByteString SenderPublicKey
C.pubKeyP
    queueRecP :: Parser ByteString QueueRec
queueRecP = do
      ByteString
recipientId <- "rid=" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser ByteString ByteString
base64P Parser ByteString ByteString
-> Parser ByteString Char -> Parser ByteString ByteString
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString Char
A.space
      ByteString
senderId <- "sid=" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser ByteString ByteString
base64P Parser ByteString ByteString
-> Parser ByteString Char -> Parser ByteString ByteString
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString Char
A.space
      SenderPublicKey
recipientKey <- "rk=" Parser ByteString ByteString
-> Parser ByteString SenderPublicKey
-> Parser ByteString SenderPublicKey
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser ByteString SenderPublicKey
C.pubKeyP Parser ByteString SenderPublicKey
-> Parser ByteString Char -> Parser ByteString SenderPublicKey
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString Char
A.space
      Maybe SenderPublicKey
senderKey <- "sk=" Parser ByteString ByteString
-> Parser ByteString (Maybe SenderPublicKey)
-> Parser ByteString (Maybe SenderPublicKey)
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser ByteString SenderPublicKey
-> Parser ByteString (Maybe SenderPublicKey)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser ByteString SenderPublicKey
C.pubKeyP
      QueueRec -> Parser ByteString QueueRec
forall (f :: * -> *) a. Applicative f => a -> f a
pure QueueRec :: ByteString
-> ByteString
-> SenderPublicKey
-> Maybe SenderPublicKey
-> QueueStatus
-> QueueRec
QueueRec {ByteString
recipientId :: ByteString
recipientId :: ByteString
recipientId, ByteString
senderId :: ByteString
senderId :: ByteString
senderId, SenderPublicKey
recipientKey :: SenderPublicKey
recipientKey :: SenderPublicKey
recipientKey, Maybe SenderPublicKey
senderKey :: Maybe SenderPublicKey
senderKey :: Maybe SenderPublicKey
senderKey, status :: QueueStatus
status = QueueStatus
QueueActive}

serializeStoreLogRecord :: StoreLogRecord -> ByteString
serializeStoreLogRecord :: StoreLogRecord -> ByteString
serializeStoreLogRecord = \case
  CreateQueue q :: QueueRec
q -> "CREATE " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> QueueRec -> ByteString
serializeQueue QueueRec
q
  SecureQueue rId :: ByteString
rId sKey :: SenderPublicKey
sKey -> "SECURE " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
encode ByteString
rId ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> " " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> SenderPublicKey -> ByteString
C.serializePubKey SenderPublicKey
sKey
  DeleteQueue rId :: ByteString
rId -> "DELETE " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
encode ByteString
rId
  where
    serializeQueue :: QueueRec -> ByteString
serializeQueue QueueRec {ByteString
recipientId :: ByteString
recipientId :: QueueRec -> ByteString
recipientId, ByteString
senderId :: ByteString
senderId :: QueueRec -> ByteString
senderId, SenderPublicKey
recipientKey :: SenderPublicKey
recipientKey :: QueueRec -> SenderPublicKey
recipientKey, Maybe SenderPublicKey
senderKey :: Maybe SenderPublicKey
senderKey :: QueueRec -> Maybe SenderPublicKey
senderKey} =
      [ByteString] -> ByteString
B.unwords
        [ "rid=" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
encode ByteString
recipientId,
          "sid=" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
encode ByteString
senderId,
          "rk=" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> SenderPublicKey -> ByteString
C.serializePubKey SenderPublicKey
recipientKey,
          "sk=" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
-> (SenderPublicKey -> ByteString)
-> Maybe SenderPublicKey
-> ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe "" SenderPublicKey -> ByteString
C.serializePubKey Maybe SenderPublicKey
senderKey
        ]

openWriteStoreLog :: FilePath -> IO (StoreLog 'WriteMode)
openWriteStoreLog :: FilePath -> IO (StoreLog 'WriteMode)
openWriteStoreLog f :: FilePath
f = FilePath -> Handle -> StoreLog 'WriteMode
WriteStoreLog FilePath
f (Handle -> StoreLog 'WriteMode)
-> IO Handle -> IO (StoreLog 'WriteMode)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> IOMode -> IO Handle
openFile FilePath
f IOMode
WriteMode

openReadStoreLog :: FilePath -> IO (StoreLog 'ReadMode)
openReadStoreLog :: FilePath -> IO (StoreLog 'ReadMode)
openReadStoreLog f :: FilePath
f = do
  FilePath -> IO Bool
doesFileExist FilePath
f IO Bool -> (Bool -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
`unless` FilePath -> FilePath -> IO ()
writeFile FilePath
f "")
  FilePath -> Handle -> StoreLog 'ReadMode
ReadStoreLog FilePath
f (Handle -> StoreLog 'ReadMode)
-> IO Handle -> IO (StoreLog 'ReadMode)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> IOMode -> IO Handle
openFile FilePath
f IOMode
ReadMode

closeStoreLog :: StoreLog a -> IO ()
closeStoreLog :: StoreLog a -> IO ()
closeStoreLog = \case
  WriteStoreLog _ h :: Handle
h -> Handle -> IO ()
hClose Handle
h
  ReadStoreLog _ h :: Handle
h -> Handle -> IO ()
hClose Handle
h

writeStoreLogRecord :: StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord :: StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord (WriteStoreLog _ h :: Handle
h) r :: StoreLogRecord
r = do
  Handle -> ByteString -> IO ()
B.hPutStrLn Handle
h (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ StoreLogRecord -> ByteString
serializeStoreLogRecord StoreLogRecord
r
  Handle -> IO ()
hFlush Handle
h

logCreateQueue :: StoreLog 'WriteMode -> QueueRec -> IO ()
logCreateQueue :: StoreLog 'WriteMode -> QueueRec -> IO ()
logCreateQueue s :: StoreLog 'WriteMode
s = StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord StoreLog 'WriteMode
s (StoreLogRecord -> IO ())
-> (QueueRec -> StoreLogRecord) -> QueueRec -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. QueueRec -> StoreLogRecord
CreateQueue

logSecureQueue :: StoreLog 'WriteMode -> QueueId -> SenderPublicKey -> IO ()
logSecureQueue :: StoreLog 'WriteMode -> ByteString -> SenderPublicKey -> IO ()
logSecureQueue s :: StoreLog 'WriteMode
s qId :: ByteString
qId sKey :: SenderPublicKey
sKey = StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord StoreLog 'WriteMode
s (StoreLogRecord -> IO ()) -> StoreLogRecord -> IO ()
forall a b. (a -> b) -> a -> b
$ ByteString -> SenderPublicKey -> StoreLogRecord
SecureQueue ByteString
qId SenderPublicKey
sKey

logDeleteQueue :: StoreLog 'WriteMode -> QueueId -> IO ()
logDeleteQueue :: StoreLog 'WriteMode -> ByteString -> IO ()
logDeleteQueue s :: StoreLog 'WriteMode
s = StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord StoreLog 'WriteMode
s (StoreLogRecord -> IO ())
-> (ByteString -> StoreLogRecord) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> StoreLogRecord
DeleteQueue

readWriteStoreLog :: StoreLog 'ReadMode -> IO (Map RecipientId QueueRec, StoreLog 'WriteMode)
readWriteStoreLog :: StoreLog 'ReadMode
-> IO (Map ByteString QueueRec, StoreLog 'WriteMode)
readWriteStoreLog s :: StoreLog 'ReadMode
s@(ReadStoreLog f :: FilePath
f _) = do
  Map ByteString QueueRec
qs <- StoreLog 'ReadMode -> IO (Map ByteString QueueRec)
readQueues StoreLog 'ReadMode
s
  StoreLog 'ReadMode -> IO ()
forall (a :: IOMode). StoreLog a -> IO ()
closeStoreLog StoreLog 'ReadMode
s
  StoreLog 'WriteMode
s' <- FilePath -> IO (StoreLog 'WriteMode)
openWriteStoreLog FilePath
f
  StoreLog 'WriteMode -> Map ByteString QueueRec -> IO ()
writeQueues StoreLog 'WriteMode
s' Map ByteString QueueRec
qs
  (Map ByteString QueueRec, StoreLog 'WriteMode)
-> IO (Map ByteString QueueRec, StoreLog 'WriteMode)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Map ByteString QueueRec
qs, StoreLog 'WriteMode
s')

writeQueues :: StoreLog 'WriteMode -> Map RecipientId QueueRec -> IO ()
writeQueues :: StoreLog 'WriteMode -> Map ByteString QueueRec -> IO ()
writeQueues s :: StoreLog 'WriteMode
s = (QueueRec -> IO ()) -> Map ByteString QueueRec -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (StoreLog 'WriteMode -> StoreLogRecord -> IO ()
writeStoreLogRecord StoreLog 'WriteMode
s (StoreLogRecord -> IO ())
-> (QueueRec -> StoreLogRecord) -> QueueRec -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. QueueRec -> StoreLogRecord
CreateQueue) (Map ByteString QueueRec -> IO ())
-> (Map ByteString QueueRec -> Map ByteString QueueRec)
-> Map ByteString QueueRec
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (QueueRec -> Bool)
-> Map ByteString QueueRec -> Map ByteString QueueRec
forall a k. (a -> Bool) -> Map k a -> Map k a
M.filter QueueRec -> Bool
active
  where
    active :: QueueRec -> Bool
active QueueRec {QueueStatus
status :: QueueStatus
status :: QueueRec -> QueueStatus
status} = QueueStatus
status QueueStatus -> QueueStatus -> Bool
forall a. Eq a => a -> a -> Bool
== QueueStatus
QueueActive

type LogParsingError = (String, ByteString)

readQueues :: StoreLog 'ReadMode -> IO (Map RecipientId QueueRec)
readQueues :: StoreLog 'ReadMode -> IO (Map ByteString QueueRec)
readQueues (ReadStoreLog _ h :: Handle
h) = Handle -> IO ByteString
LB.hGetContents Handle
h IO ByteString
-> (ByteString -> IO (Map ByteString QueueRec))
-> IO (Map ByteString QueueRec)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ([LogParsingError], Map ByteString QueueRec)
-> IO (Map ByteString QueueRec)
returnResult (([LogParsingError], Map ByteString QueueRec)
 -> IO (Map ByteString QueueRec))
-> (ByteString -> ([LogParsingError], Map ByteString QueueRec))
-> ByteString
-> IO (Map ByteString QueueRec)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ([LogParsingError], Map ByteString QueueRec)
procStoreLog
  where
    procStoreLog :: LB.ByteString -> ([LogParsingError], Map RecipientId QueueRec)
    procStoreLog :: ByteString -> ([LogParsingError], Map ByteString QueueRec)
procStoreLog = ([StoreLogRecord] -> Map ByteString QueueRec)
-> ([LogParsingError], [StoreLogRecord])
-> ([LogParsingError], Map ByteString QueueRec)
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
second ((Map ByteString QueueRec
 -> StoreLogRecord -> Map ByteString QueueRec)
-> Map ByteString QueueRec
-> [StoreLogRecord]
-> Map ByteString QueueRec
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Map ByteString QueueRec
-> StoreLogRecord -> Map ByteString QueueRec
procLogRecord Map ByteString QueueRec
forall k a. Map k a
M.empty) (([LogParsingError], [StoreLogRecord])
 -> ([LogParsingError], Map ByteString QueueRec))
-> (ByteString -> ([LogParsingError], [StoreLogRecord]))
-> ByteString
-> ([LogParsingError], Map ByteString QueueRec)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Either LogParsingError StoreLogRecord]
-> ([LogParsingError], [StoreLogRecord])
forall a b. [Either a b] -> ([a], [b])
partitionEithers ([Either LogParsingError StoreLogRecord]
 -> ([LogParsingError], [StoreLogRecord]))
-> (ByteString -> [Either LogParsingError StoreLogRecord])
-> ByteString
-> ([LogParsingError], [StoreLogRecord])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ByteString -> Either LogParsingError StoreLogRecord)
-> [ByteString] -> [Either LogParsingError StoreLogRecord]
forall a b. (a -> b) -> [a] -> [b]
map ByteString -> Either LogParsingError StoreLogRecord
parseLogRecord ([ByteString] -> [Either LogParsingError StoreLogRecord])
-> (ByteString -> [ByteString])
-> ByteString
-> [Either LogParsingError StoreLogRecord]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [ByteString]
LB.lines
    returnResult :: ([LogParsingError], Map RecipientId QueueRec) -> IO (Map RecipientId QueueRec)
    returnResult :: ([LogParsingError], Map ByteString QueueRec)
-> IO (Map ByteString QueueRec)
returnResult (errs :: [LogParsingError]
errs, res :: Map ByteString QueueRec
res) = (LogParsingError -> IO ()) -> [LogParsingError] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ LogParsingError -> IO ()
printError [LogParsingError]
errs IO () -> Map ByteString QueueRec -> IO (Map ByteString QueueRec)
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Map ByteString QueueRec
res
    parseLogRecord :: LB.ByteString -> Either LogParsingError StoreLogRecord
    parseLogRecord :: ByteString -> Either LogParsingError StoreLogRecord
parseLogRecord = (\s :: ByteString
s -> (FilePath -> LogParsingError)
-> Either FilePath StoreLogRecord
-> Either LogParsingError StoreLogRecord
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (,ByteString
s) (Either FilePath StoreLogRecord
 -> Either LogParsingError StoreLogRecord)
-> Either FilePath StoreLogRecord
-> Either LogParsingError StoreLogRecord
forall a b. (a -> b) -> a -> b
$ Parser StoreLogRecord
-> ByteString -> Either FilePath StoreLogRecord
forall a. Parser a -> ByteString -> Either FilePath a
parseAll Parser StoreLogRecord
storeLogRecordP ByteString
s) (ByteString -> Either LogParsingError StoreLogRecord)
-> (ByteString -> ByteString)
-> ByteString
-> Either LogParsingError StoreLogRecord
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
trimCR (ByteString -> ByteString)
-> (ByteString -> ByteString) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
LB.toStrict
    procLogRecord :: Map RecipientId QueueRec -> StoreLogRecord -> Map RecipientId QueueRec
    procLogRecord :: Map ByteString QueueRec
-> StoreLogRecord -> Map ByteString QueueRec
procLogRecord m :: Map ByteString QueueRec
m = \case
      CreateQueue q :: QueueRec
q -> ByteString
-> QueueRec -> Map ByteString QueueRec -> Map ByteString QueueRec
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert (QueueRec -> ByteString
recipientId QueueRec
q) QueueRec
q Map ByteString QueueRec
m
      SecureQueue qId :: ByteString
qId sKey :: SenderPublicKey
sKey -> (QueueRec -> QueueRec)
-> ByteString -> Map ByteString QueueRec -> Map ByteString QueueRec
forall k a. Ord k => (a -> a) -> k -> Map k a -> Map k a
M.adjust (\q :: QueueRec
q -> QueueRec
q {senderKey :: Maybe SenderPublicKey
senderKey = SenderPublicKey -> Maybe SenderPublicKey
forall a. a -> Maybe a
Just SenderPublicKey
sKey}) ByteString
qId Map ByteString QueueRec
m
      DeleteQueue qId :: ByteString
qId -> ByteString -> Map ByteString QueueRec -> Map ByteString QueueRec
forall k a. Ord k => k -> Map k a -> Map k a
M.delete ByteString
qId Map ByteString QueueRec
m
    printError :: LogParsingError -> IO ()
    printError :: LogParsingError -> IO ()
printError (e :: FilePath
e, s :: ByteString
s) = ByteString -> IO ()
B.putStrLn (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ "Error parsing log: " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> FilePath -> ByteString
B.pack FilePath
e ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> " - " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
s