mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-05 14:57:42 +00:00
core: mark forwarded group files without description as unavailable on connection to sender
This commit is contained in:
1 parent
1d0247b7dd
commit
ba3e777ccd
5 files changed
+187
-8
No files matched your search
@@ -0,0 +1,75 @@
|
||||
# Group file stuck on "waiting for sender to complete upload"
|
||||
|
||||
## Problem
|
||||
|
||||
In a regular (non-relay) group, a received file can stay forever on "waiting for sender to complete upload", while the sender shows it as uploaded and later files from the same sender arrive normally.
|
||||
|
||||
Observed in a 3-member group (A host, B receiver, C a member who had just joined):
|
||||
|
||||
| Time | Received by B | Path |
|
||||
|---|---|---|
|
||||
| 18:55:49 | `x.grp.mem.new` (C joined) | from host A |
|
||||
| 18:55:54 | `x.grp.mem.fwd` (introduction to C) | from host A |
|
||||
| 18:56:00 | `x.msg.new` with file 2 from C | forwarded by host A |
|
||||
| 18:56:03 | own connection to C ready; `x.grp.mem.con` sent to host A | direct connection; report to host |
|
||||
| 18:57:11 / 18:57:14 | `x.msg.new` + `x.msg.file.descr` for file 3 | direct from C |
|
||||
| 18:57:26 / 18:57:34 | `x.msg.new` + `x.msg.file.descr` for file 4 | direct from C |
|
||||
|
||||
No `x.msg.file.descr` for file 2 ever arrived, neither directly nor forwarded. In B's database, file 2's description row is empty (part 0, not complete), the file is `accepted` and no agent download exists.
|
||||
|
||||
## Cause
|
||||
|
||||
The description of a group XFTP file is sent only after the upload completes, separately from the file invitation:
|
||||
|
||||
1. When the sender sends the file, it creates per-member transfer records only for members with a ready connection: `xftpSndFileTransfer` in `Commands.hs` ("we are not sending files to pending members"). B was not connected to C yet, so B got no record. The host forwarded the invitation to B, because the host forwards a member's messages to the members that member is not yet connected to.
|
||||
2. When the upload completes (`SFDONE` in `Subscriber.hs`), the sender sends descriptions only to members that have a record and a ready connection (`memberFTs`). The spare descriptions are saved in `extra_xftp_file_descriptions`, which nothing reads.
|
||||
3. The host forwards the description it received to members not connected to the sender (`testGroupMsgForwardFile` covers this). But when B and C connected (18:56:03), on `x.grp.mem.con` the host marked them connected (`xGrpMemCon` → `setMemberVectorRelationConnected`), so it stopped forwarding C's messages to B. That included the description sent after the upload.
|
||||
|
||||
If the two members connect between sending the file and completing its upload, the description is delivered by neither path, and the receiver remains in the waiting state indefinitely.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Receiver: mark and restore (this change).** When the receiver's own connection to the author becomes ready, mark the author's forwarded files that have no description part as unavailable. Receiver-only, no protocol or schema change. Does not deliver the file, but does not leave it in the waiting state in the observed case (see Limitations for the cases it does not cover).
|
||||
2. **Sender: send the spare description** directly to members that became connected after sending. Delivers the file, but if the host also forwarded its own description in the race, the receiver can get two descriptions. For multi-part descriptions, `appendRcvFD` can then join parts from both, so this needs a receiver-side guard too.
|
||||
3. **Host: keep forwarding the description** to members it forwarded the invitation to, even after they connected. Breaks the rule that the host forwards only between unconnected members, needs per-file tracking of forward recipients, and helps only once hosts upgrade.
|
||||
|
||||
## Change
|
||||
|
||||
1. `setFwdRcvFilesUnavailable` (`Store/Files.hs`), in one statement, sets the member's received XFTP files in the group to `CIFSRcvError (FileErrOther "file was sent before you connected to the sender")` and returns them, if they:
|
||||
- were not cancelled and not prohibited as too large (`file_max_size`), with status `new` or `accepted`,
|
||||
- have a chat item with `forwarded_by_group_member_id` set, and no message from the author with this shared message ID recorded as received directly (`messages.forwarded_by_group_member_id` cleared, see change 5); this is a NOT EXISTS, so it still marks files whose `messages` rows were deleted by the 30-day message cleanup,
|
||||
- have no description part (no row, or part 0).
|
||||
2. `markFwdFilesUnavailable` (`Library/Internal.hs`) calls it and sends `CEvtChatItemUpdated` for each marked file. The apps already show this error state ("Error: …" on iOS). `rcv_files` status, `cancelled` and `to_receive` are kept, so a later description can still be processed.
|
||||
3. It is called in `Subscriber.hs` when the connection with an introduced member (`GCPreMember`/`GCPostMember`) becomes ready, next to `notifyMemberConnected`, in regular groups only (not `useRelays'`). Errors are reported and do not interrupt the connection handling.
|
||||
4. Restore in `processFDMessage`, when a description arrives after marking: in the same transaction that appends the last part, a marked file is unmarked (`unsetRcvFileUnavailable`: if the item has the marker, it is set to accepted or invitation by `rcv_files` status; on Postgres the marked `files` row is locked first, so that `rcv_files` status is read after a concurrent accept or cancel commits), so the unmark is not lost if the process stops before handling the result. `CEvtRcvFileDescrReady` contains the unmarked item. Then, as before the change, a prohibited file is reset to an invitation that the apps do not allow to be received, and otherwise:
|
||||
- accepted file: receiving starts through the existing `receiveViaCompleteFD` path, as for any accepted file (a redirect cancels it) (`startReceivingFile` sets the item status to receiving; with unapproved relays it is reset to an invitation, an oversized description leaves it accepted without receiving);
|
||||
- file not accepted (`RFSNew`): the item is an invitation, so the user can receive it.
|
||||
5. When the author's direct copy of a forwarded `x.msg.new` with a file arrives (`saveGroupRcvMsg`, duplicate forwarded message, from the same author), `messages.forwarded_by_group_member_id` of the stored message is cleared and the file is unmarked if it was already marked, in one transaction, before the existing `x.grp.mem.con` is sent to the forwarding member. If the process stops in between, processing the redelivered copy does not send `x.grp.mem.con` again; the host still gets it from both members when their connection becomes ready. Messages without a file are not changed. The author sent to the user directly only if its connection was ready (`ConnSndReady` is enough) when sending, so it has the transfer record and sends the description when the upload completes, possibly much later, unless its connection to the user is inactive at that moment (see Limitations). In an introduction, the new member creates the invitation (`xGrpMemIntro`, received only from its host) and the existing member joins it (`xGrpMemFwd`), so only the existing member becomes `ConnSndReady` before its CON. When the author is the existing member and the user the new one, the author can send directly as soon as it joined, and its messages follow its confirmation, so the direct copy can arrive before the user's CON and before marking; this is why it is recorded on the message rather than only unmarking. When the author is the new member (the observed case), it sends directly only after its own CON. The item status goes back to accepted or invitation, matching `rcv_files` status. `messages.forwarded_by_group_member_id` is otherwise read only by deduplication, where "received directly" is the correct meaning.
|
||||
6. Accepting a marked file (`acceptFileReceive`: user, bot, app auto-receive, or `startReceiveUserFiles` for files flagged by the iOS notification service) records the acceptance and keeps the error status, so a later description starts receiving and an acceptance after marking does not return the item to the waiting state. This is a conditional update in `acceptRcvFT_` (`CASE WHEN ci_file_status = <marker>`), atomic with marking on both SQLite and Postgres; other accept paths never have this status.
|
||||
7. `CancelFile` of a marked XFTP file keeps the marker instead of resetting the item to an invitation (`resetRcvXFTPFileStatus`, a conditional update like in change 6), so cancelling and accepting again does not return the item to the waiting state.
|
||||
8. Every change of the marker is a single conditional statement or is in one transaction with the condition it depends on: marking (change 1), unmarking (changes 4 and 5), accepting (change 6) and cancelling (change 7). Within one process, marking, the description and the direct copy are processed under the group lock; `ReceiveFile`, `SetFileToReceive`, `CancelFile` and `startReceiveUserFiles` do not take it, and their writes keep the marker. On iOS the app and the notification service extension can process the same file at the same time while both processes use the same database, and SQLite transactions serialize their writes.
|
||||
9. `cancelFilesInProgress` does not treat this error as ended, so deleting or moderating the item still cancels the file, as it did before marking; otherwise a description arriving afterwards would start receiving the file of a deleted item.
|
||||
|
||||
## Why the connection moment
|
||||
|
||||
- Before the connection, the receiving client cannot determine whether a description will follow. Forwarded descriptions are normal: the host forwards its own description to unconnected members, and group history sends a forwarded invitation followed by forwarded description parts. `GrpMsgForward` has no field that distinguishes history from live forwarding.
|
||||
- After the connection, the author's messages come directly, and a description for a file forwarded before the connection can still arrive in two cases:
|
||||
- the host forwarded it before processing `x.grp.mem.con` (its delivery job reads member relations when it runs, so with the host offline this can take a long time) — restoring covers it;
|
||||
- the author's side of the connection was ready before the user's (`ConnSndReady`), so it sent the file directly as well as via the host, and will send the description directly when the upload completes — the direct duplicate prevents or reverts marking (change 5), and restoring covers the description.
|
||||
- History is sent by the host after `introduceToAll`, on the same connection to the new member. Description parts are sent only for files with a complete description (`invCompleteDescr`); a signed item with text is replayed with the author's original invitation even without one (an item without text and without a complete description is not sent), and such a file is marked at connection and restored if a description is forwarded later. Usually it is processed before the new member connects with the introduced authors, which takes several round trips through the host, but this is not guaranteed for long history split into many batches. If the connection comes between a history invitation and its description, restoring covers it.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Marking and unmarking emit `CEvtChatItemUpdated`, as the existing "file digest" and "redirect not allowed" file errors and edits of old messages do, but without a user action. The apps handle an update of an item they have not loaded (`upsertChatItem`) like a new item: it can be added at the bottom of an open chat or become the chat preview, and a notification can be shown for it, depending on platform, app state and active user. So a marked or restored old file message can be shown this way. This is app-side behaviour for all item updates and is addressed separately.
|
||||
- The CLI shows a marked or unmarked item as an updated message without the file status (`viewItemUpdate` ignores it, as for other file status updates); API clients get the status.
|
||||
- The file is not delivered: the user sees an error instead of an endless wait. Delivering it needs option 2 or 3.
|
||||
- A forwarded invitation that arrives after the receiver's own connection to the author is already ready (forwarded by the host before it learned of the connection, delivered late) is not marked, and remains in the waiting state, as before. This is a smaller window than the one fixed here.
|
||||
- Files from authors whose connection was ready before this change are not revisited: marking happens only when a connection becomes ready.
|
||||
- If the forwarding host's member record is deleted before the connection (the host removed with messages), the items no longer reference a forwarding member, so the file is not marked and remains in the waiting state, as before.
|
||||
- A multi-part description of which only some parts were forwarded before the host stopped forwarding (`part_no > 0`, incomplete) is not marked, and remains in the waiting state, as before. This needs a description longer than one part (very large files) and the connection report to be processed between the parts.
|
||||
- If the author's connection to the user is inactive (quota) when its upload completes, `memberFTs` skips it and the description is not sent, although the transfer record exists and the direct `x.msg.new` (sent at once, or later from the pending queue) unmarks the file or prevents marking. The file then remains in the waiting state, as before this change; this is a sender-side loss.
|
||||
- A file that restoring resets to an invitation is not auto-received by Android and desktop apps, which auto-receive only new items; iOS receives it only if the notification service flagged it (`to_receive`) and chat is started or activated within 2 days of the invitation (`startReceiveUserFiles`).
|
||||
|
||||
## Compatibility
|
||||
|
||||
No protocol, API or database schema changes. The marker is an existing file status with an error text, so all app versions display it.
|
||||
@@ -3602,7 +3602,9 @@ processChatCommand cxt nm = \case
|
||||
liftIO $ removeFile fsFilePath `catchAll_` pure ()
|
||||
lift . forM_ agentRcvFileId $ \(AgentRcvFileId aFileId) ->
|
||||
withAgent' (`xftpDeleteRcvFile` aFileId)
|
||||
aci_ <- resetRcvCIFileStatus user fileId CIFSRcvInvitation
|
||||
aci_ <- withStore $ \db -> do
|
||||
liftIO $ resetRcvXFTPFileStatus db user fileId
|
||||
lookupChatItemByFileId db cxt user fileId
|
||||
pure $ CRRcvFileCancelled user aci_ ftr
|
||||
FileStatus fileId -> withUser $ \user -> do
|
||||
withFastStore (\db -> lookupChatItemByFileId db cxt user fileId) >>= \case
|
||||
|
||||
@@ -511,6 +511,7 @@ cancelFilesInProgress user filesInfo = do
|
||||
lift $ agentXFTPDeleteRcvFiles xrfIds
|
||||
where
|
||||
fileEnded CIFileInfo {fileStatus} = case fileStatus of
|
||||
Just (AFS SMDRcv (CIFSRcvError e)) | e == fwdFileUnavailableError -> False
|
||||
Just (AFS _ status) -> ciFileEnded status
|
||||
Nothing -> True
|
||||
getFT :: DB.Connection -> CIFileInfo -> IO (Either ChatError FileTransfer)
|
||||
@@ -918,6 +919,26 @@ resetRcvCIFileStatus user fileId ciFileStatus = do
|
||||
updateRcvFileAgentId db fileId Nothing
|
||||
lookupChatItemByFileId db cxt user fileId
|
||||
|
||||
-- The sender sends file descriptions only to the members it was connected to when it sent the file,
|
||||
-- and the host stops forwarding the sender's messages once the members connect,
|
||||
-- so a description that was not forwarded before connection usually does not arrive.
|
||||
-- The file is not cancelled, so a description that still arrives is processed.
|
||||
markFwdFilesUnavailable :: User -> GroupMember -> CM ()
|
||||
markFwdFilesUnavailable user m = do
|
||||
cxt <- chatStoreCxt
|
||||
fileIds <- withStore' $ \db -> setFwdRcvFilesUnavailable db user m
|
||||
forM_ fileIds $ \fileId ->
|
||||
withStore (\db -> lookupChatItemByFileId db cxt user fileId) >>= mapM_ (toView . CEvtChatItemUpdated user)
|
||||
|
||||
-- The author also sent the message directly, so it has the file transfer for the user and should send the description.
|
||||
setFwdMsgReceivedDirectly :: User -> GroupId -> GroupMemberId -> SharedMsgId -> CM ()
|
||||
setFwdMsgReceivedDirectly user@User {userId} groupId authorGroupMemberId sharedMsgId = do
|
||||
cxt <- chatStoreCxt
|
||||
aci_ <- withStore $ \db -> do
|
||||
liftIO $ setGroupMsgReceivedDirectly db groupId authorGroupMemberId sharedMsgId
|
||||
liftIO (eitherToMaybe <$> runExceptT (getGroupFileIdBySharedMsgId db userId groupId sharedMsgId)) $>>= unsetRcvFileUnavailable db cxt user
|
||||
forM_ aci_ $ toView . CEvtChatItemUpdated user
|
||||
|
||||
receiveViaURI :: User -> FileDescriptionURI -> CryptoFile -> CM RcvFileTransfer
|
||||
receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do
|
||||
fileId <- withStore $ \db -> createRcvStandaloneFileTransfer db userId cf fileSize chunkSize
|
||||
@@ -2940,9 +2961,12 @@ saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta
|
||||
msg <-
|
||||
withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery $ Just amGroupMemId)
|
||||
`catchAllErrors` \e -> case e of
|
||||
ChatErrorStore (SEDuplicateGroupMessage _ _ _ forwardedByGroupMemberId_) -> do
|
||||
ChatErrorStore (SEDuplicateGroupMessage _ sharedMsgId duplAuthorId_ forwardedByGroupMemberId_) -> do
|
||||
unlessM isMainApp $ toView $ CEvtDuplicateGroupMessage user am'
|
||||
forM_ forwardedByGroupMemberId_ $ \forwardedByGroupMemberId -> do
|
||||
case chatMsgEvent of
|
||||
XMsgNew MsgContainer {file = Just _} | duplAuthorId_ == Just amGroupMemId -> setFwdMsgReceivedDirectly user groupId amGroupMemId sharedMsgId `catchAllErrors` eToView
|
||||
_ -> pure ()
|
||||
cxt <- chatStoreCxt
|
||||
fm <- withStore $ \db -> getGroupMember db cxt user groupId forwardedByGroupMemberId
|
||||
forM_ (memberConn fm) $ \fmConn ->
|
||||
|
||||
@@ -986,6 +986,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
|
||||
_ -> do
|
||||
unless (memberPending m) $ withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected
|
||||
notifyMemberConnected gInfo m Nothing
|
||||
unless (useRelays' gInfo) $ markFwdFilesUnavailable user m `catchAllErrors` eToView
|
||||
let memCategory = memberCategory m
|
||||
connectedIncognito = memberIncognito membership
|
||||
when (memCategory == GCPreMember) $
|
||||
@@ -1995,14 +1996,17 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
|
||||
processFDMessage binding_ fileId aci fileDescr fileExpires fileBadge = do
|
||||
ft <- withStore $ \db -> getRcvFileTransfer db user fileId
|
||||
unless (rcvFileCompleteOrCancelled ft) $ do
|
||||
(rfd@RcvFileDescr {fileDescrComplete}, ft'@RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs, fileProhibited, fileInvitation = FileInvitation {fileSize}}) <- withStore $ \db -> do
|
||||
rfd <- appendRcvFD db userId fileId fileDescr
|
||||
(rfd@RcvFileDescr {fileDescrComplete}, ft'@RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs, fileProhibited, fileInvitation = FileInvitation {fileSize}}, unmarked_) <- withStore $ \db -> do
|
||||
rfd@RcvFileDescr {fileDescrComplete = complete} <- appendRcvFD db userId fileId fileDescr
|
||||
forM_ fileExpires $ liftIO . setFileExpiration db user fileId
|
||||
-- unmarked in the same transaction as appending the last part, so that it is not lost if the process stops
|
||||
unmarked_ <- if complete then unsetRcvFileUnavailable db cxt user fileId else pure Nothing
|
||||
-- reading second time in the same transaction as appending description
|
||||
-- to prevent race condition with accept
|
||||
ft' <- getRcvFileTransfer db user fileId
|
||||
pure (rfd, ft')
|
||||
when fileDescrComplete $ toView $ CEvtRcvFileDescrReady user aci ft' rfd
|
||||
pure (rfd, ft', unmarked_)
|
||||
forM_ unmarked_ $ toView . CEvtChatItemUpdated user
|
||||
when fileDescrComplete $ toView $ CEvtRcvFileDescrReady user (fromMaybe aci unmarked_) ft' rfd
|
||||
maxSize <- asks $ noBadge . fileSizeLimits . config
|
||||
let descrBadgeRequired = fileDescrComplete && fileSize > maxSize && isNothing fileProhibited
|
||||
prohibited_ <- case fileBadge of
|
||||
|
||||
@@ -52,6 +52,11 @@ module Simplex.Chat.Store.Files
|
||||
createRcvStandaloneFileTransfer,
|
||||
appendRcvFD,
|
||||
getRcvFileDescrByRcvFileId,
|
||||
fwdFileUnavailableError,
|
||||
setFwdRcvFilesUnavailable,
|
||||
unsetRcvFileUnavailable,
|
||||
resetRcvXFTPFileStatus,
|
||||
setGroupMsgReceivedDirectly,
|
||||
getRcvFileDescrBySndFileId,
|
||||
updateRcvFileAgentId,
|
||||
getRcvFileTransferById,
|
||||
@@ -651,6 +656,75 @@ getRcvFileDescrByRcvFileId_ db fileId =
|
||||
|]
|
||||
(Only fileId)
|
||||
|
||||
fwdFileUnavailableError :: FileError
|
||||
fwdFileUnavailableError = FileErrOther "file was sent before you connected to the sender"
|
||||
|
||||
setFwdRcvFilesUnavailable :: DB.Connection -> User -> GroupMember -> IO [FileTransferId]
|
||||
setFwdRcvFilesUnavailable db User {userId} GroupMember {groupId, groupMemberId} = do
|
||||
currentTs <- getCurrentTime
|
||||
map fromOnly
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE files SET ci_file_status = ?, updated_at = ?
|
||||
WHERE COALESCE(cancelled, 0) = 0 AND file_max_size IS NULL
|
||||
AND file_id IN (
|
||||
SELECT f.file_id
|
||||
FROM rcv_files r
|
||||
CROSS JOIN files f
|
||||
JOIN chat_items i ON i.chat_item_id = f.chat_item_id
|
||||
LEFT JOIN xftp_file_descriptions d ON d.file_descr_id = r.file_descr_id
|
||||
WHERE f.file_id = r.file_id AND f.user_id = ? AND f.group_id = ? AND r.group_member_id = ?
|
||||
AND f.protocol = ? AND r.file_status IN (?,?)
|
||||
AND i.forwarded_by_group_member_id IS NOT NULL
|
||||
AND (d.file_descr_id IS NULL OR d.file_descr_part_no = 0)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM messages m
|
||||
WHERE m.group_id = f.group_id AND m.shared_msg_id = i.shared_msg_id
|
||||
AND m.author_group_member_id = r.group_member_id AND m.forwarded_by_group_member_id IS NULL
|
||||
)
|
||||
)
|
||||
RETURNING file_id
|
||||
|]
|
||||
((CIFSRcvError fwdFileUnavailableError, currentTs, userId, groupId, groupMemberId) :. (FPXFTP, FSNew, FSAccepted))
|
||||
|
||||
-- on Postgres the marked row is locked first, so that rcv_files status is read after a concurrent accept or cancel commits
|
||||
unsetRcvFileUnavailable :: DB.Connection -> StoreCxt -> User -> FileTransferId -> ExceptT StoreError IO (Maybe AChatItem)
|
||||
unsetRcvFileUnavailable db cxt user@User {userId} fileId = do
|
||||
marked :: [Only Int] <-
|
||||
liftIO $
|
||||
DB.query
|
||||
db
|
||||
( "SELECT 1 FROM files WHERE user_id = ? AND file_id = ? AND ci_file_status = ?"
|
||||
#if defined(dbPostgres)
|
||||
<> " FOR UPDATE"
|
||||
#endif
|
||||
)
|
||||
(userId, fileId, CIFSRcvError fwdFileUnavailableError)
|
||||
if null marked
|
||||
then pure Nothing
|
||||
else do
|
||||
status_ <- liftIO $ maybeFirstRow fromOnly $ DB.query db "SELECT file_status FROM rcv_files WHERE file_id = ?" (Only fileId)
|
||||
liftIO $ updateCIFileStatus db user fileId $ if status_ == Just FSAccepted then CIFSRcvAccepted else CIFSRcvInvitation
|
||||
Just <$> getChatItemByFileId db cxt user fileId
|
||||
|
||||
resetRcvXFTPFileStatus :: DB.Connection -> User -> FileTransferId -> IO ()
|
||||
resetRcvXFTPFileStatus db User {userId} fileId = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE files SET ci_file_status = CASE WHEN ci_file_status = ? THEN ci_file_status ELSE ? END, updated_at = ? WHERE user_id = ? AND file_id = ?"
|
||||
(CIFSRcvError fwdFileUnavailableError, CIFSRcvInvitation, currentTs, userId, fileId)
|
||||
updateRcvFileStatus db fileId FSNew
|
||||
updateRcvFileAgentId db fileId Nothing
|
||||
|
||||
setGroupMsgReceivedDirectly :: DB.Connection -> GroupId -> GroupMemberId -> SharedMsgId -> IO ()
|
||||
setGroupMsgReceivedDirectly db groupId authorGroupMemberId sharedMsgId =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE messages SET forwarded_by_group_member_id = NULL WHERE group_id = ? AND shared_msg_id = ? AND author_group_member_id = ?"
|
||||
(groupId, sharedMsgId, authorGroupMemberId)
|
||||
|
||||
getRcvFileDescrBySndFileId :: DB.Connection -> FileTransferId -> ExceptT StoreError IO RcvFileDescr
|
||||
getRcvFileDescrBySndFileId db fileId = do
|
||||
liftIO (getRcvFileDescrBySndFileId_ db fileId) >>= \case
|
||||
@@ -771,8 +845,8 @@ acceptRcvFT_ :: DB.Connection -> User -> FileTransferId -> FilePath -> Bool -> M
|
||||
acceptRcvFT_ db User {userId} fileId filePath userApprovedRelays rcvFileInline currentTs = do
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE files SET file_path = ?, ci_file_status = ?, updated_at = ? WHERE user_id = ? AND file_id = ?"
|
||||
(filePath, CIFSRcvAccepted, currentTs, userId, fileId)
|
||||
"UPDATE files SET file_path = ?, ci_file_status = CASE WHEN ci_file_status = ? THEN ci_file_status ELSE ? END, updated_at = ? WHERE user_id = ? AND file_id = ?"
|
||||
(filePath, CIFSRcvError fwdFileUnavailableError, CIFSRcvAccepted, currentTs, userId, fileId)
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE rcv_files SET user_approved_relays = ?, rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?"
|
||||
|
||||
Reference in new issue
Block a user