each command takes lock if it needs it (#273)

This commit is contained in:
Evgeny Poberezkin 2022-02-06 08:21:40 +00:00 committed by GitHub
parent 5aabf87898
commit 9b67aa537a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -112,9 +112,9 @@ execChatCommand :: (MonadUnliftIO m, MonadReader ChatController m) => ByteString
execChatCommand s = case parseAll chatCommandP $ B.dropWhileEnd isSpace s of execChatCommand s = case parseAll chatCommandP $ B.dropWhileEnd isSpace s of
Left e -> pure . CRChatError . ChatError $ CECommandError e Left e -> pure . CRChatError . ChatError $ CECommandError e
Right cmd -> do Right cmd -> do
ChatController {chatLock = l, smpAgent = a, currentUser} <- ask ChatController {currentUser} <- ask
user <- readTVarIO currentUser user <- readTVarIO currentUser
withAgentLock a . withLock l $ either CRChatCmdError id <$> runExceptT (processChatCommand user cmd) either CRChatCmdError id <$> runExceptT (processChatCommand user cmd)
toView :: ChatMonad m => ChatResponse -> m () toView :: ChatMonad m => ChatResponse -> m ()
toView event = do toView event = do
@ -129,7 +129,7 @@ processChatCommand user@User {userId, profile} = \case
CTGroup -> CRApiChat . AChat SCTGroup <$> withStore (\st -> getGroupChat st user cId pagination) CTGroup -> CRApiChat . AChat SCTGroup <$> withStore (\st -> getGroupChat st user cId pagination)
CTContactRequest -> pure $ CRChatError ChatErrorNotImplemented CTContactRequest -> pure $ CRChatError ChatErrorNotImplemented
APIGetChatItems _count -> pure $ CRChatError ChatErrorNotImplemented APIGetChatItems _count -> pure $ CRChatError ChatErrorNotImplemented
APISendMessage cType chatId mc -> case cType of APISendMessage cType chatId mc -> withChatLock $ case cType of
CTDirect -> do CTDirect -> do
ct@Contact {localDisplayName = c} <- withStore $ \st -> getContact st userId chatId ct@Contact {localDisplayName = c} <- withStore $ \st -> getContact st userId chatId
ci <- sendDirectChatItem userId ct (XMsgNew mc) (CISndMsgContent mc) ci <- sendDirectChatItem userId ct (XMsgNew mc) (CISndMsgContent mc)
@ -148,7 +148,7 @@ processChatCommand user@User {userId, profile} = \case
withStore (\st -> getContactGroupNames st userId ct) >>= \case withStore (\st -> getContactGroupNames st userId ct) >>= \case
[] -> do [] -> do
conns <- withStore $ \st -> getContactConnections st userId ct conns <- withStore $ \st -> getContactConnections st userId ct
procCmd $ do withChatLock . procCmd $ do
withAgent $ \a -> forM_ conns $ \conn -> withAgent $ \a -> forM_ conns $ \conn ->
deleteConnection a (aConnId conn) `catchError` \(_ :: AgentErrorType) -> pure () deleteConnection a (aConnId conn) `catchError` \(_ :: AgentErrorType) -> pure ()
withStore $ \st -> deleteContact st userId ct withStore $ \st -> deleteContact st userId ct
@ -160,11 +160,11 @@ processChatCommand user@User {userId, profile} = \case
APIAcceptContact connReqId -> do APIAcceptContact connReqId -> do
UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = p} <- withStore $ \st -> UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = p} <- withStore $ \st ->
getContactRequest st userId connReqId getContactRequest st userId connReqId
procCmd $ do withChatLock . procCmd $ do
connId <- withAgent $ \a -> acceptContact a invId . directMessage $ XInfo profile connId <- withAgent $ \a -> acceptContact a invId . directMessage $ XInfo profile
acceptedContact <- withStore $ \st -> createAcceptedContact st userId connId cName profileId p acceptedContact <- withStore $ \st -> createAcceptedContact st userId connId cName profileId p
pure $ CRAcceptingContactRequest acceptedContact pure $ CRAcceptingContactRequest acceptedContact
APIRejectContact connReqId -> do APIRejectContact connReqId -> withChatLock $ do
cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <- cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <-
withStore $ \st -> withStore $ \st ->
getContactRequest st userId connReqId getContactRequest st userId connReqId
@ -173,29 +173,29 @@ processChatCommand user@User {userId, profile} = \case
pure $ CRContactRequestRejected cReq pure $ CRContactRequestRejected cReq
ChatHelp section -> pure $ CRChatHelp section ChatHelp section -> pure $ CRChatHelp section
Welcome -> pure $ CRWelcome user Welcome -> pure $ CRWelcome user
AddContact -> procCmd $ do AddContact -> withChatLock . procCmd $ do
(connId, cReq) <- withAgent (`createConnection` SCMInvitation) (connId, cReq) <- withAgent (`createConnection` SCMInvitation)
withStore $ \st -> createDirectConnection st userId connId withStore $ \st -> createDirectConnection st userId connId
pure $ CRInvitation cReq pure $ CRInvitation cReq
Connect (Just (ACR SCMInvitation cReq)) -> procCmd $ do Connect (Just (ACR SCMInvitation cReq)) -> withChatLock . procCmd $ do
connect cReq $ XInfo profile connect cReq $ XInfo profile
pure CRSentConfirmation pure CRSentConfirmation
Connect (Just (ACR SCMContact cReq)) -> procCmd $ do Connect (Just (ACR SCMContact cReq)) -> withChatLock . procCmd $ do
connect cReq $ XContact profile Nothing connect cReq $ XContact profile Nothing
pure CRSentInvitation pure CRSentInvitation
Connect Nothing -> throwChatError CEInvalidConnReq Connect Nothing -> throwChatError CEInvalidConnReq
ConnectAdmin -> procCmd $ do ConnectAdmin -> withChatLock . procCmd $ do
connect adminContactReq $ XContact profile Nothing connect adminContactReq $ XContact profile Nothing
pure CRSentInvitation pure CRSentInvitation
DeleteContact cName -> do DeleteContact cName -> do
contactId <- withStore $ \st -> getContactIdByName st userId cName contactId <- withStore $ \st -> getContactIdByName st userId cName
processChatCommand user $ APIDeleteChat CTDirect contactId processChatCommand user $ APIDeleteChat CTDirect contactId
ListContacts -> CRContactsList <$> withStore (`getUserContacts` user) ListContacts -> CRContactsList <$> withStore (`getUserContacts` user)
CreateMyAddress -> procCmd $ do CreateMyAddress -> withChatLock . procCmd $ do
(connId, cReq) <- withAgent (`createConnection` SCMContact) (connId, cReq) <- withAgent (`createConnection` SCMContact)
withStore $ \st -> createUserContactLink st userId connId cReq withStore $ \st -> createUserContactLink st userId connId cReq
pure $ CRUserContactLinkCreated cReq pure $ CRUserContactLinkCreated cReq
DeleteMyAddress -> do DeleteMyAddress -> withChatLock $ do
conns <- withStore $ \st -> getUserContactLinkConnections st userId conns <- withStore $ \st -> getUserContactLinkConnections st userId
procCmd $ do procCmd $ do
withAgent $ \a -> forM_ conns $ \conn -> withAgent $ \a -> forM_ conns $ \conn ->
@ -216,7 +216,7 @@ processChatCommand user@User {userId, profile} = \case
NewGroup gProfile -> do NewGroup gProfile -> do
gVar <- asks idsDrg gVar <- asks idsDrg
CRGroupCreated <$> withStore (\st -> createNewGroup st gVar user gProfile) CRGroupCreated <$> withStore (\st -> createNewGroup st gVar user gProfile)
AddMember gName cName memRole -> do AddMember gName cName memRole -> withChatLock $ do
-- TODO for large groups: no need to load all members to determine if contact is a member -- TODO for large groups: no need to load all members to determine if contact is a member
(group, contact) <- withStore $ \st -> (,) <$> getGroupByName st user gName <*> getContactByName st userId cName (group, contact) <- withStore $ \st -> (,) <$> getGroupByName st user gName <*> getContactByName st userId cName
let Group gInfo@GroupInfo {groupId, groupProfile, membership} members = group let Group gInfo@GroupInfo {groupId, groupProfile, membership} members = group
@ -243,7 +243,7 @@ processChatCommand user@User {userId, profile} = \case
| otherwise -> throwChatError $ CEGroupDuplicateMember cName | otherwise -> throwChatError $ CEGroupDuplicateMember cName
JoinGroup gName -> do JoinGroup gName -> do
ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g} <- withStore $ \st -> getGroupInvitation st user gName ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g} <- withStore $ \st -> getGroupInvitation st user gName
procCmd $ do withChatLock . procCmd $ do
agentConnId <- withAgent $ \a -> joinConnection a connRequest . directMessage . XGrpAcpt $ memberId (membership g :: GroupMember) agentConnId <- withAgent $ \a -> joinConnection a connRequest . directMessage . XGrpAcpt $ memberId (membership g :: GroupMember)
withStore $ \st -> do withStore $ \st -> do
createMemberConnection st userId fromMember agentConnId createMemberConnection st userId fromMember agentConnId
@ -258,14 +258,14 @@ processChatCommand user@User {userId, profile} = \case
Just m@GroupMember {memberId = mId, memberRole = mRole, memberStatus = mStatus} -> do Just m@GroupMember {memberId = mId, memberRole = mRole, memberStatus = mStatus} -> do
let userRole = memberRole (membership :: GroupMember) let userRole = memberRole (membership :: GroupMember)
when (userRole < GRAdmin || userRole < mRole) $ throwChatError CEGroupUserRole when (userRole < GRAdmin || userRole < mRole) $ throwChatError CEGroupUserRole
procCmd $ do withChatLock . procCmd $ do
when (mStatus /= GSMemInvited) . void . sendGroupMessage members $ XGrpMemDel mId when (mStatus /= GSMemInvited) . void . sendGroupMessage members $ XGrpMemDel mId
deleteMemberConnection m deleteMemberConnection m
withStore $ \st -> updateGroupMemberStatus st userId m GSMemRemoved withStore $ \st -> updateGroupMemberStatus st userId m GSMemRemoved
pure $ CRUserDeletedMember gInfo m pure $ CRUserDeletedMember gInfo m
LeaveGroup gName -> do LeaveGroup gName -> do
Group gInfo@GroupInfo {membership} members <- withStore $ \st -> getGroupByName st user gName Group gInfo@GroupInfo {membership} members <- withStore $ \st -> getGroupByName st user gName
procCmd $ do withChatLock . procCmd $ do
void $ sendGroupMessage members XGrpLeave void $ sendGroupMessage members XGrpLeave
mapM_ deleteMemberConnection members mapM_ deleteMemberConnection members
withStore $ \st -> updateGroupMemberStatus st userId membership GSMemLeft withStore $ \st -> updateGroupMemberStatus st userId membership GSMemLeft
@ -277,7 +277,7 @@ processChatCommand user@User {userId, profile} = \case
memberRole (membership :: GroupMember) == GROwner memberRole (membership :: GroupMember) == GROwner
|| (s == GSMemRemoved || s == GSMemLeft || s == GSMemGroupDeleted || s == GSMemInvited) || (s == GSMemRemoved || s == GSMemLeft || s == GSMemGroupDeleted || s == GSMemInvited)
unless canDelete $ throwChatError CEGroupUserRole unless canDelete $ throwChatError CEGroupUserRole
procCmd $ do withChatLock . procCmd $ do
when (memberActive membership) . void $ sendGroupMessage members XGrpDel when (memberActive membership) . void $ sendGroupMessage members XGrpDel
mapM_ deleteMemberConnection members mapM_ deleteMemberConnection members
withStore $ \st -> deleteGroup st user g withStore $ \st -> deleteGroup st user g
@ -288,7 +288,7 @@ processChatCommand user@User {userId, profile} = \case
groupId <- withStore $ \st -> getGroupIdByName st user gName groupId <- withStore $ \st -> getGroupIdByName st user gName
let mc = MCText $ safeDecodeUtf8 msg let mc = MCText $ safeDecodeUtf8 msg
processChatCommand user $ APISendMessage CTGroup groupId mc processChatCommand user $ APISendMessage CTGroup groupId mc
SendFile cName f -> do SendFile cName f -> withChatLock $ do
(fileSize, chSize) <- checkSndFile f (fileSize, chSize) <- checkSndFile f
contact <- withStore $ \st -> getContactByName st userId cName contact <- withStore $ \st -> getContactByName st userId cName
(agentConnId, fileConnReq) <- withAgent (`createConnection` SCMInvitation) (agentConnId, fileConnReq) <- withAgent (`createConnection` SCMInvitation)
@ -299,7 +299,7 @@ processChatCommand user@User {userId, profile} = \case
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 -> do SendGroupFile gName f -> withChatLock $ do
(fileSize, chSize) <- checkSndFile f (fileSize, chSize) <- checkSndFile f
Group gInfo@GroupInfo {membership} members <- withStore $ \st -> getGroupByName st user gName Group gInfo@GroupInfo {membership} members <- withStore $ \st -> getGroupByName st user gName
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
@ -322,7 +322,7 @@ processChatCommand user@User {userId, profile} = \case
ReceiveFile fileId filePath_ -> do ReceiveFile fileId filePath_ -> do
ft@RcvFileTransfer {fileInvitation = FileInvitation {fileName, fileConnReq}, fileStatus} <- withStore $ \st -> getRcvFileTransfer st userId fileId ft@RcvFileTransfer {fileInvitation = FileInvitation {fileName, fileConnReq}, fileStatus} <- withStore $ \st -> getRcvFileTransfer st userId fileId
unless (fileStatus == RFSNew) . throwChatError $ CEFileAlreadyReceiving fileName unless (fileStatus == RFSNew) . throwChatError $ CEFileAlreadyReceiving fileName
procCmd $ do withChatLock . procCmd $ do
tryError (withAgent $ \a -> joinConnection a fileConnReq . directMessage $ XFileAcpt fileName) >>= \case tryError (withAgent $ \a -> joinConnection a fileConnReq . directMessage $ XFileAcpt fileName) >>= \case
Right agentConnId -> do Right agentConnId -> do
filePath <- getRcvFilePath fileId filePath_ fileName filePath <- getRcvFilePath fileId filePath_ fileName
@ -333,7 +333,7 @@ processChatCommand user@User {userId, profile} = \case
Left e -> throwError e Left e -> throwError e
CancelFile fileId -> do CancelFile fileId -> do
ft' <- withStore (\st -> getFileTransfer st userId fileId) ft' <- withStore (\st -> getFileTransfer st userId fileId)
procCmd $ case ft' of withChatLock . procCmd $ case ft' of
FTSnd fts -> do FTSnd fts -> do
forM_ fts $ \ft -> cancelSndFileTransfer ft forM_ fts $ \ft -> cancelSndFileTransfer ft
pure $ CRSndGroupFileCancelled fts pure $ CRSndGroupFileCancelled fts
@ -350,12 +350,15 @@ processChatCommand user@User {userId, profile} = \case
let user' = (user :: User) {localDisplayName = displayName, profile = p} let user' = (user :: User) {localDisplayName = displayName, profile = p}
asks currentUser >>= atomically . (`writeTVar` user') asks currentUser >>= atomically . (`writeTVar` user')
contacts <- withStore (`getUserContacts` user) contacts <- withStore (`getUserContacts` user)
procCmd $ do withChatLock . procCmd $ do
forM_ contacts $ \ct -> sendDirectMessage (contactConn ct) $ XInfo p forM_ contacts $ \ct -> sendDirectMessage (contactConn ct) $ XInfo p
pure $ CRUserProfileUpdated profile p pure $ CRUserProfileUpdated profile p
QuitChat -> liftIO exitSuccess QuitChat -> liftIO exitSuccess
ShowVersion -> pure CRVersionInfo ShowVersion -> pure CRVersionInfo
where where
withChatLock action = do
ChatController {chatLock = l, smpAgent = a} <- ask
withAgentLock a . withLock l $ action
-- below code would make command responses asynchronous where they can be slow -- below code would make command responses asynchronous where they can be slow
-- in View.hs `r'` should be defined as `id` in this case -- in View.hs `r'` should be defined as `id` in this case
-- procCmd :: m ChatResponse -> m ChatResponse -- procCmd :: m ChatResponse -> m ChatResponse