chat item status, CRChatItemUpdated api response (#269)
This commit is contained in:
parent
eeea33c7cb
commit
f5507436f3
@ -3,7 +3,7 @@ packages: .
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: git://github.com/simplex-chat/simplexmq.git
|
location: git://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 137ff7043d49feb3b350f56783c9b64a62bc636a
|
tag: c9994c3a2ca945b9b67e250163cf8d560d2ed554
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"git://github.com/simplex-chat/simplexmq.git"."137ff7043d49feb3b350f56783c9b64a62bc636a" = "1jlxpmg40qkvisbf03082yrw6k2ah9dsw8pn1jqc0cyz5250qc49";
|
"git://github.com/simplex-chat/simplexmq.git"."c9994c3a2ca945b9b67e250163cf8d560d2ed554" = "0lc4jb46ys0hllv5p3i3x2rw8j4s8xxmz66kp893a23ki68ljyhp";
|
||||||
"git://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
"git://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
||||||
"git://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
"git://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
||||||
"git://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";
|
"git://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";
|
||||||
|
@ -26,6 +26,7 @@ library
|
|||||||
Simplex.Chat.Messages
|
Simplex.Chat.Messages
|
||||||
Simplex.Chat.Migrations.M20220101_initial
|
Simplex.Chat.Migrations.M20220101_initial
|
||||||
Simplex.Chat.Migrations.M20220122_v1_1
|
Simplex.Chat.Migrations.M20220122_v1_1
|
||||||
|
Simplex.Chat.Migrations.M20220205_chat_item_status
|
||||||
Simplex.Chat.Mobile
|
Simplex.Chat.Mobile
|
||||||
Simplex.Chat.Options
|
Simplex.Chat.Options
|
||||||
Simplex.Chat.Protocol
|
Simplex.Chat.Protocol
|
||||||
|
@ -51,7 +51,7 @@ import qualified Simplex.Messaging.Crypto as C
|
|||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (parseAll)
|
import Simplex.Messaging.Parsers (parseAll)
|
||||||
import Simplex.Messaging.Protocol (MsgBody)
|
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
|
||||||
import qualified Simplex.Messaging.Protocol as SMP
|
import qualified Simplex.Messaging.Protocol as SMP
|
||||||
import Simplex.Messaging.Util (tryError)
|
import Simplex.Messaging.Util (tryError)
|
||||||
import System.Exit (exitFailure, exitSuccess)
|
import System.Exit (exitFailure, exitSuccess)
|
||||||
@ -73,9 +73,11 @@ defaultChatConfig =
|
|||||||
{ tcpPort = undefined, -- agent does not listen to TCP
|
{ tcpPort = undefined, -- agent does not listen to TCP
|
||||||
smpServers = undefined, -- filled in from options
|
smpServers = undefined, -- filled in from options
|
||||||
dbFile = undefined, -- filled in from options
|
dbFile = undefined, -- filled in from options
|
||||||
dbPoolSize = 1
|
dbPoolSize = 1,
|
||||||
|
yesToMigrations = False
|
||||||
},
|
},
|
||||||
dbPoolSize = 1,
|
dbPoolSize = 1,
|
||||||
|
yesToMigrations = False,
|
||||||
tbqSize = 16,
|
tbqSize = 16,
|
||||||
fileChunkSize = 15780
|
fileChunkSize = 15780
|
||||||
}
|
}
|
||||||
@ -218,7 +220,7 @@ processChatCommand = \case
|
|||||||
deleteConnection a (aConnId conn) `catchError` \(_ :: AgentErrorType) -> pure ()
|
deleteConnection a (aConnId conn) `catchError` \(_ :: AgentErrorType) -> pure ()
|
||||||
withStore $ \st -> deleteUserContactLink st userId
|
withStore $ \st -> deleteUserContactLink st userId
|
||||||
pure CRUserContactLinkDeleted
|
pure CRUserContactLinkDeleted
|
||||||
ShowMyAddress -> CRUserContactLink <$> (withUser $ \User {userId} -> withStore (`getUserContactLink` userId))
|
ShowMyAddress -> CRUserContactLink <$> withUser (\User {userId} -> withStore (`getUserContactLink` userId))
|
||||||
AcceptContact cName -> withUser $ \User {userId} -> do
|
AcceptContact cName -> withUser $ \User {userId} -> do
|
||||||
connReqId <- withStore $ \st -> getContactRequestIdByName st userId cName
|
connReqId <- withStore $ \st -> getContactRequestIdByName st userId cName
|
||||||
processChatCommand $ APIAcceptContact connReqId
|
processChatCommand $ APIAcceptContact connReqId
|
||||||
@ -298,7 +300,7 @@ processChatCommand = \case
|
|||||||
mapM_ deleteMemberConnection members
|
mapM_ deleteMemberConnection members
|
||||||
withStore $ \st -> deleteGroup st user g
|
withStore $ \st -> deleteGroup st user g
|
||||||
pure $ CRGroupDeletedUser gInfo
|
pure $ CRGroupDeletedUser gInfo
|
||||||
ListMembers gName -> CRGroupMembers <$> (withUser $ \user -> withStore (\st -> getGroupByName st user gName))
|
ListMembers gName -> CRGroupMembers <$> withUser (\user -> withStore (\st -> getGroupByName st user gName))
|
||||||
ListGroups -> CRGroupsList <$> withUser (\user -> withStore (`getUserGroupDetails` user))
|
ListGroups -> CRGroupsList <$> withUser (\user -> withStore (`getUserGroupDetails` user))
|
||||||
SendGroupMessage gName msg -> withUser $ \user -> do
|
SendGroupMessage gName msg -> withUser $ \user -> do
|
||||||
groupId <- withStore $ \st -> getGroupIdByName st user gName
|
groupId <- withStore $ \st -> getGroupIdByName st user gName
|
||||||
@ -312,7 +314,7 @@ processChatCommand = \case
|
|||||||
SndFileTransfer {fileId} <- withStore $ \st ->
|
SndFileTransfer {fileId} <- withStore $ \st ->
|
||||||
createSndFileTransfer st userId contact f fileInv agentConnId chSize
|
createSndFileTransfer st userId contact f fileInv agentConnId chSize
|
||||||
ci <- sendDirectChatItem userId contact (XFile fileInv) (CISndFileInvitation fileId f)
|
ci <- sendDirectChatItem userId contact (XFile fileInv) (CISndFileInvitation fileId f)
|
||||||
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId ci
|
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId' ci
|
||||||
setActive $ ActiveC cName
|
setActive $ ActiveC cName
|
||||||
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat contact) ci
|
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat contact) ci
|
||||||
SendGroupFile gName f -> withUser $ \user@User {userId} -> withChatLock $ do
|
SendGroupFile gName f -> withUser $ \user@User {userId} -> withChatLock $ do
|
||||||
@ -546,7 +548,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
processDirectMessage :: ACommand 'Agent -> Connection -> Maybe Contact -> m ()
|
processDirectMessage :: ACommand 'Agent -> Connection -> Maybe Contact -> m ()
|
||||||
processDirectMessage agentMsg conn = \case
|
processDirectMessage agentMsg conn@Connection {connId} = \case
|
||||||
Nothing -> case agentMsg of
|
Nothing -> case agentMsg of
|
||||||
CONF confId connInfo -> do
|
CONF confId connInfo -> do
|
||||||
saveConnInfo conn connInfo
|
saveConnInfo conn connInfo
|
||||||
@ -558,9 +560,10 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
withAckMessage agentConnId meta $ pure ()
|
withAckMessage agentConnId meta $ pure ()
|
||||||
ackMsgDeliveryEvent conn meta
|
ackMsgDeliveryEvent conn meta
|
||||||
SENT msgId ->
|
SENT msgId ->
|
||||||
|
-- ? updateDirectChatItem
|
||||||
sentMsgDeliveryEvent conn msgId
|
sentMsgDeliveryEvent conn msgId
|
||||||
-- TODO print errors
|
-- TODO print errors
|
||||||
MERR _ _ -> pure ()
|
MERR _ _ -> pure () -- ? updateDirectChatItem
|
||||||
ERR _ -> pure ()
|
ERR _ -> pure ()
|
||||||
-- TODO add debugging output
|
-- TODO add debugging output
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
@ -609,8 +612,14 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
when (memberIsReady m) $ do
|
when (memberIsReady m) $ do
|
||||||
notifyMemberConnected gInfo m
|
notifyMemberConnected gInfo m
|
||||||
when (memberCategory m == GCPreMember) $ probeMatchingContacts ct
|
when (memberCategory m == GCPreMember) $ probeMatchingContacts ct
|
||||||
SENT msgId ->
|
SENT msgId -> do
|
||||||
sentMsgDeliveryEvent conn msgId
|
sentMsgDeliveryEvent conn msgId
|
||||||
|
chatItemId_ <- withStore $ \st -> getChatItemIdByAgentMsgId st connId msgId
|
||||||
|
case chatItemId_ of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just chatItemId -> do
|
||||||
|
chatItem <- withStore $ \st -> updateDirectChatItem st chatItemId CISSndSent
|
||||||
|
toView $ CRChatItemUpdated (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem)
|
||||||
END -> do
|
END -> do
|
||||||
toView $ CRContactAnotherClient ct
|
toView $ CRContactAnotherClient ct
|
||||||
showToast (c <> "> ") "connected to another client"
|
showToast (c <> "> ") "connected to another client"
|
||||||
@ -623,7 +632,13 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
showToast (c <> "> ") "is active"
|
showToast (c <> "> ") "is active"
|
||||||
setActive $ ActiveC c
|
setActive $ ActiveC c
|
||||||
-- TODO print errors
|
-- TODO print errors
|
||||||
MERR _ _ -> pure ()
|
MERR msgId err -> do
|
||||||
|
chatItemId_ <- withStore $ \st -> getChatItemIdByAgentMsgId st connId msgId
|
||||||
|
case chatItemId_ of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just chatItemId -> do
|
||||||
|
chatItem <- withStore $ \st -> updateDirectChatItem st chatItemId (agentErrToItemStatus err)
|
||||||
|
toView $ CRChatItemUpdated (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem)
|
||||||
ERR _ -> pure ()
|
ERR _ -> pure ()
|
||||||
-- TODO add debugging output
|
-- TODO add debugging output
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
@ -821,6 +836,10 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
sentMsgDeliveryEvent Connection {connId} msgId =
|
sentMsgDeliveryEvent Connection {connId} msgId =
|
||||||
withStore $ \st -> createSndMsgDeliveryEvent st connId msgId MDSSndSent
|
withStore $ \st -> createSndMsgDeliveryEvent st connId msgId MDSSndSent
|
||||||
|
|
||||||
|
agentErrToItemStatus :: AgentErrorType -> CIStatus 'MDSnd
|
||||||
|
agentErrToItemStatus (SMP AUTH) = CISSndErrorAuth
|
||||||
|
agentErrToItemStatus err = CISSndError err
|
||||||
|
|
||||||
badRcvFileChunk :: RcvFileTransfer -> String -> m ()
|
badRcvFileChunk :: RcvFileTransfer -> String -> m ()
|
||||||
badRcvFileChunk ft@RcvFileTransfer {fileStatus} err =
|
badRcvFileChunk ft@RcvFileTransfer {fileStatus} err =
|
||||||
case fileStatus of
|
case fileStatus of
|
||||||
@ -879,7 +898,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
chSize <- asks $ fileChunkSize . config
|
chSize <- asks $ fileChunkSize . config
|
||||||
ft@RcvFileTransfer {fileId} <- withStore $ \st -> createRcvFileTransfer st userId ct fInv chSize
|
ft@RcvFileTransfer {fileId} <- withStore $ \st -> createRcvFileTransfer st userId ct fInv chSize
|
||||||
ci <- saveRcvDirectChatItem userId ct msgId msgMeta (CIRcvFileInvitation ft)
|
ci <- saveRcvDirectChatItem userId ct msgId msgMeta (CIRcvFileInvitation ft)
|
||||||
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId ci
|
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId' ci
|
||||||
toView . CRNewChatItem $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci
|
toView . CRNewChatItem $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci
|
||||||
checkIntegrity msgMeta $ toView . CRMsgIntegrityError
|
checkIntegrity msgMeta $ toView . CRMsgIntegrityError
|
||||||
showToast (c <> "> ") "wants to send a file"
|
showToast (c <> "> ") "wants to send a file"
|
||||||
@ -890,7 +909,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
|
|||||||
chSize <- asks $ fileChunkSize . config
|
chSize <- asks $ fileChunkSize . config
|
||||||
ft@RcvFileTransfer {fileId} <- withStore $ \st -> createRcvGroupFileTransfer st userId m fInv chSize
|
ft@RcvFileTransfer {fileId} <- withStore $ \st -> createRcvGroupFileTransfer st userId m fInv chSize
|
||||||
ci <- saveRcvGroupChatItem userId gInfo m msgId msgMeta (CIRcvFileInvitation ft)
|
ci <- saveRcvGroupChatItem userId gInfo m msgId msgMeta (CIRcvFileInvitation ft)
|
||||||
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId ci
|
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId' ci
|
||||||
toView . CRNewChatItem $ AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci
|
toView . CRNewChatItem $ AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci
|
||||||
checkIntegrity msgMeta $ toView . CRMsgIntegrityError
|
checkIntegrity msgMeta $ toView . CRMsgIntegrityError
|
||||||
let g = groupName' gInfo
|
let g = groupName' gInfo
|
||||||
@ -1248,11 +1267,11 @@ saveRcvGroupChatItem userId g m msgId MsgMeta {broker = (_, brokerTs)} ciContent
|
|||||||
ciMeta <- saveChatItem userId (CDGroupRcv g m) $ mkNewChatItem ciContent msgId brokerTs createdAt
|
ciMeta <- saveChatItem userId (CDGroupRcv g m) $ mkNewChatItem ciContent msgId brokerTs createdAt
|
||||||
pure $ ChatItem (CIGroupRcv m) ciMeta ciContent
|
pure $ ChatItem (CIGroupRcv m) ciMeta ciContent
|
||||||
|
|
||||||
saveChatItem :: ChatMonad m => UserId -> ChatDirection c d -> NewChatItem d -> m CIMeta
|
saveChatItem :: (ChatMonad m, MsgDirectionI d) => UserId -> ChatDirection c d -> NewChatItem d -> m (CIMeta d)
|
||||||
saveChatItem userId cd ci@NewChatItem {itemTs, itemText, createdAt} = do
|
saveChatItem userId cd ci@NewChatItem {itemTs, itemText, createdAt} = do
|
||||||
tz <- liftIO getCurrentTimeZone
|
tz <- liftIO getCurrentTimeZone
|
||||||
ciId <- withStore $ \st -> createNewChatItem st userId cd ci
|
ciId <- withStore $ \st -> createNewChatItem st userId cd ci
|
||||||
pure $ mkCIMeta ciId itemText tz itemTs createdAt
|
pure $ mkCIMeta ciId itemText ciStatusNew tz itemTs createdAt
|
||||||
|
|
||||||
mkNewChatItem :: forall d. MsgDirectionI d => CIContent d -> MessageId -> UTCTime -> UTCTime -> NewChatItem d
|
mkNewChatItem :: forall d. MsgDirectionI d => CIContent d -> MessageId -> UTCTime -> UTCTime -> NewChatItem d
|
||||||
mkNewChatItem itemContent msgId itemTs createdAt =
|
mkNewChatItem itemContent msgId itemTs createdAt =
|
||||||
@ -1262,6 +1281,7 @@ mkNewChatItem itemContent msgId itemTs createdAt =
|
|||||||
itemTs,
|
itemTs,
|
||||||
itemContent,
|
itemContent,
|
||||||
itemText = ciContentToText itemContent,
|
itemText = ciContentToText itemContent,
|
||||||
|
itemStatus = ciStatusNew,
|
||||||
createdAt
|
createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-c
|
|||||||
data ChatConfig = ChatConfig
|
data ChatConfig = ChatConfig
|
||||||
{ agentConfig :: AgentConfig,
|
{ agentConfig :: AgentConfig,
|
||||||
dbPoolSize :: Int,
|
dbPoolSize :: Int,
|
||||||
|
yesToMigrations :: Bool,
|
||||||
tbqSize :: Natural,
|
tbqSize :: Natural,
|
||||||
fileChunkSize :: Integer
|
fileChunkSize :: Integer
|
||||||
}
|
}
|
||||||
@ -130,6 +131,7 @@ data ChatResponse
|
|||||||
| CRApiChats {chats :: [AChat]}
|
| CRApiChats {chats :: [AChat]}
|
||||||
| CRApiChat {chat :: AChat}
|
| CRApiChat {chat :: AChat}
|
||||||
| CRNewChatItem {chatItem :: AChatItem}
|
| CRNewChatItem {chatItem :: AChatItem}
|
||||||
|
| CRChatItemUpdated {chatItem :: AChatItem}
|
||||||
| CRMsgIntegrityError {msgerror :: MsgErrorType} -- TODO make it chat item to support in mobile
|
| CRMsgIntegrityError {msgerror :: MsgErrorType} -- TODO make it chat item to support in mobile
|
||||||
| CRCmdAccepted {corr :: CorrId}
|
| CRCmdAccepted {corr :: CorrId}
|
||||||
| CRChatHelp {helpSection :: HelpSection}
|
| CRChatHelp {helpSection :: HelpSection}
|
||||||
|
@ -15,6 +15,7 @@ module Simplex.Chat.Messages where
|
|||||||
|
|
||||||
import Data.Aeson (FromJSON, ToJSON)
|
import Data.Aeson (FromJSON, ToJSON)
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
@ -30,12 +31,13 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
|||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..))
|
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
|
||||||
|
import Simplex.Messaging.Agent.Protocol (AgentErrorType, AgentMsgId, MsgMeta (..))
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
|
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON)
|
||||||
import Simplex.Messaging.Protocol (MsgBody)
|
import Simplex.Messaging.Protocol (MsgBody)
|
||||||
import Simplex.Chat.Util (safeDecodeUtf8)
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
data ChatType = CTDirect | CTGroup | CTContactRequest
|
data ChatType = CTDirect | CTGroup | CTContactRequest
|
||||||
deriving (Show, Generic)
|
deriving (Show, Generic)
|
||||||
@ -73,7 +75,7 @@ jsonChatInfo = \case
|
|||||||
|
|
||||||
data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem
|
data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem
|
||||||
{ chatDir :: CIDirection c d,
|
{ chatDir :: CIDirection c d,
|
||||||
meta :: CIMeta,
|
meta :: CIMeta d,
|
||||||
content :: CIContent d
|
content :: CIContent d
|
||||||
}
|
}
|
||||||
deriving (Show, Generic)
|
deriving (Show, Generic)
|
||||||
@ -115,7 +117,7 @@ jsonCIDirection = \case
|
|||||||
CIGroupSnd -> JCIGroupSnd
|
CIGroupSnd -> JCIGroupSnd
|
||||||
CIGroupRcv m -> JCIGroupRcv m
|
CIGroupRcv m -> JCIGroupRcv m
|
||||||
|
|
||||||
data CChatItem c = forall d. CChatItem (SMsgDirection d) (ChatItem c d)
|
data CChatItem c = forall d. MsgDirectionI d => CChatItem (SMsgDirection d) (ChatItem c d)
|
||||||
|
|
||||||
deriving instance Show (CChatItem c)
|
deriving instance Show (CChatItem c)
|
||||||
|
|
||||||
@ -123,8 +125,8 @@ instance ToJSON (CChatItem c) where
|
|||||||
toJSON (CChatItem _ ci) = J.toJSON ci
|
toJSON (CChatItem _ ci) = J.toJSON ci
|
||||||
toEncoding (CChatItem _ ci) = J.toEncoding ci
|
toEncoding (CChatItem _ ci) = J.toEncoding ci
|
||||||
|
|
||||||
chatItemId :: ChatItem c d -> ChatItemId
|
chatItemId' :: ChatItem c d -> ChatItemId
|
||||||
chatItemId ChatItem {meta = CIMeta {itemId}} = itemId
|
chatItemId' ChatItem {meta = CIMeta {itemId}} = itemId
|
||||||
|
|
||||||
data ChatDirection (c :: ChatType) (d :: MsgDirection) where
|
data ChatDirection (c :: ChatType) (d :: MsgDirection) where
|
||||||
CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd
|
CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd
|
||||||
@ -138,6 +140,7 @@ data NewChatItem d = NewChatItem
|
|||||||
itemTs :: ChatItemTs,
|
itemTs :: ChatItemTs,
|
||||||
itemContent :: CIContent d,
|
itemContent :: CIContent d,
|
||||||
itemText :: Text,
|
itemText :: Text,
|
||||||
|
itemStatus :: CIStatus d,
|
||||||
createdAt :: UTCTime
|
createdAt :: UTCTime
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
@ -174,21 +177,91 @@ instance ToJSON (JSONAnyChatItem c d) where
|
|||||||
toJSON = J.genericToJSON J.defaultOptions
|
toJSON = J.genericToJSON J.defaultOptions
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions
|
toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
data CIMeta = CIMeta
|
data CIMeta (d :: MsgDirection) = CIMeta
|
||||||
{ itemId :: ChatItemId,
|
{ itemId :: ChatItemId,
|
||||||
itemTs :: ChatItemTs,
|
itemTs :: ChatItemTs,
|
||||||
itemText :: Text,
|
itemText :: Text,
|
||||||
|
itemStatus :: CIStatus d,
|
||||||
localItemTs :: ZonedTime,
|
localItemTs :: ZonedTime,
|
||||||
createdAt :: UTCTime
|
createdAt :: UTCTime
|
||||||
}
|
}
|
||||||
deriving (Show, Generic, FromJSON)
|
deriving (Show, Generic)
|
||||||
|
|
||||||
mkCIMeta :: ChatItemId -> Text -> TimeZone -> ChatItemTs -> UTCTime -> CIMeta
|
mkCIMeta :: ChatItemId -> Text -> CIStatus d -> TimeZone -> ChatItemTs -> UTCTime -> CIMeta d
|
||||||
mkCIMeta itemId itemText tz itemTs createdAt =
|
mkCIMeta itemId itemText itemStatus tz itemTs createdAt =
|
||||||
let localItemTs = utcToZonedTime tz itemTs
|
let localItemTs = utcToZonedTime tz itemTs
|
||||||
in CIMeta {itemId, itemTs, itemText, localItemTs, createdAt}
|
in CIMeta {itemId, itemTs, itemText, itemStatus, localItemTs, createdAt}
|
||||||
|
|
||||||
instance ToJSON CIMeta where toEncoding = J.genericToEncoding J.defaultOptions
|
instance ToJSON (CIMeta d) where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
data CIStatus (d :: MsgDirection) where
|
||||||
|
CISSndNew :: CIStatus 'MDSnd
|
||||||
|
CISSndSent :: CIStatus 'MDSnd
|
||||||
|
CISSndErrorAuth :: CIStatus 'MDSnd
|
||||||
|
CISSndError :: AgentErrorType -> CIStatus 'MDSnd
|
||||||
|
CISRcvNew :: CIStatus 'MDRcv
|
||||||
|
CISRcvRead :: CIStatus 'MDRcv
|
||||||
|
|
||||||
|
deriving instance Show (CIStatus d)
|
||||||
|
|
||||||
|
ciStatusNew :: forall d. MsgDirectionI d => CIStatus d
|
||||||
|
ciStatusNew = case msgDirection @d of
|
||||||
|
SMDSnd -> CISSndNew
|
||||||
|
SMDRcv -> CISRcvNew
|
||||||
|
|
||||||
|
instance ToJSON (CIStatus d) where
|
||||||
|
toJSON = J.toJSON . jsonCIStatus
|
||||||
|
toEncoding = J.toEncoding . jsonCIStatus
|
||||||
|
|
||||||
|
instance MsgDirectionI d => ToField (CIStatus d) where toField = toField . decodeLatin1 . strEncode
|
||||||
|
|
||||||
|
instance FromField ACIStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
|
||||||
|
|
||||||
|
data ACIStatus = forall d. MsgDirectionI d => ACIStatus (SMsgDirection d) (CIStatus d)
|
||||||
|
|
||||||
|
instance MsgDirectionI d => StrEncoding (CIStatus d) where
|
||||||
|
strEncode = \case
|
||||||
|
CISSndNew -> "snd_new"
|
||||||
|
CISSndSent -> "snd_sent"
|
||||||
|
CISSndErrorAuth -> "snd_error_auth"
|
||||||
|
CISSndError e -> "snd_error " <> strEncode e
|
||||||
|
CISRcvNew -> "rcv_new"
|
||||||
|
CISRcvRead -> "rcv_read"
|
||||||
|
strP = (\(ACIStatus _ st) -> checkDirection st) <$?> strP
|
||||||
|
|
||||||
|
instance StrEncoding ACIStatus where
|
||||||
|
strEncode (ACIStatus _ s) = strEncode s
|
||||||
|
strP =
|
||||||
|
A.takeTill (== ' ') >>= \case
|
||||||
|
"snd_new" -> pure $ ACIStatus SMDSnd CISSndNew
|
||||||
|
"snd_sent" -> pure $ ACIStatus SMDSnd CISSndSent
|
||||||
|
"snd_error_auth" -> pure $ ACIStatus SMDSnd CISSndErrorAuth
|
||||||
|
"snd_error" -> ACIStatus SMDSnd <$> (A.space *> strP)
|
||||||
|
"rcv_new" -> pure $ ACIStatus SMDRcv CISRcvNew
|
||||||
|
"rcv_read" -> pure $ ACIStatus SMDRcv CISRcvRead
|
||||||
|
_ -> fail "bad status"
|
||||||
|
|
||||||
|
data JSONCIStatus
|
||||||
|
= JCISSndNew
|
||||||
|
| JCISSndSent
|
||||||
|
| JCISSndErrorAuth
|
||||||
|
| JCISSndError {agentError :: AgentErrorType}
|
||||||
|
| JCISRcvNew
|
||||||
|
| JCISRcvRead
|
||||||
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON JSONCIStatus where
|
||||||
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCIS"
|
||||||
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCIS"
|
||||||
|
|
||||||
|
jsonCIStatus :: CIStatus d -> JSONCIStatus
|
||||||
|
jsonCIStatus = \case
|
||||||
|
CISSndNew -> JCISSndNew
|
||||||
|
CISSndSent -> JCISSndSent
|
||||||
|
CISSndErrorAuth -> JCISSndErrorAuth
|
||||||
|
CISSndError e -> JCISSndError e
|
||||||
|
CISRcvNew -> JCISRcvNew
|
||||||
|
CISRcvRead -> JCISRcvRead
|
||||||
|
|
||||||
type ChatItemId = Int64
|
type ChatItemId = Int64
|
||||||
|
|
||||||
@ -420,3 +493,8 @@ msgDeliveryStatusT' s =
|
|||||||
case testEquality d (msgDirection @d) of
|
case testEquality d (msgDirection @d) of
|
||||||
Just Refl -> Just st
|
Just Refl -> Just st
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
|
checkDirection :: forall t d d'. (MsgDirectionI d, MsgDirectionI d') => t d' -> Either String (t d)
|
||||||
|
checkDirection x = case testEquality (msgDirection @d) (msgDirection @d') of
|
||||||
|
Just Refl -> Right x
|
||||||
|
Nothing -> Left "bad direction"
|
||||||
|
20
src/Simplex/Chat/Migrations/M20220205_chat_item_status.hs
Normal file
20
src/Simplex/Chat/Migrations/M20220205_chat_item_status.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Migrations.M20220205_chat_item_status where
|
||||||
|
|
||||||
|
import Database.SQLite.Simple (Query)
|
||||||
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
|
|
||||||
|
m20220205_chat_item_status :: Query
|
||||||
|
m20220205_chat_item_status =
|
||||||
|
[sql|
|
||||||
|
PRAGMA ignore_check_constraints=ON;
|
||||||
|
|
||||||
|
ALTER TABLE chat_items ADD COLUMN item_status TEXT CHECK (item_status NOT NULL);
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = 'rcv_read' WHERE item_sent = 0;
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = 'snd_sent' WHERE item_sent = 1;
|
||||||
|
|
||||||
|
PRAGMA ignore_check_constraints=OFF;
|
||||||
|
|]
|
@ -53,6 +53,13 @@ mobileChatOpts =
|
|||||||
logging = False
|
logging = False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultMobileConfig :: ChatConfig
|
||||||
|
defaultMobileConfig =
|
||||||
|
defaultChatConfig
|
||||||
|
{ yesToMigrations = True,
|
||||||
|
agentConfig = agentConfig defaultChatConfig {yesToMigrations = True}
|
||||||
|
}
|
||||||
|
|
||||||
type CJSONString = CString
|
type CJSONString = CString
|
||||||
|
|
||||||
getActiveUser_ :: SQLiteStore -> IO (Maybe User)
|
getActiveUser_ :: SQLiteStore -> IO (Maybe User)
|
||||||
@ -61,9 +68,9 @@ getActiveUser_ st = find activeUser <$> getUsers st
|
|||||||
chatInit :: String -> IO ChatController
|
chatInit :: String -> IO ChatController
|
||||||
chatInit dbFilePrefix = do
|
chatInit dbFilePrefix = do
|
||||||
let f = chatStoreFile dbFilePrefix
|
let f = chatStoreFile dbFilePrefix
|
||||||
chatStore <- createStore f $ dbPoolSize defaultChatConfig
|
chatStore <- createStore f (dbPoolSize defaultMobileConfig) (yesToMigrations defaultMobileConfig)
|
||||||
user_ <- getActiveUser_ chatStore
|
user_ <- getActiveUser_ chatStore
|
||||||
newChatController chatStore user_ defaultChatConfig mobileChatOpts {dbFilePrefix} . const $ pure ()
|
newChatController chatStore user_ defaultMobileConfig mobileChatOpts {dbFilePrefix} . const $ pure ()
|
||||||
|
|
||||||
chatSendCmd :: ChatController -> String -> IO JSONString
|
chatSendCmd :: ChatController -> String -> IO JSONString
|
||||||
chatSendCmd cc s = LB.unpack . J.encode . APIResponse Nothing <$> runReaderT (execChatCommand $ B.pack s) cc
|
chatSendCmd cc s = LB.unpack . J.encode . APIResponse Nothing <$> runReaderT (execChatCommand $ B.pack s) cc
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
{-# LANGUAGE TypeOperators #-}
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
|
||||||
module Simplex.Chat.Store
|
module Simplex.Chat.Store
|
||||||
@ -113,6 +114,8 @@ module Simplex.Chat.Store
|
|||||||
getChatPreviews,
|
getChatPreviews,
|
||||||
getDirectChat,
|
getDirectChat,
|
||||||
getGroupChat,
|
getGroupChat,
|
||||||
|
getChatItemIdByAgentMsgId,
|
||||||
|
updateDirectChatItem,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -125,6 +128,7 @@ import Control.Monad.IO.Unlift
|
|||||||
import Crypto.Random (ChaChaDRG, randomBytesGenerate)
|
import Crypto.Random (ChaChaDRG, randomBytesGenerate)
|
||||||
import Data.Aeson (ToJSON)
|
import Data.Aeson (ToJSON)
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
|
import Data.Bifunctor (first)
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import Data.Either (rights)
|
import Data.Either (rights)
|
||||||
@ -138,6 +142,7 @@ import Data.Text (Text)
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Time.Clock (UTCTime (..), getCurrentTime)
|
import Data.Time.Clock (UTCTime (..), getCurrentTime)
|
||||||
import Data.Time.LocalTime (TimeZone, getCurrentTimeZone)
|
import Data.Time.LocalTime (TimeZone, getCurrentTimeZone)
|
||||||
|
import Data.Type.Equality
|
||||||
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), SQLError, (:.) (..))
|
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), SQLError, (:.) (..))
|
||||||
import qualified Database.SQLite.Simple as DB
|
import qualified Database.SQLite.Simple as DB
|
||||||
import Database.SQLite.Simple.QQ (sql)
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
@ -145,6 +150,7 @@ import GHC.Generics (Generic)
|
|||||||
import Simplex.Chat.Messages
|
import Simplex.Chat.Messages
|
||||||
import Simplex.Chat.Migrations.M20220101_initial
|
import Simplex.Chat.Migrations.M20220101_initial
|
||||||
import Simplex.Chat.Migrations.M20220122_v1_1
|
import Simplex.Chat.Migrations.M20220122_v1_1
|
||||||
|
import Simplex.Chat.Migrations.M20220205_chat_item_status
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Util (eitherToMaybe)
|
import Simplex.Chat.Util (eitherToMaybe)
|
||||||
@ -160,7 +166,8 @@ import UnliftIO.STM
|
|||||||
schemaMigrations :: [(String, Query)]
|
schemaMigrations :: [(String, Query)]
|
||||||
schemaMigrations =
|
schemaMigrations =
|
||||||
[ ("20220101_initial", m20220101_initial),
|
[ ("20220101_initial", m20220101_initial),
|
||||||
("20220122_v1_1", m20220122_v1_1)
|
("20220122_v1_1", m20220122_v1_1),
|
||||||
|
("20220205_chat_item_status", m20220205_chat_item_status)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | The list of migrations in ascending order by date
|
-- | The list of migrations in ascending order by date
|
||||||
@ -169,7 +176,7 @@ migrations = sortBy (compare `on` name) $ map migration schemaMigrations
|
|||||||
where
|
where
|
||||||
migration (name, query) = Migration {name = name, up = fromQuery query}
|
migration (name, query) = Migration {name = name, up = fromQuery query}
|
||||||
|
|
||||||
createStore :: FilePath -> Int -> IO SQLiteStore
|
createStore :: FilePath -> Int -> Bool -> IO SQLiteStore
|
||||||
createStore dbFilePath poolSize = createSQLiteStore dbFilePath poolSize migrations
|
createStore dbFilePath poolSize = createSQLiteStore dbFilePath poolSize migrations
|
||||||
|
|
||||||
chatStoreFile :: FilePath -> FilePath
|
chatStoreFile :: FilePath -> FilePath
|
||||||
@ -181,7 +188,7 @@ checkConstraint err action = action `E.catch` (pure . Left . handleSQLError err)
|
|||||||
handleSQLError :: StoreError -> SQLError -> StoreError
|
handleSQLError :: StoreError -> SQLError -> StoreError
|
||||||
handleSQLError err e
|
handleSQLError err e
|
||||||
| DB.sqlError e == DB.ErrorConstraint = err
|
| DB.sqlError e == DB.ErrorConstraint = err
|
||||||
| otherwise = SEInternal $ show e
|
| otherwise = SEInternalError $ show e
|
||||||
|
|
||||||
insertedRowId :: DB.Connection -> IO Int64
|
insertedRowId :: DB.Connection -> IO Int64
|
||||||
insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"
|
insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"
|
||||||
@ -851,7 +858,7 @@ getConnectionEntity st User {userId, userContactId} agentConnId =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
if connType == ConnContact
|
if connType == ConnContact
|
||||||
then pure $ RcvDirectMsgConnection c Nothing
|
then pure $ RcvDirectMsgConnection c Nothing
|
||||||
else throwError $ SEInternal $ "connection " <> show connType <> " without entity"
|
else throwError $ SEInternalError $ "connection " <> show connType <> " without entity"
|
||||||
Just entId ->
|
Just entId ->
|
||||||
case connType of
|
case connType of
|
||||||
ConnMember -> uncurry (RcvGroupMsgConnection c) <$> getGroupAndMember_ db entId c
|
ConnMember -> uncurry (RcvGroupMsgConnection c) <$> getGroupAndMember_ db entId c
|
||||||
@ -891,10 +898,10 @@ getConnectionEntity st User {userId, userContactId} agentConnId =
|
|||||||
toContact' contactId activeConn [(localDisplayName, displayName, fullName, viaGroup, createdAt)] =
|
toContact' contactId activeConn [(localDisplayName, displayName, fullName, viaGroup, createdAt)] =
|
||||||
let profile = Profile {displayName, fullName}
|
let profile = Profile {displayName, fullName}
|
||||||
in Right $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup, createdAt}
|
in Right $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup, createdAt}
|
||||||
toContact' _ _ _ = Left $ SEInternal "referenced contact not found"
|
toContact' _ _ _ = Left $ SEInternalError "referenced contact not found"
|
||||||
getGroupAndMember_ :: DB.Connection -> Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
getGroupAndMember_ :: DB.Connection -> Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||||
getGroupAndMember_ db groupMemberId c = ExceptT $ do
|
getGroupAndMember_ db groupMemberId c = ExceptT $ do
|
||||||
firstRow (toGroupAndMember c) (SEInternal "referenced group member not found") $
|
firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $
|
||||||
DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
@ -1925,8 +1932,8 @@ createMsgDeliveryEvent_ db msgDeliveryId msgDeliveryStatus createdAt = do
|
|||||||
|
|
||||||
getMsgDeliveryId_ :: DB.Connection -> Int64 -> AgentMsgId -> IO (Either StoreError Int64)
|
getMsgDeliveryId_ :: DB.Connection -> Int64 -> AgentMsgId -> IO (Either StoreError Int64)
|
||||||
getMsgDeliveryId_ db connId agentMsgId =
|
getMsgDeliveryId_ db connId agentMsgId =
|
||||||
toMsgDeliveryId
|
firstRow fromOnly (SENoMsgDelivery connId agentMsgId) $
|
||||||
<$> DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT msg_delivery_id
|
SELECT msg_delivery_id
|
||||||
@ -1935,10 +1942,6 @@ getMsgDeliveryId_ db connId agentMsgId =
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
|]
|
|]
|
||||||
(connId, agentMsgId)
|
(connId, agentMsgId)
|
||||||
where
|
|
||||||
toMsgDeliveryId :: [Only Int64] -> Either StoreError Int64
|
|
||||||
toMsgDeliveryId [Only msgDeliveryId] = Right msgDeliveryId
|
|
||||||
toMsgDeliveryId _ = Left $ SENoMsgDelivery connId agentMsgId
|
|
||||||
|
|
||||||
createPendingGroupMessage :: MonadUnliftIO m => SQLiteStore -> Int64 -> MessageId -> Maybe Int64 -> m ()
|
createPendingGroupMessage :: MonadUnliftIO m => SQLiteStore -> Int64 -> MessageId -> Maybe Int64 -> m ()
|
||||||
createPendingGroupMessage st groupMemberId messageId introId_ =
|
createPendingGroupMessage st groupMemberId messageId introId_ =
|
||||||
@ -1975,20 +1978,20 @@ deletePendingGroupMessage st groupMemberId messageId =
|
|||||||
liftIO . withTransaction st $ \db ->
|
liftIO . withTransaction st $ \db ->
|
||||||
DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId)
|
DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId)
|
||||||
|
|
||||||
createNewChatItem :: MonadUnliftIO m => SQLiteStore -> UserId -> ChatDirection c d -> NewChatItem d -> m ChatItemId
|
createNewChatItem :: (MonadUnliftIO m, MsgDirectionI d) => SQLiteStore -> UserId -> ChatDirection c d -> NewChatItem d -> m ChatItemId
|
||||||
createNewChatItem st userId chatDirection NewChatItem {createdByMsgId, itemSent, itemTs, itemContent, itemText, createdAt} =
|
createNewChatItem st userId chatDirection NewChatItem {createdByMsgId, itemSent, itemTs, itemContent, itemText, itemStatus, createdAt} =
|
||||||
liftIO . withTransaction st $ \db -> do
|
liftIO . withTransaction st $ \db -> do
|
||||||
let (contactId_, groupId_, groupMemberId_) = ids
|
let (contactId_, groupId_, groupMemberId_) = ids
|
||||||
DB.execute
|
DB.execute
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
INSERT INTO chat_items (
|
INSERT INTO chat_items (
|
||||||
user_id, contact_id, group_id, group_member_id,
|
user_id, contact_id, group_id, group_member_id, created_by_msg_id,
|
||||||
created_by_msg_id, item_sent, item_ts, item_content, item_text, created_at, updated_at
|
item_sent, item_ts, item_content, item_text, item_status, created_at, updated_at
|
||||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
|]
|
|]
|
||||||
( (userId, contactId_, groupId_, groupMemberId_)
|
( (userId, contactId_, groupId_, groupMemberId_, createdByMsgId)
|
||||||
:. (createdByMsgId, itemSent, itemTs, itemContent, itemText, createdAt, createdAt)
|
:. (itemSent, itemTs, itemContent, itemText, itemStatus, createdAt, createdAt)
|
||||||
)
|
)
|
||||||
ciId <- insertedRowId db
|
ciId <- insertedRowId db
|
||||||
case createdByMsgId of
|
case createdByMsgId of
|
||||||
@ -2038,7 +2041,7 @@ getDirectChatPreviews_ db User {userId} = do
|
|||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.conn_status, c.conn_type,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.conn_status, c.conn_type,
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at,
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at,
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at
|
||||||
FROM contacts ct
|
FROM contacts ct
|
||||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||||
JOIN connections c ON c.contact_id = ct.contact_id
|
JOIN connections c ON c.contact_id = ct.contact_id
|
||||||
@ -2083,7 +2086,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
|
|||||||
mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id,
|
mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id,
|
||||||
pu.display_name, pu.full_name,
|
pu.display_name, pu.full_name,
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at,
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at,
|
||||||
-- Maybe GroupMember - sender
|
-- Maybe GroupMember - sender
|
||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
||||||
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
||||||
@ -2145,20 +2148,22 @@ getDirectChat st user contactId pagination =
|
|||||||
getDirectChatLast_ :: DB.Connection -> User -> Int64 -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
getDirectChatLast_ :: DB.Connection -> User -> Int64 -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
||||||
getDirectChatLast_ db User {userId} contactId count = do
|
getDirectChatLast_ db User {userId} contactId count = do
|
||||||
contact <- ExceptT $ getContact_ db userId contactId
|
contact <- ExceptT $ getContact_ db userId contactId
|
||||||
chatItems <- liftIO getDirectChatItemsLast_
|
chatItems <- ExceptT getDirectChatItemsLast_
|
||||||
pure $ Chat (DirectChat contact) (reverse chatItems)
|
pure $ Chat (DirectChat contact) (reverse chatItems)
|
||||||
where
|
where
|
||||||
getDirectChatItemsLast_ :: IO [CChatItem 'CTDirect]
|
getDirectChatItemsLast_ :: IO (Either StoreError [CChatItem 'CTDirect])
|
||||||
getDirectChatItemsLast_ = do
|
getDirectChatItemsLast_ = do
|
||||||
tz <- getCurrentTimeZone
|
tz <- getCurrentTimeZone
|
||||||
map (toDirectChatItem tz)
|
mapM (toDirectChatItem tz)
|
||||||
<$> DB.query
|
<$> DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT chat_item_id, item_ts, item_content, item_text, created_at
|
SELECT
|
||||||
FROM chat_items
|
-- ChatItem
|
||||||
WHERE user_id = ? AND contact_id = ?
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at
|
||||||
ORDER BY chat_item_id DESC
|
FROM chat_items ci
|
||||||
|
WHERE ci.user_id = ? AND ci.contact_id = ?
|
||||||
|
ORDER BY ci.chat_item_id DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
|]
|
|]
|
||||||
(userId, contactId, count)
|
(userId, contactId, count)
|
||||||
@ -2166,20 +2171,22 @@ getDirectChatLast_ db User {userId} contactId count = do
|
|||||||
getDirectChatAfter_ :: DB.Connection -> User -> Int64 -> ChatItemId -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
getDirectChatAfter_ :: DB.Connection -> User -> Int64 -> ChatItemId -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
||||||
getDirectChatAfter_ db User {userId} contactId afterChatItemId count = do
|
getDirectChatAfter_ db User {userId} contactId afterChatItemId count = do
|
||||||
contact <- ExceptT $ getContact_ db userId contactId
|
contact <- ExceptT $ getContact_ db userId contactId
|
||||||
chatItems <- liftIO getDirectChatItemsAfter_
|
chatItems <- ExceptT getDirectChatItemsAfter_
|
||||||
pure $ Chat (DirectChat contact) chatItems
|
pure $ Chat (DirectChat contact) chatItems
|
||||||
where
|
where
|
||||||
getDirectChatItemsAfter_ :: IO [CChatItem 'CTDirect]
|
getDirectChatItemsAfter_ :: IO (Either StoreError [CChatItem 'CTDirect])
|
||||||
getDirectChatItemsAfter_ = do
|
getDirectChatItemsAfter_ = do
|
||||||
tz <- getCurrentTimeZone
|
tz <- getCurrentTimeZone
|
||||||
map (toDirectChatItem tz)
|
mapM (toDirectChatItem tz)
|
||||||
<$> DB.query
|
<$> DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT chat_item_id, item_ts, item_content, item_text, created_at
|
SELECT
|
||||||
FROM chat_items
|
-- ChatItem
|
||||||
WHERE user_id = ? AND contact_id = ? AND chat_item_id > ?
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at
|
||||||
ORDER BY chat_item_id ASC
|
FROM chat_items ci
|
||||||
|
WHERE ci.user_id = ? AND ci.contact_id = ? AND ci.chat_item_id > ?
|
||||||
|
ORDER BY ci.chat_item_id ASC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
|]
|
|]
|
||||||
(userId, contactId, afterChatItemId, count)
|
(userId, contactId, afterChatItemId, count)
|
||||||
@ -2187,20 +2194,22 @@ getDirectChatAfter_ db User {userId} contactId afterChatItemId count = do
|
|||||||
getDirectChatBefore_ :: DB.Connection -> User -> Int64 -> ChatItemId -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
getDirectChatBefore_ :: DB.Connection -> User -> Int64 -> ChatItemId -> Int -> ExceptT StoreError IO (Chat 'CTDirect)
|
||||||
getDirectChatBefore_ db User {userId} contactId beforeChatItemId count = do
|
getDirectChatBefore_ db User {userId} contactId beforeChatItemId count = do
|
||||||
contact <- ExceptT $ getContact_ db userId contactId
|
contact <- ExceptT $ getContact_ db userId contactId
|
||||||
chatItems <- liftIO getDirectChatItemsBefore_
|
chatItems <- ExceptT getDirectChatItemsBefore_
|
||||||
pure $ Chat (DirectChat contact) (reverse chatItems)
|
pure $ Chat (DirectChat contact) (reverse chatItems)
|
||||||
where
|
where
|
||||||
getDirectChatItemsBefore_ :: IO [CChatItem 'CTDirect]
|
getDirectChatItemsBefore_ :: IO (Either StoreError [CChatItem 'CTDirect])
|
||||||
getDirectChatItemsBefore_ = do
|
getDirectChatItemsBefore_ = do
|
||||||
tz <- getCurrentTimeZone
|
tz <- getCurrentTimeZone
|
||||||
map (toDirectChatItem tz)
|
mapM (toDirectChatItem tz)
|
||||||
<$> DB.query
|
<$> DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT chat_item_id, item_ts, item_content, item_text, created_at
|
SELECT
|
||||||
FROM chat_items
|
-- ChatItem
|
||||||
WHERE user_id = ? AND contact_id = ? AND chat_item_id < ?
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at
|
||||||
ORDER BY chat_item_id DESC
|
FROM chat_items ci
|
||||||
|
WHERE ci.user_id = ? AND ci.contact_id = ? AND ci.chat_item_id < ?
|
||||||
|
ORDER BY ci.chat_item_id DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
|]
|
|]
|
||||||
(userId, contactId, beforeChatItemId, count)
|
(userId, contactId, beforeChatItemId, count)
|
||||||
@ -2266,7 +2275,7 @@ getGroupChatLast_ db user@User {userId, userContactId} groupId count = do
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at,
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at,
|
||||||
-- GroupMember
|
-- GroupMember
|
||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
||||||
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
||||||
@ -2295,7 +2304,7 @@ getGroupChatAfter_ db user@User {userId, userContactId} groupId afterChatItemId
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at,
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at,
|
||||||
-- GroupMember
|
-- GroupMember
|
||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
||||||
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
||||||
@ -2324,7 +2333,7 @@ getGroupChatBefore_ db user@User {userId, userContactId} groupId beforeChatItemI
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at,
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at,
|
||||||
-- GroupMember
|
-- GroupMember
|
||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
|
||||||
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
|
||||||
@ -2373,20 +2382,75 @@ getGroupIdByName_ db User {userId} gName =
|
|||||||
firstRow fromOnly (SEGroupNotFoundByName gName) $
|
firstRow fromOnly (SEGroupNotFoundByName gName) $
|
||||||
DB.query db "SELECT group_id FROM groups WHERE user_id = ? AND local_display_name = ?" (userId, gName)
|
DB.query db "SELECT group_id FROM groups WHERE user_id = ? AND local_display_name = ?" (userId, gName)
|
||||||
|
|
||||||
type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, UTCTime)
|
getChatItemIdByAgentMsgId :: StoreMonad m => SQLiteStore -> Int64 -> AgentMsgId -> m (Maybe ChatItemId)
|
||||||
|
getChatItemIdByAgentMsgId st connId msgId =
|
||||||
|
liftIO . withTransaction st $ \db ->
|
||||||
|
join . listToMaybe . map fromOnly
|
||||||
|
<$> DB.query
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
SELECT chat_item_id
|
||||||
|
FROM chat_item_messages
|
||||||
|
WHERE message_id = (
|
||||||
|
SELECT message_id
|
||||||
|
FROM msg_deliveries
|
||||||
|
WHERE connection_id = ? AND agent_msg_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
|]
|
||||||
|
(connId, msgId)
|
||||||
|
|
||||||
type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe UTCTime)
|
updateDirectChatItem :: (StoreMonad m, MsgDirectionI d) => SQLiteStore -> ChatItemId -> CIStatus d -> m (ChatItem 'CTDirect d)
|
||||||
|
updateDirectChatItem st itemId itemStatus =
|
||||||
|
liftIOEither . withTransaction st $ \db -> do
|
||||||
|
ci <- getDirectChatItem_ db itemId
|
||||||
|
DB.execute db "UPDATE chat_items SET item_status = ? WHERE chat_item_id = ?" (itemStatus, itemId)
|
||||||
|
pure ci
|
||||||
|
|
||||||
toDirectChatItem :: TimeZone -> ChatItemRow -> CChatItem 'CTDirect
|
getDirectChatItem_ :: forall d. MsgDirectionI d => DB.Connection -> ChatItemId -> IO (Either StoreError (ChatItem 'CTDirect d))
|
||||||
toDirectChatItem tz (itemId, itemTs, itemContent, itemText, createdAt) =
|
getDirectChatItem_ db itemId = do
|
||||||
let ciMeta = mkCIMeta itemId itemText tz itemTs createdAt
|
tz <- getCurrentTimeZone
|
||||||
in case itemContent of
|
join
|
||||||
ACIContent d@SMDSnd ciContent -> CChatItem d $ ChatItem CIDirectSnd ciMeta ciContent
|
<$> firstRow
|
||||||
ACIContent d@SMDRcv ciContent -> CChatItem d $ ChatItem CIDirectRcv ciMeta ciContent
|
(correctDir <=< toDirectChatItem tz)
|
||||||
|
(SEChatItemNotFound itemId)
|
||||||
|
( DB.query
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
SELECT
|
||||||
|
-- ChatItem
|
||||||
|
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.item_status, ci.created_at
|
||||||
|
FROM chat_items ci
|
||||||
|
WHERE ci.chat_item_id = ?
|
||||||
|
|]
|
||||||
|
(Only itemId)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
correctDir :: CChatItem c -> Either StoreError (ChatItem c d)
|
||||||
|
correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci
|
||||||
|
|
||||||
|
type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, ACIStatus, UTCTime)
|
||||||
|
|
||||||
|
type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe ACIStatus, Maybe UTCTime)
|
||||||
|
|
||||||
|
toDirectChatItem :: TimeZone -> ChatItemRow -> Either StoreError (CChatItem 'CTDirect)
|
||||||
|
toDirectChatItem tz (itemId, itemTs, itemContent, itemText, itemStatus, createdAt) =
|
||||||
|
case (itemContent, itemStatus) of
|
||||||
|
(ACIContent d@SMDSnd ciContent, ACIStatus d'@SMDSnd ciStatus) -> case testEquality d d' of
|
||||||
|
Just Refl -> Right $ CChatItem d (ChatItem CIDirectSnd (ciMeta ciStatus) ciContent)
|
||||||
|
_ -> badItem
|
||||||
|
(ACIContent d@SMDRcv ciContent, ACIStatus d'@SMDRcv ciStatus) -> case testEquality d d' of
|
||||||
|
Just Refl -> Right $ CChatItem d (ChatItem CIDirectRcv (ciMeta ciStatus) ciContent)
|
||||||
|
_ -> badItem
|
||||||
|
_ -> badItem
|
||||||
|
where
|
||||||
|
badItem = Left $ SEBadChatItem itemId
|
||||||
|
ciMeta :: CIStatus d -> CIMeta d
|
||||||
|
ciMeta status = mkCIMeta itemId itemText status tz itemTs createdAt
|
||||||
|
|
||||||
toDirectChatItemList :: TimeZone -> MaybeChatItemRow -> [CChatItem 'CTDirect]
|
toDirectChatItemList :: TimeZone -> MaybeChatItemRow -> [CChatItem 'CTDirect]
|
||||||
toDirectChatItemList tz (Just itemId, Just itemTs, Just itemContent, Just itemText, Just createdAt) =
|
toDirectChatItemList tz (Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, Just createdAt) =
|
||||||
[toDirectChatItem tz (itemId, itemTs, itemContent, itemText, createdAt)]
|
either (const []) (: []) $ toDirectChatItem tz (itemId, itemTs, itemContent, itemText, itemStatus, createdAt)
|
||||||
toDirectChatItemList _ _ = []
|
toDirectChatItemList _ _ = []
|
||||||
|
|
||||||
type GroupChatItemRow = ChatItemRow :. MaybeGroupMemberRow
|
type GroupChatItemRow = ChatItemRow :. MaybeGroupMemberRow
|
||||||
@ -2394,17 +2458,24 @@ type GroupChatItemRow = ChatItemRow :. MaybeGroupMemberRow
|
|||||||
type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow
|
type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow
|
||||||
|
|
||||||
toGroupChatItem :: TimeZone -> Int64 -> GroupChatItemRow -> Either StoreError (CChatItem 'CTGroup)
|
toGroupChatItem :: TimeZone -> Int64 -> GroupChatItemRow -> Either StoreError (CChatItem 'CTGroup)
|
||||||
toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, createdAt) :. memberRow_) =
|
toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, itemStatus, createdAt) :. memberRow_) = do
|
||||||
let ciMeta = mkCIMeta itemId itemText tz itemTs createdAt
|
let member_ = toMaybeGroupMember userContactId memberRow_
|
||||||
member_ = toMaybeGroupMember userContactId memberRow_
|
case (itemContent, itemStatus, member_) of
|
||||||
in case (itemContent, member_) of
|
(ACIContent d@SMDSnd ciContent, ACIStatus d'@SMDSnd ciStatus, Nothing) -> case testEquality d d' of
|
||||||
(ACIContent d@SMDSnd ciContent, Nothing) -> Right $ CChatItem d (ChatItem CIGroupSnd ciMeta ciContent)
|
Just Refl -> Right $ CChatItem d (ChatItem CIGroupSnd (ciMeta ciStatus) ciContent)
|
||||||
(ACIContent d@SMDRcv ciContent, Just member) -> Right $ CChatItem d (ChatItem (CIGroupRcv member) ciMeta ciContent)
|
_ -> badItem
|
||||||
_ -> Left $ SEBadChatItem itemId
|
(ACIContent d@SMDRcv ciContent, ACIStatus d'@SMDRcv ciStatus, Just member) -> case testEquality d d' of
|
||||||
|
Just Refl -> Right $ CChatItem d (ChatItem (CIGroupRcv member) (ciMeta ciStatus) ciContent)
|
||||||
|
_ -> badItem
|
||||||
|
_ -> badItem
|
||||||
|
where
|
||||||
|
badItem = Left $ SEBadChatItem itemId
|
||||||
|
ciMeta :: CIStatus d -> CIMeta d
|
||||||
|
ciMeta status = mkCIMeta itemId itemText status tz itemTs createdAt
|
||||||
|
|
||||||
toGroupChatItemList :: TimeZone -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup]
|
toGroupChatItemList :: TimeZone -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup]
|
||||||
toGroupChatItemList tz userContactId ((Just itemId, Just itemTs, Just itemContent, Just itemText, Just createdAt) :. memberRow_) =
|
toGroupChatItemList tz userContactId ((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, Just createdAt) :. memberRow_) =
|
||||||
either (const []) (: []) $ toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, createdAt) :. memberRow_)
|
either (const []) (: []) $ toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, itemStatus, createdAt) :. memberRow_)
|
||||||
toGroupChatItemList _ _ _ = []
|
toGroupChatItemList _ _ _ = []
|
||||||
|
|
||||||
-- | Saves unique local display name based on passed displayName, suffixed with _N if required.
|
-- | Saves unique local display name based on passed displayName, suffixed with _N if required.
|
||||||
@ -2459,7 +2530,7 @@ createWithRandomBytes size gVar create = tryCreate 3
|
|||||||
Right x -> pure $ Right x
|
Right x -> pure $ Right x
|
||||||
Left e
|
Left e
|
||||||
| DB.sqlError e == DB.ErrorConstraint -> tryCreate (n - 1)
|
| DB.sqlError e == DB.ErrorConstraint -> tryCreate (n - 1)
|
||||||
| otherwise -> pure . Left . SEInternal $ show e
|
| otherwise -> pure . Left . SEInternalError $ show e
|
||||||
|
|
||||||
randomBytes :: TVar ChaChaDRG -> Int -> IO ByteString
|
randomBytes :: TVar ChaChaDRG -> Int -> IO ByteString
|
||||||
randomBytes gVar n = B64.encode <$> (atomically . stateTVar gVar $ randomBytesGenerate n)
|
randomBytes gVar n = B64.encode <$> (atomically . stateTVar gVar $ randomBytesGenerate n)
|
||||||
@ -2488,9 +2559,10 @@ data StoreError
|
|||||||
| SEConnectionNotFound {agentConnId :: AgentConnId}
|
| SEConnectionNotFound {agentConnId :: AgentConnId}
|
||||||
| SEIntroNotFound
|
| SEIntroNotFound
|
||||||
| SEUniqueID
|
| SEUniqueID
|
||||||
| SEInternal {message :: String}
|
| SEInternalError {message :: String}
|
||||||
| SENoMsgDelivery {connId :: Int64, agentMsgId :: AgentMsgId}
|
| SENoMsgDelivery {connId :: Int64, agentMsgId :: AgentMsgId}
|
||||||
| SEBadChatItem {itemId :: Int64}
|
| SEBadChatItem {itemId :: ChatItemId}
|
||||||
|
| SEChatItemNotFound {itemId :: ChatItemId}
|
||||||
deriving (Show, Exception, Generic)
|
deriving (Show, Exception, Generic)
|
||||||
|
|
||||||
instance ToJSON StoreError where
|
instance ToJSON StoreError where
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
|
||||||
module Simplex.Chat.Terminal where
|
module Simplex.Chat.Terminal where
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ import Simplex.Messaging.Util (raceAny_)
|
|||||||
import UnliftIO (async, waitEither_)
|
import UnliftIO (async, waitEither_)
|
||||||
|
|
||||||
simplexChat :: WithTerminal t => ChatConfig -> ChatOpts -> t -> IO ()
|
simplexChat :: WithTerminal t => ChatConfig -> ChatOpts -> t -> IO ()
|
||||||
simplexChat cfg opts t
|
simplexChat cfg@ChatConfig {dbPoolSize, yesToMigrations} opts t
|
||||||
| logging opts = do
|
| logging opts = do
|
||||||
setLogLevel LogInfo -- LogError
|
setLogLevel LogInfo -- LogError
|
||||||
withGlobalLogging logCfg initRun
|
withGlobalLogging logCfg initRun
|
||||||
@ -27,7 +28,7 @@ simplexChat cfg opts t
|
|||||||
initRun = do
|
initRun = do
|
||||||
sendNotification' <- initializeNotifications
|
sendNotification' <- initializeNotifications
|
||||||
let f = chatStoreFile $ dbFilePrefix opts
|
let f = chatStoreFile $ dbFilePrefix opts
|
||||||
st <- createStore f $ dbPoolSize cfg
|
st <- createStore f dbPoolSize yesToMigrations
|
||||||
u <- getCreateActiveUser st
|
u <- getCreateActiveUser st
|
||||||
ct <- newChatTerminal t
|
ct <- newChatTerminal t
|
||||||
cc <- newChatController st (Just u) cfg opts sendNotification'
|
cc <- newChatController st (Just u) cfg opts sendNotification'
|
||||||
|
@ -39,6 +39,7 @@ responseToView cmd = \case
|
|||||||
CRApiChats chats -> r [sShow chats]
|
CRApiChats chats -> r [sShow chats]
|
||||||
CRApiChat chat -> r [sShow chat]
|
CRApiChat chat -> r [sShow chat]
|
||||||
CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item
|
CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item
|
||||||
|
CRChatItemUpdated _ -> []
|
||||||
CRMsgIntegrityError mErr -> viewMsgIntegrityError mErr
|
CRMsgIntegrityError mErr -> viewMsgIntegrityError mErr
|
||||||
CRCmdAccepted _ -> r []
|
CRCmdAccepted _ -> r []
|
||||||
CRChatHelp section -> case section of
|
CRChatHelp section -> case section of
|
||||||
@ -308,10 +309,10 @@ viewContactUpdated
|
|||||||
where
|
where
|
||||||
fullNameUpdate = if T.null fullName' || fullName' == n' then " removed full name" else " updated full name: " <> plain fullName'
|
fullNameUpdate = if T.null fullName' || fullName' == n' then " removed full name" else " updated full name: " <> plain fullName'
|
||||||
|
|
||||||
viewReceivedMessage :: StyledString -> CIMeta -> MsgContent -> [StyledString]
|
viewReceivedMessage :: StyledString -> CIMeta d -> MsgContent -> [StyledString]
|
||||||
viewReceivedMessage from meta mc = receivedWithTime_ from meta (ttyMsgContent mc)
|
viewReceivedMessage from meta mc = receivedWithTime_ from meta (ttyMsgContent mc)
|
||||||
|
|
||||||
receivedWithTime_ :: StyledString -> CIMeta -> [StyledString] -> [StyledString]
|
receivedWithTime_ :: StyledString -> CIMeta d -> [StyledString] -> [StyledString]
|
||||||
receivedWithTime_ from CIMeta {localItemTs, createdAt} styledMsg = do
|
receivedWithTime_ from CIMeta {localItemTs, createdAt} styledMsg = do
|
||||||
prependFirst (formattedTime <> " " <> from) styledMsg -- ++ showIntegrity mOk
|
prependFirst (formattedTime <> " " <> from) styledMsg -- ++ showIntegrity mOk
|
||||||
where
|
where
|
||||||
@ -326,13 +327,13 @@ receivedWithTime_ from CIMeta {localItemTs, createdAt} styledMsg = do
|
|||||||
else "%H:%M"
|
else "%H:%M"
|
||||||
in styleTime $ formatTime defaultTimeLocale format localTime
|
in styleTime $ formatTime defaultTimeLocale format localTime
|
||||||
|
|
||||||
viewSentMessage :: StyledString -> MsgContent -> CIMeta -> [StyledString]
|
viewSentMessage :: StyledString -> MsgContent -> CIMeta d -> [StyledString]
|
||||||
viewSentMessage to = sentWithTime_ . prependFirst to . ttyMsgContent
|
viewSentMessage to = sentWithTime_ . prependFirst to . ttyMsgContent
|
||||||
|
|
||||||
viewSentFileInvitation :: StyledString -> FileTransferId -> FilePath -> CIMeta -> [StyledString]
|
viewSentFileInvitation :: StyledString -> FileTransferId -> FilePath -> CIMeta d -> [StyledString]
|
||||||
viewSentFileInvitation to fId fPath = sentWithTime_ $ ttySentFile to fId fPath
|
viewSentFileInvitation to fId fPath = sentWithTime_ $ ttySentFile to fId fPath
|
||||||
|
|
||||||
sentWithTime_ :: [StyledString] -> CIMeta -> [StyledString]
|
sentWithTime_ :: [StyledString] -> CIMeta d -> [StyledString]
|
||||||
sentWithTime_ styledMsg CIMeta {localItemTs} =
|
sentWithTime_ styledMsg CIMeta {localItemTs} =
|
||||||
prependFirst (ttyMsgTime localItemTs <> " ") styledMsg
|
prependFirst (ttyMsgTime localItemTs <> " ") styledMsg
|
||||||
|
|
||||||
@ -371,7 +372,7 @@ sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} =
|
|||||||
sndFile :: SndFileTransfer -> StyledString
|
sndFile :: SndFileTransfer -> StyledString
|
||||||
sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName
|
sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName
|
||||||
|
|
||||||
viewReceivedFileInvitation :: StyledString -> CIMeta -> RcvFileTransfer -> [StyledString]
|
viewReceivedFileInvitation :: StyledString -> CIMeta d -> RcvFileTransfer -> [StyledString]
|
||||||
viewReceivedFileInvitation from meta ft = receivedWithTime_ from meta (receivedFileInvitation_ ft)
|
viewReceivedFileInvitation from meta ft = receivedWithTime_ from meta (receivedFileInvitation_ ft)
|
||||||
|
|
||||||
receivedFileInvitation_ :: RcvFileTransfer -> [StyledString]
|
receivedFileInvitation_ :: RcvFileTransfer -> [StyledString]
|
||||||
|
@ -48,7 +48,7 @@ extra-deps:
|
|||||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||||
# - ../simplexmq
|
# - ../simplexmq
|
||||||
- github: simplex-chat/simplexmq
|
- github: simplex-chat/simplexmq
|
||||||
commit: 137ff7043d49feb3b350f56783c9b64a62bc636a
|
commit: c9994c3a2ca945b9b67e250163cf8d560d2ed554
|
||||||
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
|
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
|
||||||
- github: simplex-chat/aeson
|
- github: simplex-chat/aeson
|
||||||
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7
|
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7
|
||||||
|
@ -75,7 +75,7 @@ cfg =
|
|||||||
|
|
||||||
virtualSimplexChat :: FilePath -> Profile -> IO TestCC
|
virtualSimplexChat :: FilePath -> Profile -> IO TestCC
|
||||||
virtualSimplexChat dbFilePrefix profile = do
|
virtualSimplexChat dbFilePrefix profile = do
|
||||||
st <- createStore (dbFilePrefix <> "_chat.db") 1
|
st <- createStore (dbFilePrefix <> "_chat.db") 1 False
|
||||||
Right user <- runExceptT $ createUser st profile True
|
Right user <- runExceptT $ createUser st profile True
|
||||||
t <- withVirtualTerminal termSettings pure
|
t <- withVirtualTerminal termSettings pure
|
||||||
ct <- newChatTerminal t
|
ct <- newChatTerminal t
|
||||||
|
@ -35,7 +35,7 @@ testChatApiNoUser = withTmpFiles $ do
|
|||||||
testChatApi :: IO ()
|
testChatApi :: IO ()
|
||||||
testChatApi = withTmpFiles $ do
|
testChatApi = withTmpFiles $ do
|
||||||
let f = chatStoreFile testDBPrefix
|
let f = chatStoreFile testDBPrefix
|
||||||
st <- createStore f 1
|
st <- createStore f 1 True
|
||||||
Right _ <- runExceptT $ createUser st aliceProfile True
|
Right _ <- runExceptT $ createUser st aliceProfile True
|
||||||
cc <- chatInit testDBPrefix
|
cc <- chatInit testDBPrefix
|
||||||
chatSendCmd cc "/u" `shouldReturn` activeUser
|
chatSendCmd cc "/u" `shouldReturn` activeUser
|
||||||
|
Loading…
Reference in New Issue
Block a user