From 27285092a1b0073d84eb1986d5d07e5a744a7779 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 2 Feb 2024 21:03:23 +0200 Subject: [PATCH 01/33] chat: add direct xftp upload/download commands --- cabal.project | 2 +- src/Simplex/Chat.hs | 188 +++++++++++++++++------------ src/Simplex/Chat/Controller.hs | 22 ++-- src/Simplex/Chat/Markdown.hs | 7 +- src/Simplex/Chat/Store/Files.hs | 32 ++++- src/Simplex/Chat/Store/Messages.hs | 7 ++ src/Simplex/Chat/View.hs | 36 ++++-- tests/ChatClient.hs | 2 +- tests/ChatTests/Files.hs | 28 +++++ tests/ProtocolTests.hs | 3 +- 10 files changed, 216 insertions(+), 111 deletions(-) diff --git a/cabal.project b/cabal.project index 780703f012..0747ac4b0f 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: a516c2f72c81bb4a433c4065b1b5aa484b8292b1 + tag: c40aa973cf42f35dade43ac7068aacccc0c6e9c5 source-repository-package type: git diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index dcd392629c..e1751c3e18 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6,6 +6,7 @@ {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} @@ -81,7 +82,8 @@ import Simplex.Chat.Types.Util import Simplex.Chat.Util (encryptFile, shuffle) import Simplex.FileTransfer.Client.Main (maxFileSize) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) -import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb) +import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription, gb, kb, mb, pattern ValidFileDescription) +import qualified Simplex.FileTransfer.Description as FD import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentWorkersDetails, getAgentWorkersSummary, temporaryAgentError) @@ -102,6 +104,7 @@ import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol) import qualified Simplex.Messaging.Protocol as SMP +import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Transport.Client (defaultSocksProxy) import Simplex.Messaging.Util @@ -671,7 +674,7 @@ processChatCommand' vr = \case (fileSize, fileMode) <- checkSndFile mc file 1 case fileMode of SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline - SendFileXFTP -> xftpSndFileTransfer user file fileSize 1 $ CGContact ct + SendFileXFTP -> xftpSndFileTransfer_ user file fileSize 1 . Just $ CGContact ct where smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled @@ -736,7 +739,7 @@ processChatCommand' vr = \case (fileSize, fileMode) <- checkSndFile mc file $ fromIntegral n case fileMode of SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline - SendFileXFTP -> xftpSndFileTransfer user file fileSize n $ CGGroup g + SendFileXFTP -> xftpSndFileTransfer_ user file fileSize n . Just $ CGGroup g where smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled @@ -764,31 +767,6 @@ processChatCommand' vr = \case CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) - xftpSndFileTransfer user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup = do - let fileName = takeFileName filePath - fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} - fInv = xftpFileInvitation fileName fileSize fileDescr - fsFilePath <- toFSFilePath filePath - let srcFile = CryptoFile fsFilePath cfArgs - aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) - -- TODO CRSndFileStart event for XFTP - chSize <- asks $ fileChunkSize . config - ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup file fInv (AgentSndFileId aFileId) chSize - let fileSource = Just $ CryptoFile filePath cfArgs - ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} - case contactOrGroup of - CGContact Contact {activeConn} -> forM_ activeConn $ \conn -> - withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft fileDescr - CGGroup (Group _ ms) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) - where - -- we are not sending files to pending members, same as with inline files - saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = - when ((connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn)) $ - withStore' $ - \db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr - saveMemberFD _ = pure () - pure (fInv, ciFile, ft) unzipMaybe3 :: Maybe (a, b, c) -> (Maybe a, Maybe b, Maybe c) unzipMaybe3 (Just (a, b, c)) = (Just a, Just b, Just c) unzipMaybe3 _ = (Nothing, Nothing, Nothing) @@ -2054,6 +2032,10 @@ processChatCommand' vr = \case StopRemoteCtrl -> withUser_ $ stopRemoteCtrl >> ok_ ListRemoteCtrls -> withUser_ $ CRRemoteCtrlList <$> listRemoteCtrls DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ + APIXFTPDirectUpload userId file -> withUserId userId $ \user -> do + fileSize <- liftIO $ CF.getFileContentsSize file + xftpSndFileTransfer_ user file fileSize 1 Nothing >> ok_ + APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ QuitChat -> liftIO exitSuccess ShowVersion -> do -- simplexmqCommitQ makes iOS builds crash m( @@ -2454,7 +2436,7 @@ processChatCommand' vr = \case where cReqSchemas :: (ConnReqInvitation, ConnReqInvitation) cReqSchemas = - ( CRInvitationUri crData {crScheme = CRSSimplex} e2e, + ( CRInvitationUri crData {crScheme = SSSimplex} e2e, CRInvitationUri crData {crScheme = simplexChat} e2e ) connectPlan user (ACR SCMContact (CRContactUri crData)) = do @@ -2499,7 +2481,7 @@ processChatCommand' vr = \case where cReqSchemas :: (ConnReqContact, ConnReqContact) cReqSchemas = - ( CRContactUri crData {crScheme = CRSSimplex}, + ( CRContactUri crData {crScheme = SSSimplex}, CRContactUri crData {crScheme = simplexChat} ) cReqHashes :: (ConnReqUriHash, ConnReqUriHash) @@ -2807,13 +2789,25 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} startReceivingFile user fileId withStoreCtx' (Just "receiveViaCompleteFD, updateRcvFileAgentId") $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) +receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m () +receiveViaURI user@User {userId} (FileDescriptionURI _ vfd) cf@CryptoFile {cryptoArgs} = do + fileId <- withStore $ \db -> createRcvDirectFileTransfer db userId cf fileSize chunkSize + aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) vfd cryptoArgs + -- startReceivingFile user fileId + withStore' $ \db -> do + updateRcvFileStatus db fileId FSConnected + updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 + updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + where + ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = vfd + startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do vr <- chatVersionRange ci <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRRcvFileStart user ci getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> String -> Bool -> m FilePath @@ -3281,56 +3275,61 @@ processAgentMsgSndFile _corrId aFileId msg = let status = CIFSSndTransfer {sndProgress, sndTotal} ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal SFDONE sndDescr rfds -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) - ci@(AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) <- - withStore $ \db -> getChatItemByFileId db vr user fileId - case (msgId_, itemDeleted) of - (Just sharedMsgId, Nothing) -> do - when (length rfds < length sfts) $ throwChatError $ CEInternalError "not enough XFTP file descriptions to send" - -- TODO either update database status or move to SFPROG - toView $ CRSndFileProgressXFTP user ci ft 1 1 - case (rfds, sfts, d, cInfo) of - (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do - withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage ct - withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId - withAgent (`xftpDeleteSndFileInternal` aFileId) - (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do - ms <- withStore' $ \db -> getGroupMembers db user g - let rfdsMemberFTs = zip rfds $ memberFTs ms - extraRFDs = drop (length rfdsMemberFTs) rfds - withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) - ci' <- withStore $ \db -> do - liftIO $ updateCIFileStatus db user fileId CIFSSndComplete - getChatItemByFileId db vr user fileId - withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileCompleteXFTP user ci' ft - where - memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] - memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId + case ci of + Nothing -> do + withAgent (`xftpDeleteSndFileInternal` aFileId) + let uris = map (decodeLatin1 . strEncode . FileDescriptionURI SSSimplex) rfds + toView $ CRSndFileCompleteXFTP user ci ft $ Just uris + Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> + case (msgId_, itemDeleted) of + (Just sharedMsgId, Nothing) -> do + when (length rfds < length sfts) $ throwChatError $ CEInternalError "not enough XFTP file descriptions to send" + -- TODO either update database status or move to SFPROG + toView $ CRSndFileProgressXFTP user ci ft 1 1 + case (rfds, sfts, d, cInfo) of + (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) + msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage ct + withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId + withAgent (`xftpDeleteSndFileInternal` aFileId) + (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do + ms <- withStore' $ \db -> getGroupMembers db user g + let rfdsMemberFTs = zip rfds $ memberFTs ms + extraRFDs = drop (length rfdsMemberFTs) rfds + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) + forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) + ci' <- withStore $ \db -> do + liftIO $ updateCIFileStatus db user fileId CIFSSndComplete + lookupChatItemByFileId db vr user fileId + withAgent (`xftpDeleteSndFileInternal` aFileId) + toView $ CRSndFileCompleteXFTP user ci' ft Nothing where - mConns' = mapMaybe useMember ms - sfts' = mapMaybe (\sft@SndFileTransfer {groupMemberId} -> (,sft) <$> groupMemberId) sfts - useMember GroupMember {groupMemberId, activeConn = Just conn@Connection {connStatus}} - | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) - | otherwise = Nothing - useMember _ = Nothing - sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> m () - sendToMember (rfd, (conn, sft)) = - void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> sendDirectMessage conn msg' $ GroupId groupId - _ -> pure () - _ -> pure () -- TODO error? + memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] + memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') + where + mConns' = mapMaybe useMember ms + sfts' = mapMaybe (\sft@SndFileTransfer {groupMemberId} -> (,sft) <$> groupMemberId) sfts + useMember GroupMember {groupMemberId, activeConn = Just conn@Connection {connStatus}} + | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) + | otherwise = Nothing + useMember _ = Nothing + sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> m () + sendToMember (rfd, (conn, sft)) = + void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> sendDirectMessage conn msg' $ GroupId groupId + _ -> pure () + _ -> pure () -- TODO error? SFERR e | temporaryAgentError e -> throwChatError $ CEXFTPSndFile fileId (AgentSndFileId aFileId) e | otherwise -> do ci <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId CIFSSndError - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId withAgent (`xftpDeleteSndFileInternal` aFileId) toView $ CRSndFileError user ci where @@ -3383,7 +3382,7 @@ processAgentMsgRcvFile _corrId aFileId msg = let status = CIFSRcvTransfer {rcvProgress, rcvTotal} ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal RFDONE xftpPath -> case liveRcvFileTransferPath ft of @@ -3391,20 +3390,22 @@ processAgentMsgRcvFile _corrId aFileId msg = Just targetPath -> do fsTargetPath <- toFSFilePath targetPath renameFile xftpPath fsTargetPath - ci <- withStore $ \db -> do + ci_ <- withStore $ \db -> do liftIO $ do updateRcvFileStatus db fileId FSComplete updateCIFileStatus db user fileId CIFSRcvComplete - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileComplete user ci + case ci_ of + Nothing -> toView $ CRRcvFileCompleteXFTP user fsTargetPath + Just ci -> toView $ CRRcvFileComplete user ci RFERR e | temporaryAgentError e -> throwChatError $ CEXFTPRcvFile fileId (AgentRcvFileId aFileId) e | otherwise -> do ci <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId CIFSRcvError - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId toView $ CRRcvFileError user ci e @@ -4088,7 +4089,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = getChatRefByFileId db user fileId >>= \case ChatRef CTDirect _ -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled _ -> pure () - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRSndFileRcvCancelled user ci ft _ -> throwChatError $ CEFileSend fileId err MSG meta _ _ -> withAckMessage' agentConnId conn meta $ pure () @@ -4773,7 +4774,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case file of Just CIFile {fileProtocol = FPXFTP} -> do ft <- withStore $ \db -> getFileTransferMeta db user fileId - toView $ CRSndFileCompleteXFTP user ci ft + toView $ CRSndFileCompleteXFTP user (Just ci) ft Nothing _ -> toView $ CRSndFileComplete user ci sft allowSendInline :: Integer -> Maybe InlineFileMode -> m Bool @@ -6744,6 +6745,9 @@ chatCommandP = "/list remote ctrls" $> ListRemoteCtrls, "/stop remote ctrl" $> StopRemoteCtrl, "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), + -- XXX: _xftp/xftp prefix is taken + "/_upload " *> (APIXFTPDirectUpload <$> A.decimal <* A.space <*> cryptoFileP), + "/_download " *> (APIXFTPDirectDownload <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, "/debug locks" $> DebugLocks, @@ -6931,3 +6935,31 @@ mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 | isPunctuation prev = validFirstChar || isSpace c || (punct < 3 && isPunctuation c) | otherwise = validFirstChar || isSpace c || isMark c || isPunctuation c validFirstChar = isLetter c || isNumber c || isSymbol c + +xftpSndFileTransfer_ :: ChatMonad m => User -> CryptoFile -> Integer -> Int -> Maybe ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) +xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup_ = do + let fileName = takeFileName filePath + fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} + fInv = xftpFileInvitation fileName fileSize fileDescr + fsFilePath <- toFSFilePath filePath + let srcFile = CryptoFile fsFilePath cfArgs + aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) + -- TODO CRSndFileStart event for XFTP + chSize <- asks $ fileChunkSize . config + ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup_ file fInv (AgentSndFileId aFileId) chSize + let fileSource = Just $ CryptoFile filePath cfArgs + ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} + case contactOrGroup_ of + Nothing -> + pure () + Just (CGContact Contact {activeConn}) -> forM_ activeConn $ \conn -> + withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft fileDescr + Just (CGGroup (Group _ ms)) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) + where + -- we are not sending files to pending members, same as with inline files + saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = + when ((connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn)) $ + withStore' $ + \db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr + saveMemberFD _ = pure () + pure (fInv, ciFile, ft) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index d3c8698f94..5b49fcfdf6 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -59,6 +59,7 @@ import Simplex.Chat.Remote.Types import Simplex.Chat.Store (AutoAccept, StoreError (..), UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types import Simplex.Chat.Types.Preferences +import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) @@ -452,6 +453,8 @@ data ChatCommand | ListRemoteCtrls | StopRemoteCtrl -- Stop listening for announcements or terminate an active session | DeleteRemoteCtrl RemoteCtrlId -- Remove all local data associated with a remote controller session + | APIXFTPDirectUpload UserId CryptoFile + | APIXFTPDirectDownload UserId FileDescriptionURI CryptoFile | QuitChat | ShowVersion | DebugLocks @@ -592,21 +595,22 @@ data ChatResponse | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} - | CRRcvFileStart {user :: User, chatItem :: AChatItem} - | CRRcvFileProgressXFTP {user :: User, chatItem :: AChatItem, receivedSize :: Int64, totalSize :: Int64} + | CRRcvFileStart {user :: User, chatItem_ :: Maybe AChatItem} + | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} + | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath} | CRRcvFileCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileError {user :: User, chatItem :: AChatItem, agentError :: AgentErrorType} + | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType} | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileRcvCancelled {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} + | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} - | CRSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileProgressXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} - | CRSndFileCompleteXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileCancelledXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileError {user :: User, chatItem :: AChatItem} + | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} + | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: Maybe [Text]} + | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} | CRUserProfileImage {user :: User, profile :: Profile} | CRContactAliasUpdated {user :: User, toContact :: Contact} diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 6ee4898e3d..2eabb48166 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -30,10 +30,11 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Types import Simplex.Chat.Types.Util -import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) +import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON) import Simplex.Messaging.Protocol (ProtocolServer (..)) +import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import Simplex.Messaging.Util (safeDecodeUtf8) import System.Console.ANSI.Types import qualified Text.Email.Validate as Email @@ -231,10 +232,10 @@ markdownP = mconcat <$> A.many' fragmentP simplexUriFormat :: AConnectionRequestUri -> Format simplexUriFormat = \case ACR _ (CRContactUri crData) -> - let uri = safeDecodeUtf8 . strEncode $ CRContactUri crData {crScheme = CRSSimplex} + let uri = safeDecodeUtf8 . strEncode $ CRContactUri crData {crScheme = SSSimplex} in SimplexLink (linkType' crData) uri $ uriHosts crData ACR _ (CRInvitationUri crData e2e) -> - let uri = safeDecodeUtf8 . strEncode $ CRInvitationUri crData {crScheme = CRSSimplex} e2e + let uri = safeDecodeUtf8 . strEncode $ CRInvitationUri crData {crScheme = SSSimplex} e2e in SimplexLink XLInvitation uri $ uriHosts crData where uriHosts ConnReqUriData {crSmpQueues} = L.map (safeDecodeUtf8 . strEncode) $ sconcat $ L.map (host . qServer) crSmpQueues diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index bc5cec3332..0d6565fa28 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -46,6 +46,7 @@ module Simplex.Chat.Store.Files deleteSndFileChunks, createRcvFileTransfer, createRcvGroupFileTransfer, + createRcvDirectFileTransfer, appendRcvFD, getRcvFileDescrByRcvFileId, getRcvFileDescrBySndFileId, @@ -88,6 +89,7 @@ import Control.Monad.IO.Class import Data.Either (rights) import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust, listToMaybe) +import Data.Word (Word32) import Data.Text (Text) import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) @@ -277,14 +279,14 @@ getSndFTViaMsgDelivery db User {userId} Connection {connId, agentConnId} agentMs (\n -> SndFileTransfer {fileId, fileStatus, fileName, fileSize, chunkSize, filePath, fileDescrId, fileInline, groupMemberId, recipientDisplayName = n, connId, agentConnId}) <$> (contactName_ <|> memberName_) -createSndFileTransferXFTP :: DB.Connection -> User -> ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta -createSndFileTransferXFTP db User {userId} contactOrGroup (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do +createSndFileTransferXFTP :: DB.Connection -> User -> Maybe ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta +createSndFileTransferXFTP db User {userId} contactOrGroup_ (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do currentTs <- getCurrentTime let xftpSndFile = Just XFTPSndFile {agentSndFileId, privateSndFileDescr = Nothing, agentSndFileDeleted = False, cryptoArgs} DB.execute db "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - (contactAndGroupIds contactOrGroup :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) + (maybe (Nothing, Nothing) contactAndGroupIds contactOrGroup_ :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) fileId <- insertedRowId db pure FileTransferMeta {fileId, xftpSndFile, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} @@ -536,6 +538,23 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD (fileId, FSNew, fileConnReq, fileInline, rcvFileInline, groupMemberId, rfdId, currentTs, currentTs) pure RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = f, fileStatus = RFSNew, rcvFileInline, senderDisplayName = c, chunkSize, cancelled = False, grpMemberId = Just groupMemberId, cryptoArgs = Nothing} +createRcvDirectFileTransfer :: DB.Connection -> UserId -> CryptoFile -> Int64 -> Word32 -> ExceptT StoreError IO Int64 +createRcvDirectFileTransfer db userId (CryptoFile filePath cfArgs_) fileSize chunkSize = do + currentTs <- liftIO getCurrentTime + fileId <- liftIO $ do + DB.execute + db + "INSERT INTO files (user_id, file_name, file_path, file_size, chunk_size, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (userId, takeFileName filePath, filePath, fileSize, chunkSize, CIFSRcvInvitation, FPXFTP, currentTs, currentTs) + insertedRowId db + liftIO . forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs + liftIO $ + DB.execute + db + "INSERT INTO rcv_files (file_id, file_status, created_at, updated_at) VALUES (?,?,?,?)" + (fileId, FSNew, currentTs, currentTs) + pure fileId + createRcvFD_ :: DB.Connection -> UserId -> UTCTime -> FileDescr -> ExceptT StoreError IO RcvFileDescr createRcvFD_ db userId currentTs FileDescr {fileDescrText, fileDescrPartNo, fileDescrComplete} = do when (fileDescrPartNo /= 0) $ throwError SERcvFileInvalidDescrPart @@ -662,9 +681,9 @@ getRcvFileTransfer_ db userId fileId = do (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool) :. (Maybe Int64, Maybe AgentConnId) -> ExceptT StoreError IO RcvFileTransfer rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted) :. (connId_, agentConnId_)) = - case contactName_ <|> memberName_ of + case contactName_ <|> memberName_ <|> directName_ of Nothing -> throwError $ SERcvFileInvalid fileId - Just name -> do + Just name -> case fileStatus' of FSNew -> pure $ ft name RFSNew FSAccepted -> ft name . RFSAccepted <$> rfi @@ -672,6 +691,9 @@ getRcvFileTransfer_ db userId fileId = do FSComplete -> ft name . RFSComplete <$> rfi FSCancelled -> ft name . RFSCancelled <$> rfi_ where + directName_ = case (connId_, agentRcvFileId, filePath_) of + (Nothing, Just _, Just _) -> Just "direct" -- XXX: filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer + _ -> Nothing ft senderDisplayName fileStatus = let fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing} cryptoArgs = CFArgs <$> fileKey <*> fileNonce diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 891e9887e6..96b6a1eaf8 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -92,6 +92,7 @@ module Simplex.Chat.Store.Messages getLocalChatItemIdByText, getLocalChatItemIdByText', getChatItemByFileId, + lookupChatItemByFileId, getChatItemByGroupId, updateDirectChatItemStatus, getTimedItems, @@ -2085,6 +2086,12 @@ getChatItemByFileId db vr user@User {userId} fileId = do (userId, fileId) getAChatItem db vr user chatRef itemId +lookupChatItemByFileId :: DB.Connection -> VersionRange -> User -> Int64 -> ExceptT StoreError IO (Maybe AChatItem) +lookupChatItemByFileId db vr user fileId = do + fmap Just (getChatItemByFileId db vr user fileId) `catchError` \case + SEChatItemNotFoundByFileId {} -> pure Nothing + e -> throwError e + getChatItemByGroupId :: DB.Connection -> VersionRange -> User -> GroupId -> ExceptT StoreError IO AChatItem getChatItemByGroupId db vr user@User {userId} groupId = do (chatRef, itemId) <- diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 05c90696b4..9dba23aeee 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,15 +198,17 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile - CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci - CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci + CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left "") Right ci) + CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) + CRRcvFileCompleteXFTP u path -> ttyUser u $ receivingFile_' hu testView "completed" (Left path) CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u ci e -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] + CRRcvFileError u ci e -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left "") Right ci) <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndFileStartXFTP {} -> [] CRSndFileProgressXFTP {} -> [] - CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" ci + CRSndFileCompleteXFTP u ci _ Nothing -> ttyUser u $ uploadingFile "completed" ci + CRSndFileCompleteXFTP u _ _ (Just uris) -> ttyUser u $ directUploadComplete uris CRSndFileCancelledXFTP {} -> [] CRSndFileError u ci -> ttyUser u $ uploadingFile "error" ci CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> @@ -1557,12 +1559,16 @@ sendingFile_ :: StyledString -> SndFileTransfer -> [StyledString] sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} = [status <> " sending " <> sndFile ft <> " to " <> ttyContact c] -uploadingFile :: StyledString -> AChatItem -> [StyledString] -uploadingFile status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) = - [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] -uploadingFile status (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) = - [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] -uploadingFile status _ = [status <> " uploading file"] -- shouldn't happen +uploadingFile :: StyledString -> Maybe AChatItem -> [StyledString] +uploadingFile status = \case + Just (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) -> + [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] + Just (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) -> + [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] + _ -> [status <> " uploading file"] + +directUploadComplete :: [Text] -> [StyledString] +directUploadComplete uris = "file upload complete. download links:" : map plain uris sndFile :: SndFileTransfer -> StyledString sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName @@ -1594,8 +1600,8 @@ savingFile' (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource ["saving file " <> sShow fileId <> fileFrom chat chatDir <> " to " <> plain filePath] savingFile' _ = ["saving file"] -- shouldn't happen -receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> AChatItem -> [StyledString] -receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir}) = +receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> Either FilePath AChatItem -> [StyledString] +receivingFile_' hu testView status (Right (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir})) = [plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr where cfArgsStr (Just cfArgs) = [plain (cryptoFileArgsStr testView cfArgs) | status == "completed"] @@ -1608,7 +1614,11 @@ receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIF highlight ("/get remote file " <> show rhId <> " " <> LB.unpack (J.encode RemoteFile {userId, fileId, sent = False, fileSource = f})) ] _ -> [] -receivingFile_' _ _ status _ = [plain status <> " receiving file"] -- shouldn't happen +receivingFile_' _ _ status (Left path) | not (null path) = + [ plain status <> " receiving file" + , plain path + ] +receivingFile_' _ _ status _ = [plain status <> " receiving file"] viewLocalFile :: StyledString -> CIFile d -> CurrentTime -> TimeZone -> CIMeta c d -> [StyledString] viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index f7982c5fb4..c15a39d627 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -407,7 +407,7 @@ xftpServerConfig = storeLogFile = Just "tests/tmp/xftp-server-store.log", filesPath = xftpServerFiles, fileSizeQuota = Nothing, - allowedChunkSizes = [kb 128, kb 256, mb 1, mb 4], + allowedChunkSizes = [kb 64, kb 128, kb 256, mb 1, mb 4], allowNewFiles = True, newFileBasicAuth = Nothing, fileExpiration = Just defaultFileExpiration, diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index a6b0f56ba3..213f86ac14 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -9,6 +9,7 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import Control.Concurrent.Async (concurrently_) +import Control.Logger.Simple import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB @@ -77,6 +78,8 @@ chatFileTests = do it "cancel receiving file, repeat receive" testXFTPCancelRcvRepeat it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles + it "send and receive file without chat items" testXFTPDirect + -- TODO: send/receive with cfArgs runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () runTestFileTransfer alice bob = do @@ -1545,6 +1548,31 @@ testProhibitFiles = where cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"} +testXFTPDirect :: HasCallStack => FilePath -> IO () +testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + logNote "sending" + src ##> "/_upload 1 ./tests/fixtures/test.jpg" + src <## "ok" + threadDelay 250000 + src <## "file upload complete. download links:" + uri1 <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/test.jpg" + dst ##> ("/_download 1 " <> uri1 <> " " <> dstFile) + dst <## "ok" + threadDelay 250000 + dst <## "completed receiving file" + getTermLine dst `shouldReturn` dstFile + srcBody <- B.readFile "./tests/fixtures/test.jpg" + B.readFile dstFile `shouldReturn` srcBody + + logNote "bye" + startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 783f7fb344..01ab7905a3 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -15,6 +15,7 @@ import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet import Simplex.Messaging.Protocol (supportedSMPClientVRange) +import Simplex.Messaging.ServiceScheme import Simplex.Messaging.Version import Test.Hspec @@ -37,7 +38,7 @@ queue = connReqData :: ConnReqUriData connReqData = ConnReqUriData - { crScheme = CRSSimplex, + { crScheme = SSSimplex, crAgentVRange = mkVersionRange 1 1, crSmpQueues = [queue], crClientData = Nothing From fc80915fa39225bfabf00ad8f72ad1c477a4e144 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Tue, 6 Feb 2024 20:39:26 +0200 Subject: [PATCH 02/33] adapt to FileDescriptionURI record --- src/Simplex/Chat.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index e1751c3e18..eef94495eb 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2790,16 +2790,16 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} withStoreCtx' (Just "receiveViaCompleteFD, updateRcvFileAgentId") $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m () -receiveViaURI user@User {userId} (FileDescriptionURI _ vfd) cf@CryptoFile {cryptoArgs} = do +receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do fileId <- withStore $ \db -> createRcvDirectFileTransfer db userId cf fileSize chunkSize - aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) vfd cryptoArgs + aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) description cryptoArgs -- startReceivingFile user fileId withStore' $ \db -> do updateRcvFileStatus db fileId FSConnected updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) where - ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = vfd + ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do @@ -3283,7 +3283,7 @@ processAgentMsgSndFile _corrId aFileId msg = case ci of Nothing -> do withAgent (`xftpDeleteSndFileInternal` aFileId) - let uris = map (decodeLatin1 . strEncode . FileDescriptionURI SSSimplex) rfds + let uris = map (decodeLatin1 . strEncode . FD.fileDescriptionURI) rfds toView $ CRSndFileCompleteXFTP user ci ft $ Just uris Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of From 2dae0af231bf5e1f0a697f5a68baa0df7b78023f Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Tue, 13 Feb 2024 16:41:34 +0200 Subject: [PATCH 03/33] bump simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 0747ac4b0f..482d3e6646 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: c40aa973cf42f35dade43ac7068aacccc0c6e9c5 + tag: 2f7a288280f4ae74ef1a70f3cb173345c95a4da1 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 042c455b09..ef56384c2e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."a516c2f72c81bb4a433c4065b1b5aa484b8292b1" = "05ny2i262c236li5w040i1nd3l037cpzgbzjknlla9dd139f3al3"; + "https://github.com/simplex-chat/simplexmq.git"."2f7a288280f4ae74ef1a70f3cb173345c95a4da1" = "1n3szfd2lzawd9vr4sz4iisjg9hsi2rbpimcq4y4m6vcxg21q139"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From b2fa151bd52d9c59c0af44e45eb7d57fe04afb98 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Tue, 13 Feb 2024 21:47:28 +0200 Subject: [PATCH 04/33] add description uploading --- src/Simplex/Chat.hs | 23 ++++++++++++++++++++--- src/Simplex/Chat/Controller.hs | 2 ++ src/Simplex/Chat/Store/Files.hs | 8 ++++++++ src/Simplex/Chat/Store/Shared.hs | 1 + src/Simplex/Chat/View.hs | 13 +++++++++---- tests/ChatTests/Files.hs | 12 +++++------- 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index b2f83ee0c4..e524eac149 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2037,7 +2037,19 @@ processChatCommand' vr = \case DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ APIXFTPDirectUpload userId file -> withUserId userId $ \user -> do fileSize <- liftIO $ CF.getFileContentsSize file - xftpSndFileTransfer_ user file fileSize 1 Nothing >> ok_ + (_, _, fileTransferMeta) <- xftpSndFileTransfer_ user file fileSize 1 Nothing + pure CRSndFileStartXFTPDirect {user, fileTransferMeta} + APIXFTPDescriptionUpload userId fileTransferId -> withUserId userId $ \user -> do + descr <- withStore $ \db -> takeExtraSndFTDescr db user fileTransferId + vfd <- liftEitherWith (ChatError . CEInternalError . ("description yaml decode error: " <>)) . strDecode $ encodeUtf8 descr + let fileName = "redirect.yaml" + file = CryptoFile fileName Nothing + fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} + fInv = xftpFileInvitation fileName (fromIntegral $ T.length descr) fileDescr + aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd + chSize <- asks $ fileChunkSize . config + fileTransferMeta <- withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) chSize + pure CRSndFileStartXFTPDirect {user, fileTransferMeta} APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ QuitChat -> liftIO exitSuccess ShowVersion -> do @@ -3286,8 +3298,12 @@ processAgentMsgSndFile _corrId aFileId msg = case ci of Nothing -> do withAgent (`xftpDeleteSndFileInternal` aFileId) - let uris = map (decodeLatin1 . strEncode . FD.fileDescriptionURI) rfds - toView $ CRSndFileCompleteXFTP user ci ft $ Just uris + let uris = L.nonEmpty $ do + vfd@(ValidFileDescription FD.FileDescription {redirect = Just _}) <- rfds -- only generate URIs for redirects + -- XXX: just filter by size? + pure . decodeLatin1 . strEncode $ FD.fileDescriptionURI vfd + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) + toView $ CRSndFileCompleteXFTP user ci ft (L.toList <$> uris) Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do @@ -6750,6 +6766,7 @@ chatCommandP = "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), -- XXX: _xftp/xftp prefix is taken "/_upload " *> (APIXFTPDirectUpload <$> A.decimal <* A.space <*> cryptoFileP), + "/_upload description " *> (APIXFTPDescriptionUpload <$> A.decimal <* A.space <*> A.decimal), -- TODO: extras (: "/_download " *> (APIXFTPDirectDownload <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3d7df12b20..4507df6bf9 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -454,6 +454,7 @@ data ChatCommand | StopRemoteCtrl -- Stop listening for announcements or terminate an active session | DeleteRemoteCtrl RemoteCtrlId -- Remove all local data associated with a remote controller session | APIXFTPDirectUpload UserId CryptoFile + | APIXFTPDescriptionUpload UserId FileTransferId | APIXFTPDirectDownload UserId FileDescriptionURI CryptoFile | QuitChat | ShowVersion @@ -607,6 +608,7 @@ data ChatResponse | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndFileStartXFTPDirect {user :: User, fileTransferMeta :: FileTransferMeta} -- sent by _upload instead of ok, signalling fileId to expect | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: Maybe [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 0d6565fa28..5017bc1160 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -28,6 +28,7 @@ module Simplex.Chat.Store.Files setSndFTPrivateSndDescr, updateSndFTDescrXFTP, createExtraSndFTDescrs, + takeExtraSndFTDescr, updateSndFTDeliveryXFTP, setSndFTAgentDeleted, getXFTPSndFileDBId, @@ -335,6 +336,13 @@ createExtraSndFTDescrs db User {userId} fileId rfdTexts = do "INSERT INTO extra_xftp_file_descriptions (file_id, user_id, file_descr_text, created_at, updated_at) VALUES (?,?,?,?,?)" (fileId, userId, rfdText, currentTs, currentTs) +takeExtraSndFTDescr :: DB.Connection -> User -> FileTransferId -> ExceptT StoreError IO Text +takeExtraSndFTDescr db User {userId} fileId = do + (eId :: Int64, descr) <- ExceptT . firstRow id (SEExtraFileDescrNotFound fileId) $ + DB.query db "SELECT extra_file_descr_id, file_descr_text FROM extra_xftp_file_descriptions WHERE user_id = ? AND file_id = ?" (userId, fileId) + liftIO $ DB.execute db "DELETE FROM extra_xftp_file_descriptions WHERE extra_file_descr_id = ?" (Only eId) + pure descr + updateSndFTDeliveryXFTP :: DB.Connection -> SndFileTransfer -> Int64 -> IO () updateSndFTDeliveryXFTP db SndFileTransfer {connId, fileId, fileDescrId} msgDeliveryId = DB.execute diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index e4d47b32cc..45311bac71 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -84,6 +84,7 @@ data StoreError | SEFileIdNotFoundBySharedMsgId {sharedMsgId :: SharedMsgId} | SESndFileNotFoundXFTP {agentSndFileId :: AgentSndFileId} | SERcvFileNotFoundXFTP {agentRcvFileId :: AgentRcvFileId} + | SEExtraFileDescrNotFound {fileId :: FileTransferId} | SEConnectionNotFound {agentConnId :: AgentConnId} | SEConnectionNotFoundById {connId :: Int64} | SEConnectionNotFoundByMemberId {groupMemberId :: GroupMemberId} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2800a659a5..0c7e36eaa7 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -206,9 +206,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndFileStartXFTP {} -> [] + CRSndFileStartXFTPDirect {} -> [] CRSndFileProgressXFTP {} -> [] - CRSndFileCompleteXFTP u ci _ Nothing -> ttyUser u $ uploadingFile "completed" ci - CRSndFileCompleteXFTP u _ _ (Just uris) -> ttyUser u $ directUploadComplete uris + CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ directUploadComplete ft uris + CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] CRSndFileError u ci -> ttyUser u $ uploadingFile "error" ci CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> @@ -1567,8 +1568,12 @@ uploadingFile status = \case [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] _ -> [status <> " uploading file"] -directUploadComplete :: [Text] -> [StyledString] -directUploadComplete uris = "file upload complete. download links:" : map plain uris +directUploadComplete :: FileTransferMeta -> Maybe [Text] -> [StyledString] +directUploadComplete FileTransferMeta {fileId, fileName} = \case + Nothing -> [fileTransferStr fileId fileName <> " upload complete."] + Just uris -> + fileTransferStr fileId fileName <> " upload complete. download with:" + : map plain uris sndFile :: SndFileTransfer -> StyledString sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 213f86ac14..68cec3374a 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1553,17 +1553,15 @@ testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do logNote "sending" src ##> "/_upload 1 ./tests/fixtures/test.jpg" - src <## "ok" threadDelay 250000 - src <## "file upload complete. download links:" - uri1 <- getTermLine src - _uri2 <- getTermLine src - _uri3 <- getTermLine src - _uri4 <- getTermLine src + src <## "file 1 (test.jpg) upload complete." + src ##> "/_upload description 1 1" + src <## "file 2 (redirect.yaml) upload complete. download with:" + uri <- getTermLine src logNote "receiving" let dstFile = "./tests/tmp/test.jpg" - dst ##> ("/_download 1 " <> uri1 <> " " <> dstFile) + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) dst <## "ok" threadDelay 250000 dst <## "completed receiving file" From 7ccabe9a8fa5742fc4db5ac135176d8e4b4e0c0d Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Tue, 13 Feb 2024 22:14:11 +0200 Subject: [PATCH 05/33] filter URIs by size --- src/Simplex/Chat.hs | 18 ++++++++--------- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/View.hs | 6 +++--- tests/ChatTests/Files.hs | 36 ++++++++++++++++++++++++++++++---- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index e524eac149..e471a178dc 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -82,7 +82,7 @@ import Simplex.Chat.Types.Util import Simplex.Chat.Util (encryptFile, shuffle) import Simplex.FileTransfer.Client.Main (maxFileSize) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) -import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription, gb, kb, mb, pattern ValidFileDescription) +import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription, gb, kb, mb) import qualified Simplex.FileTransfer.Description as FD import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import Simplex.Messaging.Agent as Agent @@ -2814,7 +2814,7 @@ receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) where - ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description + FD.ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do @@ -3298,12 +3298,8 @@ processAgentMsgSndFile _corrId aFileId msg = case ci of Nothing -> do withAgent (`xftpDeleteSndFileInternal` aFileId) - let uris = L.nonEmpty $ do - vfd@(ValidFileDescription FD.FileDescription {redirect = Just _}) <- rfds -- only generate URIs for redirects - -- XXX: just filter by size? - pure . decodeLatin1 . strEncode $ FD.fileDescriptionURI vfd withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) - toView $ CRSndFileCompleteXFTP user ci ft (L.toList <$> uris) + toView $ CRSndFileCompleteXFTP user ci ft $ mapMaybe fileDescrURI rfds Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do @@ -3326,7 +3322,7 @@ processAgentMsgSndFile _corrId aFileId msg = liftIO $ updateCIFileStatus db user fileId CIFSSndComplete lookupChatItemByFileId db vr user fileId withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileCompleteXFTP user ci' ft Nothing + toView $ CRSndFileCompleteXFTP user ci' ft [] where memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') @@ -3354,6 +3350,10 @@ processAgentMsgSndFile _corrId aFileId msg = where fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text fileDescrText = safeDecodeUtf8 . strEncode + fileDescrURI :: ValidFileDescription 'FRecipient -> Maybe T.Text + fileDescrURI vfd = if T.length uri < FD.qrSizeLimit then Just uri else Nothing + where + uri = decodeLatin1 . strEncode $ FD.fileDescriptionURI vfd sendFileDescription :: SndFileTransfer -> ValidFileDescription 'FRecipient -> SharedMsgId -> (ChatMsgEvent 'Json -> m (SndMessage, Int64)) -> m Int64 sendFileDescription sft rfd msgId sendMsg = do let rfdText = fileDescrText rfd @@ -4793,7 +4793,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case file of Just CIFile {fileProtocol = FPXFTP} -> do ft <- withStore $ \db -> getFileTransferMeta db user fileId - toView $ CRSndFileCompleteXFTP user (Just ci) ft Nothing + toView $ CRSndFileCompleteXFTP user (Just ci) ft [] _ -> toView $ CRSndFileComplete user ci sft allowSendInline :: Integer -> Maybe InlineFileMode -> m Bool diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 4507df6bf9..76e38ee1f0 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -610,7 +610,7 @@ data ChatResponse | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileStartXFTPDirect {user :: User, fileTransferMeta :: FileTransferMeta} -- sent by _upload instead of ok, signalling fileId to expect | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} - | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: Maybe [Text]} + | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 0c7e36eaa7..ed0ef66e76 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1568,10 +1568,10 @@ uploadingFile status = \case [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] _ -> [status <> " uploading file"] -directUploadComplete :: FileTransferMeta -> Maybe [Text] -> [StyledString] +directUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] directUploadComplete FileTransferMeta {fileId, fileName} = \case - Nothing -> [fileTransferStr fileId fileName <> " upload complete."] - Just uris -> + [] -> [fileTransferStr fileId fileName <> " upload complete."] + uris -> fileTransferStr fileId fileName <> " upload complete. download with:" : map plain uris diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 68cec3374a..39a1c8c18a 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -78,7 +78,9 @@ chatFileTests = do it "cancel receiving file, repeat receive" testXFTPCancelRcvRepeat it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles - it "send and receive file without chat items" testXFTPDirect + fdescribe "file transfer over XFTP without chat items" $ do + it "directly send and receive file" testXFTPDirect + it "directly send and receive file via description" testXFTPDirectDescr -- TODO: send/receive with cfArgs runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () @@ -1554,7 +1556,35 @@ testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do logNote "sending" src ##> "/_upload 1 ./tests/fixtures/test.jpg" threadDelay 250000 - src <## "file 1 (test.jpg) upload complete." + src <## "file 1 (test.jpg) upload complete. download with:" + -- file description fits, enjoy the direct URIs + _uri1 <- getTermLine src + _uri2 <- getTermLine src + uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/test.jpg" + dst ##> ("/_download 1 " <> uri3 <> " " <> dstFile) + dst <## "ok" + threadDelay 250000 + dst <## "completed receiving file" + getTermLine dst `shouldReturn` dstFile + srcBody <- B.readFile "./tests/fixtures/test.jpg" + B.readFile dstFile `shouldReturn` srcBody + +testXFTPDirectDescr :: HasCallStack => FilePath -> IO () +testXFTPDirectDescr = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + logNote "sending" + src ##> "/_upload 1 ./tests/fixtures/test.jpg" + threadDelay 250000 + src <## "file 1 (test.jpg) upload complete. download with:" + -- ignoring file descriptions + _uri1 <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src src ##> "/_upload description 1 1" src <## "file 2 (redirect.yaml) upload complete. download with:" uri <- getTermLine src @@ -1569,8 +1599,6 @@ testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do srcBody <- B.readFile "./tests/fixtures/test.jpg" B.readFile dstFile `shouldReturn` srcBody - logNote "bye" - startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" From 2a66b404cb0dae25b17b5334cdec96be68efe9db Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Tue, 13 Feb 2024 22:32:03 +0200 Subject: [PATCH 06/33] cleanup --- src/Simplex/Chat.hs | 3 +-- src/Simplex/Chat/Store/Files.hs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index e471a178dc..77eaf0bfcd 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6764,9 +6764,8 @@ chatCommandP = "/list remote ctrls" $> ListRemoteCtrls, "/stop remote ctrl" $> StopRemoteCtrl, "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), - -- XXX: _xftp/xftp prefix is taken "/_upload " *> (APIXFTPDirectUpload <$> A.decimal <* A.space <*> cryptoFileP), - "/_upload description " *> (APIXFTPDescriptionUpload <$> A.decimal <* A.space <*> A.decimal), -- TODO: extras (: + "/_upload description " *> (APIXFTPDescriptionUpload <$> A.decimal <* A.space <*> A.decimal), "/_download " *> (APIXFTPDirectDownload <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 5017bc1160..5e94745100 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -700,7 +700,7 @@ getRcvFileTransfer_ db userId fileId = do FSCancelled -> ft name . RFSCancelled <$> rfi_ where directName_ = case (connId_, agentRcvFileId, filePath_) of - (Nothing, Just _, Just _) -> Just "direct" -- XXX: filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer + (Nothing, Just _, Just _) -> Just "direct" -- filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer _ -> Nothing ft senderDisplayName fileStatus = let fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing} From 8a191d0bf14b41e97215669bcf094d74af1c0a02 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 07:20:07 +0200 Subject: [PATCH 07/33] add file meta to events --- src/Simplex/Chat.hs | 16 +++++++++------- src/Simplex/Chat/Controller.hs | 10 +++++----- src/Simplex/Chat/View.hs | 8 ++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 77eaf0bfcd..878d243a54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2819,11 +2819,13 @@ receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do vr <- chatVersionRange - ci <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do + (ci, ft) <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 - lookupChatItemByFileId db vr user fileId - toView $ CRRcvFileStart user ci + ci <- lookupChatItemByFileId db vr user fileId + ft <- getRcvFileTransfer db user fileId + pure (ci, ft) + toView $ CRRcvFileStart user ci ft getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> String -> Bool -> m FilePath getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of @@ -3346,7 +3348,7 @@ processAgentMsgSndFile _corrId aFileId msg = liftIO $ updateFileCancelled db user fileId CIFSSndError lookupChatItemByFileId db vr user fileId withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileError user ci + toView $ CRSndFileError user ci ft where fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text fileDescrText = safeDecodeUtf8 . strEncode @@ -3402,7 +3404,7 @@ processAgentMsgRcvFile _corrId aFileId msg = ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status lookupChatItemByFileId db vr user fileId - toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal + toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal ft RFDONE xftpPath -> case liveRcvFileTransferPath ft of Nothing -> throwChatError $ CEInternalError "no target path for received XFTP file" @@ -3416,7 +3418,7 @@ processAgentMsgRcvFile _corrId aFileId msg = lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId case ci_ of - Nothing -> toView $ CRRcvFileCompleteXFTP user fsTargetPath + Nothing -> toView $ CRRcvFileCompleteXFTP user fsTargetPath ft Just ci -> toView $ CRRcvFileComplete user ci RFERR e | temporaryAgentError e -> @@ -3426,7 +3428,7 @@ processAgentMsgRcvFile _corrId aFileId msg = liftIO $ updateFileCancelled db user fileId CIFSRcvError lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileError user ci e + toView $ CRRcvFileError user ci e ft processAgentMessageConn :: forall m. ChatMonad m => VersionRange -> User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 76e38ee1f0..420467228c 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -596,13 +596,13 @@ data ChatResponse | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} - | CRRcvFileStart {user :: User, chatItem_ :: Maybe AChatItem} - | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64} + | CRRcvFileStart {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} + | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} - | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath} + | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType} + | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} @@ -612,7 +612,7 @@ data ChatResponse | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem} + | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} | CRUserProfileImage {user :: User, profile :: Profile} | CRContactAliasUpdated {user :: User, toContact :: Contact} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ed0ef66e76..534d5ead4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,11 +198,11 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile - CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left "") Right ci) + CRRcvFileStart u ci _ -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left "") Right ci) CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) - CRRcvFileCompleteXFTP u path -> ttyUser u $ receivingFile_' hu testView "completed" (Left path) + CRRcvFileCompleteXFTP u path _ -> ttyUser u $ receivingFile_' hu testView "completed" (Left path) CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u ci e -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left "") Right ci) <> [sShow e] + CRRcvFileError u ci e _ -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left "") Right ci) <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndFileStartXFTP {} -> [] @@ -211,7 +211,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ directUploadComplete ft uris CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] - CRSndFileError u ci -> ttyUser u $ uploadingFile "error" ci + CRSndFileError u ci _ -> ttyUser u $ uploadingFile "error" ci CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] CRContactConnecting u _ -> ttyUser u [] From db905c4c1fa1f8b3b9bb28d218921c843b633fe1 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 07:20:32 +0200 Subject: [PATCH 08/33] remove focus --- tests/ChatTests/Files.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 39a1c8c18a..0a9073276a 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -78,7 +78,7 @@ chatFileTests = do it "cancel receiving file, repeat receive" testXFTPCancelRcvRepeat it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles - fdescribe "file transfer over XFTP without chat items" $ do + describe "file transfer over XFTP without chat items" $ do it "directly send and receive file" testXFTPDirect it "directly send and receive file via description" testXFTPDirectDescr -- TODO: send/receive with cfArgs From 92569e3d106d478a5817a0297f18ca9ea53ab0a4 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 13:40:05 +0200 Subject: [PATCH 09/33] auto-redirect when no URI fits --- src/Simplex/Chat.hs | 28 +++++++++++++++++++--------- src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/View.hs | 5 +++++ tests/ChatTests/Files.hs | 29 ++++++++++++++++++++++++++--- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 878d243a54..6a59deba54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2042,13 +2042,7 @@ processChatCommand' vr = \case APIXFTPDescriptionUpload userId fileTransferId -> withUserId userId $ \user -> do descr <- withStore $ \db -> takeExtraSndFTDescr db user fileTransferId vfd <- liftEitherWith (ChatError . CEInternalError . ("description yaml decode error: " <>)) . strDecode $ encodeUtf8 descr - let fileName = "redirect.yaml" - file = CryptoFile fileName Nothing - fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} - fInv = xftpFileInvitation fileName (fromIntegral $ T.length descr) fileDescr - aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd - chSize <- asks $ fileChunkSize . config - fileTransferMeta <- withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) chSize + fileTransferMeta <- xftpSndFileRedirect_ user vfd pure CRSndFileStartXFTPDirect {user, fileTransferMeta} APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ QuitChat -> liftIO exitSuccess @@ -3294,14 +3288,20 @@ processAgentMsgSndFile _corrId aFileId msg = liftIO $ updateCIFileStatus db user fileId status lookupChatItemByFileId db vr user fileId toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal - SFDONE sndDescr rfds -> do + SFDONE sndDescr [] -> do + withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) + withAgent (`xftpDeleteSndFileInternal` aFileId) + + SFDONE sndDescr rfds@(rvfd : _) -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId case ci of Nothing -> do withAgent (`xftpDeleteSndFileInternal` aFileId) withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) - toView $ CRSndFileCompleteXFTP user ci ft $ mapMaybe fileDescrURI rfds + case mapMaybe fileDescrURI rfds of + [] -> xftpSndFileRedirect_ user rvfd >>= toView . CRSndFileRedirectXFTP user ft + uris -> toView $ CRSndFileCompleteXFTP user ci ft uris Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do @@ -6984,3 +6984,13 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr \db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr saveMemberFD _ = pure () pure (fInv, ciFile, ft) + +xftpSndFileRedirect_ :: ChatMonad m => User -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect_ user vfd = do + let fileName = "redirect.yaml" + file = CryptoFile fileName Nothing + fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} + fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) fileDescr + aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd + chSize <- asks $ fileChunkSize . config + withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) chSize diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 420467228c..ecc9ada2de 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -610,6 +610,7 @@ data ChatResponse | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileStartXFTPDirect {user :: User, fileTransferMeta :: FileTransferMeta} -- sent by _upload instead of ok, signalling fileId to expect | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} + | CRSndFileRedirectXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 534d5ead4b..4f256975a5 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -208,6 +208,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRSndFileStartXFTP {} -> [] CRSndFileStartXFTPDirect {} -> [] CRSndFileProgressXFTP {} -> [] + CRSndFileRedirectXFTP u ft ftRedirect -> ttyUser u $ directUploadRedirect ft ftRedirect CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ directUploadComplete ft uris CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] @@ -1568,6 +1569,10 @@ uploadingFile status = \case [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] _ -> [status <> " uploading file"] +directUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] +directUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = + [fileTransferStr fileId fileName <> " upload complete. preparing redirect file " <> sShow redirectId] + directUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] directUploadComplete FileTransferMeta {fileId, fileName} = \case [] -> [fileTransferStr fileId fileName <> " upload complete."] diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 0a9073276a..d9d6f0a686 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -79,7 +79,8 @@ chatFileTests = do it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles describe "file transfer over XFTP without chat items" $ do - it "directly send and receive file" testXFTPDirect + it "directly send and receive small file" testXFTPDirectSmall + it "directly send and receive large file" testXFTPDirectLarge it "directly send and receive file via description" testXFTPDirectDescr -- TODO: send/receive with cfArgs @@ -1550,8 +1551,8 @@ testProhibitFiles = where cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"} -testXFTPDirect :: HasCallStack => FilePath -> IO () -testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do +testXFTPDirectSmall :: HasCallStack => FilePath -> IO () +testXFTPDirectSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do logNote "sending" src ##> "/_upload 1 ./tests/fixtures/test.jpg" @@ -1573,6 +1574,28 @@ testXFTPDirect = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do srcBody <- B.readFile "./tests/fixtures/test.jpg" B.readFile dstFile `shouldReturn` srcBody +testXFTPDirectLarge :: HasCallStack => FilePath -> IO () +testXFTPDirectLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + threadDelay 250000 + src <## "file 1 (testfile.in) upload complete. preparing redirect file 2" + src <## "file 2 (redirect.yaml) upload complete. download with:" + uri <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/testfile.out" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) + dst <## "ok" + threadDelay 250000 + dst <## "completed receiving file" + getTermLine dst `shouldReturn` dstFile + srcBody <- B.readFile "./tests/tmp/testfile.in" + B.readFile dstFile `shouldReturn` srcBody + testXFTPDirectDescr :: HasCallStack => FilePath -> IO () testXFTPDirectDescr = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do From e522c2e24397e113ec33bbb65b19e9559da023c7 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 14:52:36 +0200 Subject: [PATCH 10/33] send "upload complete" event with the original file id --- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 23 +++++++++++-------- .../Migrations/M20240214_redirect_file_id.hs | 18 +++++++++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 3 ++- src/Simplex/Chat/Store/Files.hs | 22 +++++++++--------- src/Simplex/Chat/Store/Migrations.hs | 4 +++- src/Simplex/Chat/Types.hs | 1 + src/Simplex/Chat/View.hs | 2 +- tests/ChatTests/Files.hs | 4 ++-- 9 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 8035892414..9e2b863192 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -133,6 +133,7 @@ library Simplex.Chat.Migrations.M20240104_members_profile_update Simplex.Chat.Migrations.M20240115_block_member_for_all Simplex.Chat.Migrations.M20240122_indexes + Simplex.Chat.Migrations.M20240214_redirect_file_id Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 6a59deba54..6d2eff89c8 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2042,7 +2042,7 @@ processChatCommand' vr = \case APIXFTPDescriptionUpload userId fileTransferId -> withUserId userId $ \user -> do descr <- withStore $ \db -> takeExtraSndFTDescr db user fileTransferId vfd <- liftEitherWith (ChatError . CEInternalError . ("description yaml decode error: " <>)) . strDecode $ encodeUtf8 descr - fileTransferMeta <- xftpSndFileRedirect_ user vfd + fileTransferMeta <- xftpSndFileRedirect_ user Nothing vfd pure CRSndFileStartXFTPDirect {user, fileTransferMeta} APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ QuitChat -> liftIO exitSuccess @@ -3277,7 +3277,7 @@ processAgentMsgSndFile _corrId aFileId msg = where process :: User -> m () process user = do - (ft@FileTransferMeta {fileId, cancelled}, sfts) <- withStore $ \db -> do + (ft@FileTransferMeta {fileId, xftpRedirectFor, cancelled}, sfts) <- withStore $ \db -> do fileId <- getXFTPSndFileDBId db user $ AgentSndFileId aFileId getSndFileTransfer db user fileId vr <- chatVersionRange @@ -3289,9 +3289,10 @@ processAgentMsgSndFile _corrId aFileId msg = lookupChatItemByFileId db vr user fileId toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal SFDONE sndDescr [] -> do + -- should not happen + logError "File sent without receiver descriptions" withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) withAgent (`xftpDeleteSndFileInternal` aFileId) - SFDONE sndDescr rfds@(rvfd : _) -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId @@ -3300,8 +3301,10 @@ processAgentMsgSndFile _corrId aFileId msg = withAgent (`xftpDeleteSndFileInternal` aFileId) withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) case mapMaybe fileDescrURI rfds of - [] -> xftpSndFileRedirect_ user rvfd >>= toView . CRSndFileRedirectXFTP user ft - uris -> toView $ CRSndFileCompleteXFTP user ci ft uris + [] -> xftpSndFileRedirect_ user (Just fileId) rvfd >>= toView . CRSndFileRedirectXFTP user ft + uris -> do + ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor + toView $ CRSndFileCompleteXFTP user ci ft' uris Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do @@ -6967,7 +6970,7 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) -- TODO CRSndFileStart event for XFTP chSize <- asks $ fileChunkSize . config - ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup_ file fInv (AgentSndFileId aFileId) chSize + ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup_ file fInv (AgentSndFileId aFileId) Nothing chSize let fileSource = Just $ CryptoFile filePath cfArgs ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} case contactOrGroup_ of @@ -6985,12 +6988,12 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr saveMemberFD _ = pure () pure (fInv, ciFile, ft) -xftpSndFileRedirect_ :: ChatMonad m => User -> ValidFileDescription 'FRecipient -> m FileTransferMeta -xftpSndFileRedirect_ user vfd = do - let fileName = "redirect.yaml" +xftpSndFileRedirect_ :: ChatMonad m => User -> Maybe FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect_ user ftId_ vfd = do + let fileName = maybe "redirect.yaml" (show) ftId_ file = CryptoFile fileName Nothing fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) fileDescr aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd chSize <- asks $ fileChunkSize . config - withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) chSize + withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) ftId_ chSize diff --git a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs new file mode 100644 index 0000000000..f5f496deaf --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240214_redirect_file_id where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240214_redirect_file_id :: Query +m20240214_redirect_file_id = + [sql| +ALTER TABLE files ADD COLUMN redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE; +|] + +down_m20240214_redirect_file_id :: Query +down_m20240214_redirect_file_id = + [sql| +ALTER TABLE files DROP COLUMN redirect_file_id; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index efed6d168a..e5e623e8f4 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -193,7 +193,8 @@ CREATE TABLE files( protocol TEXT NOT NULL DEFAULT 'smp', file_crypto_key BLOB, file_crypto_nonce BLOB, - note_folder_id INTEGER DEFAULT NULL REFERENCES note_folders ON DELETE CASCADE + note_folder_id INTEGER DEFAULT NULL REFERENCES note_folders ON DELETE CASCADE, + redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE ); CREATE TABLE snd_files( file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE, diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 5e94745100..e14e615586 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -187,7 +187,7 @@ createSndDirectFileTransfer db userId Contact {contactId} filePath FileInvitatio db "INSERT INTO snd_files (file_id, file_status, file_inline, connection_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" (fileId, fileStatus, fileInline, connId, currentTs, currentTs) - pure FileTransferMeta {fileId, xftpSndFile = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} + pure FileTransferMeta {fileId, xftpSndFile = Nothing, xftpRedirectFor = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} createSndDirectFTConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> SubscriptionMode -> IO () createSndDirectFTConnection db user@User {userId} fileId (cmdId, acId) subMode = do @@ -207,7 +207,7 @@ createSndGroupFileTransfer db userId GroupInfo {groupId} filePath FileInvitation "INSERT INTO files (user_id, group_id, file_name, file_path, file_size, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)" ((userId, groupId, fileName, filePath, fileSize, chunkSize) :. (fileInline, CIFSSndStored, FPSMP, currentTs, currentTs)) fileId <- insertedRowId db - pure FileTransferMeta {fileId, xftpSndFile = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} + pure FileTransferMeta {fileId, xftpSndFile = Nothing, xftpRedirectFor = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} createSndGroupFileTransferConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> GroupMember -> SubscriptionMode -> IO () createSndGroupFileTransferConnection db user@User {userId} fileId (cmdId, acId) GroupMember {groupMemberId} subMode = do @@ -280,16 +280,16 @@ getSndFTViaMsgDelivery db User {userId} Connection {connId, agentConnId} agentMs (\n -> SndFileTransfer {fileId, fileStatus, fileName, fileSize, chunkSize, filePath, fileDescrId, fileInline, groupMemberId, recipientDisplayName = n, connId, agentConnId}) <$> (contactName_ <|> memberName_) -createSndFileTransferXFTP :: DB.Connection -> User -> Maybe ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta -createSndFileTransferXFTP db User {userId} contactOrGroup_ (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do +createSndFileTransferXFTP :: DB.Connection -> User -> Maybe ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Maybe FileTransferId -> Integer -> IO FileTransferMeta +createSndFileTransferXFTP db User {userId} contactOrGroup_ (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId xftpRedirectFor chunkSize = do currentTs <- getCurrentTime let xftpSndFile = Just XFTPSndFile {agentSndFileId, privateSndFileDescr = Nothing, agentSndFileDeleted = False, cryptoArgs} DB.execute db - "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - (maybe (Nothing, Nothing) contactAndGroupIds contactOrGroup_ :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) + "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, redirect_file_id, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + (maybe (Nothing, Nothing) contactAndGroupIds contactOrGroup_ :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize) :. (xftpRedirectFor, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) fileId <- insertedRowId db - pure FileTransferMeta {fileId, xftpSndFile, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} + pure FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} createSndFTDescrXFTP :: DB.Connection -> User -> Maybe GroupMember -> Connection -> FileTransferMeta -> FileDescr -> IO () createSndFTDescrXFTP db User {userId} m Connection {connId} FileTransferMeta {fileId} FileDescr {fileDescrText, fileDescrPartNo, fileDescrComplete} = do @@ -936,17 +936,17 @@ getFileTransferMeta_ db userId fileId = DB.query db [sql| - SELECT file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled + SELECT file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled, redirect_file_id FROM files WHERE user_id = ? AND file_id = ? |] (userId, fileId) where - fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool) -> FileTransferMeta - fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_) = + fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta + fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = let cryptoArgs = CFArgs <$> fileKey <*> fileNonce xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ - in FileTransferMeta {fileId, xftpSndFile, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 2f5e61a5e6..832f07dcb9 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -98,6 +98,7 @@ import Simplex.Chat.Migrations.M20240102_note_folders import Simplex.Chat.Migrations.M20240104_members_profile_update import Simplex.Chat.Migrations.M20240115_block_member_for_all import Simplex.Chat.Migrations.M20240122_indexes +import Simplex.Chat.Migrations.M20240214_redirect_file_id import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -195,7 +196,8 @@ schemaMigrations = ("20240102_note_folders", m20240102_note_folders, Just down_m20240102_note_folders), ("20240104_members_profile_update", m20240104_members_profile_update, Just down_m20240104_members_profile_update), ("20240115_block_member_for_all", m20240115_block_member_for_all, Just down_m20240115_block_member_for_all), - ("20240122_indexes", m20240122_indexes, Just down_m20240122_indexes) + ("20240122_indexes", m20240122_indexes, Just down_m20240122_indexes), + ("20240214_redirect_file_id", m20240214_redirect_file_id, Just down_m20240214_redirect_file_id) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index e65e1a916f..c340130f8a 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1210,6 +1210,7 @@ data FileTransfer data FileTransferMeta = FileTransferMeta { fileId :: FileTransferId, xftpSndFile :: Maybe XFTPSndFile, + xftpRedirectFor :: Maybe FileTransferId, fileName :: String, filePath :: String, fileSize :: Integer, diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 4f256975a5..62b29f0272 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1571,7 +1571,7 @@ uploadingFile status = \case directUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] directUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = - [fileTransferStr fileId fileName <> " upload complete. preparing redirect file " <> sShow redirectId] + [fileTransferStr fileId fileName <> " uploaded, preparing redirect file " <> sShow redirectId] directUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] directUploadComplete FileTransferMeta {fileId, fileName} = \case diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index d9d6f0a686..5cad849d55 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1582,8 +1582,8 @@ testXFTPDirectLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> d logNote "sending" src ##> "/_upload 1 ./tests/tmp/testfile.in" threadDelay 250000 - src <## "file 1 (testfile.in) upload complete. preparing redirect file 2" - src <## "file 2 (redirect.yaml) upload complete. download with:" + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" uri <- getTermLine src logNote "receiving" From 3b875030e8e5d00b14a2d0001c28615b6456760c Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 14:59:29 +0200 Subject: [PATCH 11/33] remove description upload command --- src/Simplex/Chat.hs | 16 +++++----------- src/Simplex/Chat/Controller.hs | 1 - src/Simplex/Chat/Store/Files.hs | 8 -------- tests/ChatTests/Files.hs | 28 ---------------------------- 4 files changed, 5 insertions(+), 48 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 6d2eff89c8..a40eba75d6 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2039,11 +2039,6 @@ processChatCommand' vr = \case fileSize <- liftIO $ CF.getFileContentsSize file (_, _, fileTransferMeta) <- xftpSndFileTransfer_ user file fileSize 1 Nothing pure CRSndFileStartXFTPDirect {user, fileTransferMeta} - APIXFTPDescriptionUpload userId fileTransferId -> withUserId userId $ \user -> do - descr <- withStore $ \db -> takeExtraSndFTDescr db user fileTransferId - vfd <- liftEitherWith (ChatError . CEInternalError . ("description yaml decode error: " <>)) . strDecode $ encodeUtf8 descr - fileTransferMeta <- xftpSndFileRedirect_ user Nothing vfd - pure CRSndFileStartXFTPDirect {user, fileTransferMeta} APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ QuitChat -> liftIO exitSuccess ShowVersion -> do @@ -3301,7 +3296,7 @@ processAgentMsgSndFile _corrId aFileId msg = withAgent (`xftpDeleteSndFileInternal` aFileId) withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) case mapMaybe fileDescrURI rfds of - [] -> xftpSndFileRedirect_ user (Just fileId) rvfd >>= toView . CRSndFileRedirectXFTP user ft + [] -> xftpSndFileRedirect_ user fileId rvfd >>= toView . CRSndFileRedirectXFTP user ft uris -> do ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor toView $ CRSndFileCompleteXFTP user ci ft' uris @@ -6770,7 +6765,6 @@ chatCommandP = "/stop remote ctrl" $> StopRemoteCtrl, "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), "/_upload " *> (APIXFTPDirectUpload <$> A.decimal <* A.space <*> cryptoFileP), - "/_upload description " *> (APIXFTPDescriptionUpload <$> A.decimal <* A.space <*> A.decimal), "/_download " *> (APIXFTPDirectDownload <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, @@ -6988,12 +6982,12 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr saveMemberFD _ = pure () pure (fInv, ciFile, ft) -xftpSndFileRedirect_ :: ChatMonad m => User -> Maybe FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta -xftpSndFileRedirect_ user ftId_ vfd = do - let fileName = maybe "redirect.yaml" (show) ftId_ +xftpSndFileRedirect_ :: ChatMonad m => User -> FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect_ user ftId vfd = do + let fileName = "redirect.yaml" file = CryptoFile fileName Nothing fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) fileDescr aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd chSize <- asks $ fileChunkSize . config - withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) ftId_ chSize + withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) (Just ftId) chSize diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index ecc9ada2de..41d9617922 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -454,7 +454,6 @@ data ChatCommand | StopRemoteCtrl -- Stop listening for announcements or terminate an active session | DeleteRemoteCtrl RemoteCtrlId -- Remove all local data associated with a remote controller session | APIXFTPDirectUpload UserId CryptoFile - | APIXFTPDescriptionUpload UserId FileTransferId | APIXFTPDirectDownload UserId FileDescriptionURI CryptoFile | QuitChat | ShowVersion diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index e14e615586..d1985e26df 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -28,7 +28,6 @@ module Simplex.Chat.Store.Files setSndFTPrivateSndDescr, updateSndFTDescrXFTP, createExtraSndFTDescrs, - takeExtraSndFTDescr, updateSndFTDeliveryXFTP, setSndFTAgentDeleted, getXFTPSndFileDBId, @@ -336,13 +335,6 @@ createExtraSndFTDescrs db User {userId} fileId rfdTexts = do "INSERT INTO extra_xftp_file_descriptions (file_id, user_id, file_descr_text, created_at, updated_at) VALUES (?,?,?,?,?)" (fileId, userId, rfdText, currentTs, currentTs) -takeExtraSndFTDescr :: DB.Connection -> User -> FileTransferId -> ExceptT StoreError IO Text -takeExtraSndFTDescr db User {userId} fileId = do - (eId :: Int64, descr) <- ExceptT . firstRow id (SEExtraFileDescrNotFound fileId) $ - DB.query db "SELECT extra_file_descr_id, file_descr_text FROM extra_xftp_file_descriptions WHERE user_id = ? AND file_id = ?" (userId, fileId) - liftIO $ DB.execute db "DELETE FROM extra_xftp_file_descriptions WHERE extra_file_descr_id = ?" (Only eId) - pure descr - updateSndFTDeliveryXFTP :: DB.Connection -> SndFileTransfer -> Int64 -> IO () updateSndFTDeliveryXFTP db SndFileTransfer {connId, fileId, fileDescrId} msgDeliveryId = DB.execute diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 5cad849d55..c0c17d910d 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -81,8 +81,6 @@ chatFileTests = do describe "file transfer over XFTP without chat items" $ do it "directly send and receive small file" testXFTPDirectSmall it "directly send and receive large file" testXFTPDirectLarge - it "directly send and receive file via description" testXFTPDirectDescr - -- TODO: send/receive with cfArgs runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () runTestFileTransfer alice bob = do @@ -1596,32 +1594,6 @@ testXFTPDirectLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> d srcBody <- B.readFile "./tests/tmp/testfile.in" B.readFile dstFile `shouldReturn` srcBody -testXFTPDirectDescr :: HasCallStack => FilePath -> IO () -testXFTPDirectDescr = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do - withXFTPServer $ do - logNote "sending" - src ##> "/_upload 1 ./tests/fixtures/test.jpg" - threadDelay 250000 - src <## "file 1 (test.jpg) upload complete. download with:" - -- ignoring file descriptions - _uri1 <- getTermLine src - _uri2 <- getTermLine src - _uri3 <- getTermLine src - _uri4 <- getTermLine src - src ##> "/_upload description 1 1" - src <## "file 2 (redirect.yaml) upload complete. download with:" - uri <- getTermLine src - - logNote "receiving" - let dstFile = "./tests/tmp/test.jpg" - dst ##> ("/_download 1 " <> uri <> " " <> dstFile) - dst <## "ok" - threadDelay 250000 - dst <## "completed receiving file" - getTermLine dst `shouldReturn` dstFile - srcBody <- B.readFile "./tests/fixtures/test.jpg" - B.readFile dstFile `shouldReturn` srcBody - startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" From 5867dc7e0d30a0668b2cc85cdac3bac9e402d9fb Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:50:47 +0400 Subject: [PATCH 12/33] add index --- src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs | 4 ++++ src/Simplex/Chat/Migrations/chat_schema.sql | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs index f5f496deaf..da8f4d413b 100644 --- a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs +++ b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs @@ -9,10 +9,14 @@ m20240214_redirect_file_id :: Query m20240214_redirect_file_id = [sql| ALTER TABLE files ADD COLUMN redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE; + +CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); |] down_m20240214_redirect_file_id :: Query down_m20240214_redirect_file_id = [sql| +DROP INDEX idx_files_redirect_file_id; + ALTER TABLE files DROP COLUMN redirect_file_id; |] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index e5e623e8f4..b5726cae2d 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -855,3 +855,4 @@ CREATE INDEX idx_chat_items_notes_item_status on chat_items( note_folder_id, item_status ); +CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); From b92d4cdd7bfd472161eb90a3735b2b1844bf0e19 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:41:33 +0400 Subject: [PATCH 13/33] refactor --- src/Simplex/Chat.hs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index a40eba75d6..069745432f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3283,12 +3283,7 @@ processAgentMsgSndFile _corrId aFileId msg = liftIO $ updateCIFileStatus db user fileId status lookupChatItemByFileId db vr user fileId toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal - SFDONE sndDescr [] -> do - -- should not happen - logError "File sent without receiver descriptions" - withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) - withAgent (`xftpDeleteSndFileInternal` aFileId) - SFDONE sndDescr rfds@(rvfd : _) -> do + SFDONE sndDescr rfds -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId case ci of @@ -3296,7 +3291,9 @@ processAgentMsgSndFile _corrId aFileId msg = withAgent (`xftpDeleteSndFileInternal` aFileId) withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) case mapMaybe fileDescrURI rfds of - [] -> xftpSndFileRedirect_ user fileId rvfd >>= toView . CRSndFileRedirectXFTP user ft + [] -> case rfds of + [] -> logError "File sent without receiver descriptions" -- should not happen + (rfd : _) -> xftpSndFileRedirect_ user fileId rfd >>= toView . CRSndFileRedirectXFTP user ft uris -> do ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor toView $ CRSndFileCompleteXFTP user ci ft' uris From e2a7447efdf9c0c11e76775d84861d518fe04869 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:57:44 +0400 Subject: [PATCH 14/33] update simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 482d3e6646..f536f7d667 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 2f7a288280f4ae74ef1a70f3cb173345c95a4da1 + tag: 004597c764a8f8d5dc1e03fbad713b02a878c2ed source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index ef56384c2e..d4f602ddb9 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."2f7a288280f4ae74ef1a70f3cb173345c95a4da1" = "1n3szfd2lzawd9vr4sz4iisjg9hsi2rbpimcq4y4m6vcxg21q139"; + "https://github.com/simplex-chat/simplexmq.git"."004597c764a8f8d5dc1e03fbad713b02a878c2ed" = "0c87qjlki5f3p598jazhc9267059fxw5xvqi3zx3g4sqy49aww6a"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 863962dc6a9b5d07ebebf59d860ea6cb439d3bb1 Mon Sep 17 00:00:00 2001 From: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:59:56 +0200 Subject: [PATCH 15/33] Apply suggestions from code review Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- src/Simplex/Chat/Store/Files.hs | 4 ++-- src/Simplex/Chat/View.hs | 8 ++++---- tests/ChatTests/Files.hs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index d1985e26df..7120101bd1 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -538,7 +538,7 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD (fileId, FSNew, fileConnReq, fileInline, rcvFileInline, groupMemberId, rfdId, currentTs, currentTs) pure RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = f, fileStatus = RFSNew, rcvFileInline, senderDisplayName = c, chunkSize, cancelled = False, grpMemberId = Just groupMemberId, cryptoArgs = Nothing} -createRcvDirectFileTransfer :: DB.Connection -> UserId -> CryptoFile -> Int64 -> Word32 -> ExceptT StoreError IO Int64 +createRcvStandaloneFileTransfer :: DB.Connection -> UserId -> CryptoFile -> Int64 -> Word32 -> ExceptT StoreError IO Int64 createRcvDirectFileTransfer db userId (CryptoFile filePath cfArgs_) fileSize chunkSize = do currentTs <- liftIO getCurrentTime fileId <- liftIO $ do @@ -681,7 +681,7 @@ getRcvFileTransfer_ db userId fileId = do (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool) :. (Maybe Int64, Maybe AgentConnId) -> ExceptT StoreError IO RcvFileTransfer rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted) :. (connId_, agentConnId_)) = - case contactName_ <|> memberName_ <|> directName_ of + case contactName_ <|> memberName_ <|> standaloneName_ of Nothing -> throwError $ SERcvFileInvalid fileId Just name -> case fileStatus' of diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 62b29f0272..cc6698f851 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1569,12 +1569,12 @@ uploadingFile status = \case [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] _ -> [status <> " uploading file"] -directUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] -directUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = +standaloneUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] +standaloneUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = [fileTransferStr fileId fileName <> " uploaded, preparing redirect file " <> sShow redirectId] -directUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] -directUploadComplete FileTransferMeta {fileId, fileName} = \case +standaloneUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] +standaloneUploadComplete FileTransferMeta {fileId, fileName} = \case [] -> [fileTransferStr fileId fileName <> " upload complete."] uris -> fileTransferStr fileId fileName <> " upload complete. download with:" diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index c0c17d910d..7b558d822e 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -79,8 +79,8 @@ chatFileTests = do it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles describe "file transfer over XFTP without chat items" $ do - it "directly send and receive small file" testXFTPDirectSmall - it "directly send and receive large file" testXFTPDirectLarge + it "send and receive small standalone file" testXFTPStandaloneSmall + it "send and receive large standalone file" testXFTPStandaloneLarge runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () runTestFileTransfer alice bob = do From 0b06ac80d332970ca1405cdc9bb78384ab9395e5 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 18:41:58 +0200 Subject: [PATCH 16/33] fix /fc command for non-chat uploads --- src/Simplex/Chat.hs | 20 ++++++++++---------- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Store/Files.hs | 17 +++++++++++------ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 069745432f..2a2499e1f4 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1936,16 +1936,16 @@ processChatCommand' vr = \case | otherwise -> do fileAgentConnIds <- cancelSndFile user ftm fts True deleteAgentConnectionsAsync user fileAgentConnIds - sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId - withStore (\db -> getChatRefByFileId db user fileId) >>= \case - ChatRef CTDirect contactId -> do - contact <- withStore $ \db -> getContact db user contactId + withStore (\db -> liftIO $ lookupChatRefByFileId db user fileId) >>= \case + Nothing -> pure () + Just (ChatRef CTDirect contactId) -> do + (contact, sharedMsgId) <- withStore $ \db -> (,) <$> getContact db user contactId <*> getSharedMsgIdByFileId db userId fileId void . sendDirectContactMessage contact $ XFileCancel sharedMsgId - ChatRef CTGroup groupId -> do - Group gInfo ms <- withStore $ \db -> getGroup db vr user groupId + Just (ChatRef CTGroup groupId) -> do + (Group gInfo ms, sharedMsgId) <- withStore $ \db -> (,) <$> getGroup db vr user groupId <*> getSharedMsgIdByFileId db userId fileId void . sendGroupMessage user gInfo ms $ XFileCancel sharedMsgId - _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" - ci <- withStore $ \db -> getChatItemByFileId db vr user fileId + Just _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId pure $ CRSndFileCancelled user ci ftm fts where fileCancelledOrCompleteSMP SndFileTransfer {fileStatus = s} = @@ -4102,8 +4102,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case err of SMP SMP.AUTH -> unless (fileStatus == FSCancelled) $ do ci <- withStore $ \db -> do - getChatRefByFileId db user fileId >>= \case - ChatRef CTDirect _ -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled + liftIO (lookupChatRefByFileId db user fileId) >>= \case + Just (ChatRef CTDirect _) -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled _ -> pure () lookupChatItemByFileId db vr user fileId toView $ CRSndFileRcvCancelled user ci ft diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 41d9617922..6fed6a801c 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -605,7 +605,7 @@ data ChatResponse | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileCancelled {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} + | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileStartXFTPDirect {user :: User, fileTransferMeta :: FileTransferMeta} -- sent by _upload instead of ok, signalling fileId to expect | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 7120101bd1..e3c9ff2f45 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -39,6 +39,7 @@ module Simplex.Chat.Store.Files getGroupFileIdBySharedMsgId, getDirectFileIdBySharedMsgId, getChatRefByFileId, + lookupChatRefByFileId, updateSndFileStatus, createSndFileChunk, updateSndFileChunkMsg, @@ -87,13 +88,14 @@ import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class import Data.Either (rights) +import Data.Functor ((<&>)) import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust, listToMaybe) -import Data.Word (Word32) import Data.Text (Text) import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Data.Type.Equality +import Data.Word (Word32) import Database.SQLite.Simple (Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.ToField (ToField) @@ -423,11 +425,14 @@ getDirectFileIdBySharedMsgId db User {userId} Contact {contactId} sharedMsgId = (userId, contactId, sharedMsgId) getChatRefByFileId :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO ChatRef -getChatRefByFileId db User {userId} fileId = - liftIO getChatRef >>= \case - [(Just contactId, Nothing)] -> pure $ ChatRef CTDirect contactId - [(Nothing, Just groupId)] -> pure $ ChatRef CTGroup groupId - _ -> throwError $ SEInternalError "could not retrieve chat ref by file id" +getChatRefByFileId db user fileId = liftIO (lookupChatRefByFileId db user fileId) >>= maybe (throwError $ SEInternalError "could not retrieve chat ref by file id") pure + +lookupChatRefByFileId :: DB.Connection -> User -> Int64 -> IO (Maybe ChatRef) +lookupChatRefByFileId db User {userId} fileId = + getChatRef <&> \case + [(Just contactId, Nothing)] -> Just $ ChatRef CTDirect contactId + [(Nothing, Just groupId)] -> Just $ ChatRef CTGroup groupId + _ -> Nothing where getChatRef = DB.query From 673856a47f8589b9541701e634512f8fa9fa0f1d Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Wed, 14 Feb 2024 19:16:03 +0200 Subject: [PATCH 17/33] fix --- src/Simplex/Chat.hs | 14 ++++++++------ src/Simplex/Chat/Store/Files.hs | 6 +++--- src/Simplex/Chat/View.hs | 17 +++++++---------- tests/ChatTests/Files.hs | 16 ++++++++-------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2a2499e1f4..0a7a9e493d 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2795,13 +2795,15 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m () receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do - fileId <- withStore $ \db -> createRcvDirectFileTransfer db userId cf fileSize chunkSize + fileId <- withStore $ \db -> createRcvStandaloneFileTransfer db userId cf fileSize chunkSize aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) description cryptoArgs - -- startReceivingFile user fileId - withStore' $ \db -> do - updateRcvFileStatus db fileId FSConnected - updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 - updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + ft <- withStore $ \db -> do + liftIO $ do + updateRcvFileStatus db fileId FSConnected + updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 + updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + getRcvFileTransfer db user fileId + toView $ CRRcvFileStart user Nothing ft where FD.ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index e3c9ff2f45..9b36da2460 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -47,7 +47,7 @@ module Simplex.Chat.Store.Files deleteSndFileChunks, createRcvFileTransfer, createRcvGroupFileTransfer, - createRcvDirectFileTransfer, + createRcvStandaloneFileTransfer, appendRcvFD, getRcvFileDescrByRcvFileId, getRcvFileDescrBySndFileId, @@ -544,7 +544,7 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD pure RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = f, fileStatus = RFSNew, rcvFileInline, senderDisplayName = c, chunkSize, cancelled = False, grpMemberId = Just groupMemberId, cryptoArgs = Nothing} createRcvStandaloneFileTransfer :: DB.Connection -> UserId -> CryptoFile -> Int64 -> Word32 -> ExceptT StoreError IO Int64 -createRcvDirectFileTransfer db userId (CryptoFile filePath cfArgs_) fileSize chunkSize = do +createRcvStandaloneFileTransfer db userId (CryptoFile filePath cfArgs_) fileSize chunkSize = do currentTs <- liftIO getCurrentTime fileId <- liftIO $ do DB.execute @@ -696,7 +696,7 @@ getRcvFileTransfer_ db userId fileId = do FSComplete -> ft name . RFSComplete <$> rfi FSCancelled -> ft name . RFSCancelled <$> rfi_ where - directName_ = case (connId_, agentRcvFileId, filePath_) of + standaloneName_ = case (connId_, agentRcvFileId, filePath_) of (Nothing, Just _, Just _) -> Just "direct" -- filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer _ -> Nothing ft senderDisplayName fileStatus = diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index cc6698f851..7ba76778a1 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,18 +198,18 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile - CRRcvFileStart u ci _ -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left "") Right ci) + CRRcvFileStart u ci ft -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left ft) Right ci) CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) - CRRcvFileCompleteXFTP u path _ -> ttyUser u $ receivingFile_' hu testView "completed" (Left path) + CRRcvFileCompleteXFTP u _ ft -> ttyUser u $ receivingFile_' hu testView "completed" (Left ft) CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u ci e _ -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left "") Right ci) <> [sShow e] + CRRcvFileError u ci e ft -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left ft) Right ci) <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndFileStartXFTP {} -> [] CRSndFileStartXFTPDirect {} -> [] CRSndFileProgressXFTP {} -> [] - CRSndFileRedirectXFTP u ft ftRedirect -> ttyUser u $ directUploadRedirect ft ftRedirect - CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ directUploadComplete ft uris + CRSndFileRedirectXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect + CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ standaloneUploadComplete ft uris CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] CRSndFileError u ci _ -> ttyUser u $ uploadingFile "error" ci @@ -1610,7 +1610,7 @@ savingFile' (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource ["saving file " <> sShow fileId <> fileFrom chat chatDir <> " to " <> plain filePath] savingFile' _ = ["saving file"] -- shouldn't happen -receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> Either FilePath AChatItem -> [StyledString] +receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> Either RcvFileTransfer AChatItem -> [StyledString] receivingFile_' hu testView status (Right (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir})) = [plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr where @@ -1624,10 +1624,7 @@ receivingFile_' hu testView status (Right (AChatItem _ _ chat ChatItem {file = J highlight ("/get remote file " <> show rhId <> " " <> LB.unpack (J.encode RemoteFile {userId, fileId, sent = False, fileSource = f})) ] _ -> [] -receivingFile_' _ _ status (Left path) | not (null path) = - [ plain status <> " receiving file" - , plain path - ] +receivingFile_' _ _ status (Left RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}}) = [plain status <> " receiving " <> fileTransferStr fileId fileName] receivingFile_' _ _ status _ = [plain status <> " receiving file"] viewLocalFile :: StyledString -> CIFile d -> CurrentTime -> TimeZone -> CIMeta c d -> [StyledString] diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 7b558d822e..b79dd03a91 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1549,8 +1549,8 @@ testProhibitFiles = where cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"} -testXFTPDirectSmall :: HasCallStack => FilePath -> IO () -testXFTPDirectSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do +testXFTPStandaloneSmall :: HasCallStack => FilePath -> IO () +testXFTPStandaloneSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do logNote "sending" src ##> "/_upload 1 ./tests/fixtures/test.jpg" @@ -1567,13 +1567,13 @@ testXFTPDirectSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> d dst ##> ("/_download 1 " <> uri3 <> " " <> dstFile) dst <## "ok" threadDelay 250000 - dst <## "completed receiving file" - getTermLine dst `shouldReturn` dstFile + dst <## "started receiving file 1 (test.jpg)" + dst <## "completed receiving file 1 (test.jpg)" srcBody <- B.readFile "./tests/fixtures/test.jpg" B.readFile dstFile `shouldReturn` srcBody -testXFTPDirectLarge :: HasCallStack => FilePath -> IO () -testXFTPDirectLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do +testXFTPStandaloneLarge :: HasCallStack => FilePath -> IO () +testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] @@ -1589,8 +1589,8 @@ testXFTPDirectLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> d dst ##> ("/_download 1 " <> uri <> " " <> dstFile) dst <## "ok" threadDelay 250000 - dst <## "completed receiving file" - getTermLine dst `shouldReturn` dstFile + dst <## "started receiving file 1 (testfile.out)" + dst <## "completed receiving file 1 (testfile.out)" srcBody <- B.readFile "./tests/tmp/testfile.in" B.readFile dstFile `shouldReturn` srcBody From 152567171c0eabff2dbc6bdfb51e89b961902331 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 15 Feb 2024 09:16:03 +0000 Subject: [PATCH 18/33] rename (tests fail) --- src/Simplex/Chat.hs | 19 ++++++++++--------- src/Simplex/Chat/Controller.hs | 9 +++++---- src/Simplex/Chat/View.hs | 9 +++++---- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 0a7a9e493d..82478e745b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2035,11 +2035,13 @@ processChatCommand' vr = \case StopRemoteCtrl -> withUser_ $ stopRemoteCtrl >> ok_ ListRemoteCtrls -> withUser_ $ CRRemoteCtrlList <$> listRemoteCtrls DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ - APIXFTPDirectUpload userId file -> withUserId userId $ \user -> do + APIUploadStandaloneFile userId file -> withUserId userId $ \user -> do fileSize <- liftIO $ CF.getFileContentsSize file (_, _, fileTransferMeta) <- xftpSndFileTransfer_ user file fileSize 1 Nothing - pure CRSndFileStartXFTPDirect {user, fileTransferMeta} - APIXFTPDirectDownload userId uri file -> withUserId userId $ \user -> receiveViaURI user uri file >> ok_ + pure CRSndStandaloneFileCreated {user, fileTransferMeta} + APIDownloadStandaloneFile userId uri file -> withUserId userId $ \user -> do + ft <- receiveViaURI user uri file + pure $ CRRcvStandaloneFileCreated user ft QuitChat -> liftIO exitSuccess ShowVersion -> do -- simplexmqCommitQ makes iOS builds crash m( @@ -2793,17 +2795,16 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} startReceivingFile user fileId withStoreCtx' (Just "receiveViaCompleteFD, updateRcvFileAgentId") $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) -receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m () +receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m RcvFileTransfer receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do fileId <- withStore $ \db -> createRcvStandaloneFileTransfer db userId cf fileSize chunkSize aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) description cryptoArgs - ft <- withStore $ \db -> do + withStore $ \db -> do liftIO $ do updateRcvFileStatus db fileId FSConnected updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) getRcvFileTransfer db user fileId - toView $ CRRcvFileStart user Nothing ft where FD.ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description @@ -3295,7 +3296,7 @@ processAgentMsgSndFile _corrId aFileId msg = case mapMaybe fileDescrURI rfds of [] -> case rfds of [] -> logError "File sent without receiver descriptions" -- should not happen - (rfd : _) -> xftpSndFileRedirect_ user fileId rfd >>= toView . CRSndFileRedirectXFTP user ft + (rfd : _) -> xftpSndFileRedirect_ user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft uris -> do ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor toView $ CRSndFileCompleteXFTP user ci ft' uris @@ -6763,8 +6764,8 @@ chatCommandP = "/list remote ctrls" $> ListRemoteCtrls, "/stop remote ctrl" $> StopRemoteCtrl, "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), - "/_upload " *> (APIXFTPDirectUpload <$> A.decimal <* A.space <*> cryptoFileP), - "/_download " *> (APIXFTPDirectDownload <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), + "/_upload " *> (APIUploadStandaloneFile <$> A.decimal <* A.space <*> cryptoFileP), + "/_download " *> (APIDownloadStandaloneFile <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, "/debug locks" $> DebugLocks, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 6fed6a801c..9ba48759a0 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -453,8 +453,8 @@ data ChatCommand | ListRemoteCtrls | StopRemoteCtrl -- Stop listening for announcements or terminate an active session | DeleteRemoteCtrl RemoteCtrlId -- Remove all local data associated with a remote controller session - | APIXFTPDirectUpload UserId CryptoFile - | APIXFTPDirectDownload UserId FileDescriptionURI CryptoFile + | APIUploadStandaloneFile UserId CryptoFile + | APIDownloadStandaloneFile UserId FileDescriptionURI CryptoFile | QuitChat | ShowVersion | DebugLocks @@ -595,6 +595,7 @@ data ChatResponse | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} + | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download | CRRcvFileStart {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} @@ -606,10 +607,10 @@ data ChatResponse | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} + | CRSndStandaloneFileCreated {user :: User, fileTransferMeta :: FileTransferMeta} -- returned by _upload | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileStartXFTPDirect {user :: User, fileTransferMeta :: FileTransferMeta} -- sent by _upload instead of ok, signalling fileId to expect | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} - | CRSndFileRedirectXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} + | CRSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 7ba76778a1..233f1558b9 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,6 +198,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile + CRRcvStandaloneFileCreated u ft -> ["TODO"] CRRcvFileStart u ci ft -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left ft) Right ci) CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) CRRcvFileCompleteXFTP u _ ft -> ttyUser u $ receivingFile_' hu testView "completed" (Left ft) @@ -205,10 +206,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRRcvFileError u ci e ft -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left ft) Right ci) <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft - CRSndFileStartXFTP {} -> [] - CRSndFileStartXFTPDirect {} -> [] - CRSndFileProgressXFTP {} -> [] - CRSndFileRedirectXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect + CRSndStandaloneFileCreated {} -> [] -- TODO + CRSndFileStartXFTP {} -> [] -- TODO + CRSndFileProgressXFTP {} -> [] -- TODO + CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ standaloneUploadComplete ft uris CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] From 011148d4cfea8b9f620744f761025576d927e29f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:45:30 +0400 Subject: [PATCH 19/33] num recipients --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index f536f7d667..854071e6ce 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 004597c764a8f8d5dc1e03fbad713b02a878c2ed + tag: 6f62d7ff05b9d25bca4f822c04ee83a102c34e42 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d4f602ddb9..07becc382e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."004597c764a8f8d5dc1e03fbad713b02a878c2ed" = "0c87qjlki5f3p598jazhc9267059fxw5xvqi3zx3g4sqy49aww6a"; + "https://github.com/simplex-chat/simplexmq.git"."6f62d7ff05b9d25bca4f822c04ee83a102c34e42" = "1d1wbfk1p7r5mxs0mrgzg1f6sy89r4vvqfn911vss3c02nhaj080"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 82478e745b..afd82ab13b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6988,6 +6988,6 @@ xftpSndFileRedirect_ user ftId vfd = do file = CryptoFile fileName Nothing fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) fileDescr - aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd + aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd (roundedFDCount 1) chSize <- asks $ fileChunkSize . config withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) (Just ftId) chSize From 6a6534fd954af73f79ee9e9234a5e7c9b2413d83 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 13:35:55 +0200 Subject: [PATCH 20/33] update messages --- src/Simplex/Chat.hs | 2 +- src/Simplex/Chat/Controller.hs | 4 ++-- src/Simplex/Chat/View.hs | 21 +++++++++++---------- tests/ChatTests/Files.hs | 15 +++++++++++---- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index afd82ab13b..403199d301 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2814,7 +2814,7 @@ startReceivingFile user fileId = do (ci, ft) <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 - ci <- lookupChatItemByFileId db vr user fileId + ci <- getChatItemByFileId db vr user fileId ft <- getRcvFileTransfer db user fileId pure (ci, ft) toView $ CRRcvFileStart user ci ft diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 9ba48759a0..883da1e1a4 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -596,7 +596,7 @@ data ChatResponse | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download - | CRRcvFileStart {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} + | CRRcvFileStart {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} -- sent by chats | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} @@ -608,7 +608,7 @@ data ChatResponse | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRSndStandaloneFileCreated {user :: User, fileTransferMeta :: FileTransferMeta} -- returned by _upload - | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} -- not used | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} | CRSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 233f1558b9..54f78f55c6 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,22 +198,22 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile - CRRcvStandaloneFileCreated u ft -> ["TODO"] - CRRcvFileStart u ci ft -> ttyUser u $ receivingFile_' hu testView "started" (maybe (Left ft) Right ci) + CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFile_' hu testView "started standalone" (Left ft) + CRRcvFileStart u ci _ -> ttyUser u $ receivingFile_' hu testView "started" (Right ci) CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) CRRcvFileCompleteXFTP u _ ft -> ttyUser u $ receivingFile_' hu testView "completed" (Left ft) CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft CRRcvFileError u ci e ft -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left ft) Right ci) <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft - CRSndStandaloneFileCreated {} -> [] -- TODO - CRSndFileStartXFTP {} -> [] -- TODO - CRSndFileProgressXFTP {} -> [] -- TODO + CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFile "started standalone" (Left ft) + CRSndFileStartXFTP {} -> [] + CRSndFileProgressXFTP {} -> [] CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ standaloneUploadComplete ft uris - CRSndFileCompleteXFTP u ci _ _ -> ttyUser u $ uploadingFile "completed" ci + CRSndFileCompleteXFTP u ci ft _ -> ttyUser u $ uploadingFile "completed" (maybe (Left ft) Right ci) CRSndFileCancelledXFTP {} -> [] - CRSndFileError u ci _ -> ttyUser u $ uploadingFile "error" ci + CRSndFileError u ci ft -> ttyUser u $ uploadingFile "error" (maybe (Left ft) Right ci) CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] CRContactConnecting u _ -> ttyUser u [] @@ -1562,12 +1562,13 @@ sendingFile_ :: StyledString -> SndFileTransfer -> [StyledString] sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} = [status <> " sending " <> sndFile ft <> " to " <> ttyContact c] -uploadingFile :: StyledString -> Maybe AChatItem -> [StyledString] +uploadingFile :: StyledString -> Either FileTransferMeta AChatItem -> [StyledString] uploadingFile status = \case - Just (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) -> + Right (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) -> [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] - Just (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) -> + Right (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) -> [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] + Left FileTransferMeta {fileId, fileName} -> [status <> " uploading " <> fileTransferStr fileId fileName] _ -> [status <> " uploading file"] standaloneUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index b79dd03a91..94b9c9f5c4 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1554,6 +1554,8 @@ testXFTPStandaloneSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst withXFTPServer $ do logNote "sending" src ##> "/_upload 1 ./tests/fixtures/test.jpg" + src <## "started standalone uploading file 1 (test.jpg)" + -- silent progress events threadDelay 250000 src <## "file 1 (test.jpg) upload complete. download with:" -- file description fits, enjoy the direct URIs @@ -1565,9 +1567,9 @@ testXFTPStandaloneSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst logNote "receiving" let dstFile = "./tests/tmp/test.jpg" dst ##> ("/_download 1 " <> uri3 <> " " <> dstFile) - dst <## "ok" + dst <## "started standalone receiving file 1 (test.jpg)" + -- silent progress events threadDelay 250000 - dst <## "started receiving file 1 (test.jpg)" dst <## "completed receiving file 1 (test.jpg)" srcBody <- B.readFile "./tests/fixtures/test.jpg" B.readFile dstFile `shouldReturn` srcBody @@ -1579,17 +1581,22 @@ testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst logNote "sending" src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events threadDelay 250000 src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" src <## "file 1 (testfile.in) upload complete. download with:" uri <- getTermLine src + _uri <- getTermLine src + _uri <- getTermLine src + _uri <- getTermLine src logNote "receiving" let dstFile = "./tests/tmp/testfile.out" dst ##> ("/_download 1 " <> uri <> " " <> dstFile) - dst <## "ok" + dst <## "started standalone receiving file 1 (testfile.out)" + -- silent progress events threadDelay 250000 - dst <## "started receiving file 1 (testfile.out)" dst <## "completed receiving file 1 (testfile.out)" srcBody <- B.readFile "./tests/tmp/testfile.in" B.readFile dstFile `shouldReturn` srcBody From b33fd233d2d138ea625f68517e2d5a1f79ae8c1f Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 14:01:30 +0200 Subject: [PATCH 21/33] split "file complete" events for chats and standalone --- src/Simplex/Chat.hs | 16 +++++++--------- src/Simplex/Chat/Controller.hs | 7 ++++--- src/Simplex/Chat/View.hs | 6 +++--- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 403199d301..dfd2c25c88 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2811,13 +2811,11 @@ receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do vr <- chatVersionRange - (ci, ft) <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do + ci <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 - ci <- getChatItemByFileId db vr user fileId - ft <- getRcvFileTransfer db user fileId - pure (ci, ft) - toView $ CRRcvFileStart user ci ft + getChatItemByFileId db vr user fileId + toView $ CRRcvFileStart user ci getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> String -> Bool -> m FilePath getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of @@ -3299,7 +3297,7 @@ processAgentMsgSndFile _corrId aFileId msg = (rfd : _) -> xftpSndFileRedirect_ user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft uris -> do ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor - toView $ CRSndFileCompleteXFTP user ci ft' uris + toView $ CRSndStandaloneFileComplete user ft' uris Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do @@ -3320,9 +3318,9 @@ processAgentMsgSndFile _corrId aFileId msg = forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) ci' <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId CIFSSndComplete - lookupChatItemByFileId db vr user fileId + getChatItemByFileId db vr user fileId withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileCompleteXFTP user ci' ft [] + toView $ CRSndFileCompleteXFTP user ci' ft where memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') @@ -4793,7 +4791,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case file of Just CIFile {fileProtocol = FPXFTP} -> do ft <- withStore $ \db -> getFileTransferMeta db user fileId - toView $ CRSndFileCompleteXFTP user (Just ci) ft [] + toView $ CRSndFileCompleteXFTP user ci ft _ -> toView $ CRSndFileComplete user ci sft allowSendInline :: Integer -> Maybe InlineFileMode -> m Bool diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 883da1e1a4..13bf65ff67 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -596,7 +596,7 @@ data ChatResponse | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download - | CRRcvFileStart {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} -- sent by chats + | CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} @@ -608,10 +608,11 @@ data ChatResponse | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRSndStandaloneFileCreated {user :: User, fileTransferMeta :: FileTransferMeta} -- returned by _upload - | CRSndFileStartXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} -- not used + | CRSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} -- not used | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} | CRSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} - | CRSndFileCompleteXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} + | CRSndFileCompleteXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndStandaloneFileComplete {user :: User, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 54f78f55c6..2dffb94e8c 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -199,7 +199,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFile_' hu testView "started standalone" (Left ft) - CRRcvFileStart u ci _ -> ttyUser u $ receivingFile_' hu testView "started" (Right ci) + CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" (Right ci) CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) CRRcvFileCompleteXFTP u _ ft -> ttyUser u $ receivingFile_' hu testView "completed" (Left ft) CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft @@ -210,8 +210,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRSndFileStartXFTP {} -> [] CRSndFileProgressXFTP {} -> [] CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect - CRSndFileCompleteXFTP u Nothing ft uris -> ttyUser u $ standaloneUploadComplete ft uris - CRSndFileCompleteXFTP u ci ft _ -> ttyUser u $ uploadingFile "completed" (maybe (Left ft) Right ci) + CRSndStandaloneFileComplete u ft uris -> ttyUser u $ standaloneUploadComplete ft uris + CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" (Right ci) CRSndFileCancelledXFTP {} -> [] CRSndFileError u ci ft -> ttyUser u $ uploadingFile "error" (maybe (Left ft) Right ci) CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> From 65ac62c33b0836903dcb77b11971e9444bfc08c9 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 14:28:36 +0200 Subject: [PATCH 22/33] restore xftpSndFileRedirect --- src/Simplex/Chat.hs | 47 ++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index dfd2c25c88..714e0a2fa8 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -677,7 +677,7 @@ processChatCommand' vr = \case (fileSize, fileMode) <- checkSndFile mc file 1 case fileMode of SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline - SendFileXFTP -> xftpSndFileTransfer_ user file fileSize 1 . Just $ CGContact ct + SendFileXFTP -> xftpSndFileTransfer user file fileSize 1 $ CGContact ct where smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled @@ -742,7 +742,7 @@ processChatCommand' vr = \case (fileSize, fileMode) <- checkSndFile mc file $ fromIntegral n case fileMode of SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline - SendFileXFTP -> xftpSndFileTransfer_ user file fileSize n . Just $ CGGroup g + SendFileXFTP -> xftpSndFileTransfer user file fileSize n $ CGGroup g where smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled @@ -770,6 +770,21 @@ processChatCommand' vr = \case CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where + xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) + xftpSndFileTransfer user file fileSize n contactOrGroup = do + (fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup + case contactOrGroup of + CGContact Contact {activeConn} -> forM_ activeConn $ \conn -> + withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft dummyFileDescr + CGGroup (Group _ ms) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) + where + -- we are not sending files to pending members, same as with inline files + saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = + when ((connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn)) $ + withStore' $ + \db -> createSndFTDescrXFTP db user (Just m) conn ft dummyFileDescr + saveMemberFD _ = pure () + pure (fInv, ciFile, ft) unzipMaybe3 :: Maybe (a, b, c) -> (Maybe a, Maybe b, Maybe c) unzipMaybe3 (Just (a, b, c)) = (Just a, Just b, Just c) unzipMaybe3 _ = (Nothing, Nothing, Nothing) @@ -3294,7 +3309,7 @@ processAgentMsgSndFile _corrId aFileId msg = case mapMaybe fileDescrURI rfds of [] -> case rfds of [] -> logError "File sent without receiver descriptions" -- should not happen - (rfd : _) -> xftpSndFileRedirect_ user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft + (rfd : _) -> xftpSndFileRedirect user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft uris -> do ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor toView $ CRSndStandaloneFileComplete user ft' uris @@ -6955,8 +6970,7 @@ mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 xftpSndFileTransfer_ :: ChatMonad m => User -> CryptoFile -> Integer -> Int -> Maybe ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup_ = do let fileName = takeFileName filePath - fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} - fInv = xftpFileInvitation fileName fileSize fileDescr + fInv = xftpFileInvitation fileName fileSize dummyFileDescr fsFilePath <- toFSFilePath filePath let srcFile = CryptoFile fsFilePath cfArgs aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) @@ -6965,27 +6979,16 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup_ file fInv (AgentSndFileId aFileId) Nothing chSize let fileSource = Just $ CryptoFile filePath cfArgs ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} - case contactOrGroup_ of - Nothing -> - pure () - Just (CGContact Contact {activeConn}) -> forM_ activeConn $ \conn -> - withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft fileDescr - Just (CGGroup (Group _ ms)) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) - where - -- we are not sending files to pending members, same as with inline files - saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = - when ((connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn)) $ - withStore' $ - \db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr - saveMemberFD _ = pure () pure (fInv, ciFile, ft) -xftpSndFileRedirect_ :: ChatMonad m => User -> FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta -xftpSndFileRedirect_ user ftId vfd = do +xftpSndFileRedirect :: ChatMonad m => User -> FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect user ftId vfd = do let fileName = "redirect.yaml" file = CryptoFile fileName Nothing - fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} - fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) fileDescr + fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) dummyFileDescr aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd (roundedFDCount 1) chSize <- asks $ fileChunkSize . config withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) (Just ftId) chSize + +dummyFileDescr :: FileDescr +dummyFileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} From 09974b27d06a579eab7309f1645a96d395e08012 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 14:39:14 +0200 Subject: [PATCH 23/33] remove unused store error --- src/Simplex/Chat/Store/Shared.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 45311bac71..e4d47b32cc 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -84,7 +84,6 @@ data StoreError | SEFileIdNotFoundBySharedMsgId {sharedMsgId :: SharedMsgId} | SESndFileNotFoundXFTP {agentSndFileId :: AgentSndFileId} | SERcvFileNotFoundXFTP {agentRcvFileId :: AgentRcvFileId} - | SEExtraFileDescrNotFound {fileId :: FileTransferId} | SEConnectionNotFound {agentConnId :: AgentConnId} | SEConnectionNotFoundById {connId :: Int64} | SEConnectionNotFoundByMemberId {groupMemberId :: GroupMemberId} From 056f685a0bc55bf65e6efcfd57bf124455cd8a71 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 17:29:12 +0200 Subject: [PATCH 24/33] add send/cancel test --- src/Simplex/Chat.hs | 7 ++++++- src/Simplex/Chat/Store/Files.hs | 29 +++++++++++++++++++------- tests/ChatTests/Files.hs | 36 ++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 714e0a2fa8..25ec627ac5 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6304,11 +6304,16 @@ agentXFTPDeleteRcvFile aFileId fileId = do agentXFTPDeleteSndFileRemote :: ChatMonad m => User -> XFTPSndFile -> FileTransferId -> m () agentXFTPDeleteSndFileRemote user XFTPSndFile {agentSndFileId = AgentSndFileId aFileId, privateSndFileDescr, agentSndFileDeleted} fileId = - unless agentSndFileDeleted $ + unless agentSndFileDeleted $ do forM_ privateSndFileDescr $ \sfdText -> do sd <- parseFileDescription sfdText withAgent $ \a -> xftpDeleteSndFileRemote a (aUserId user) aFileId sd withStore' $ \db -> setSndFTAgentDeleted db user fileId + -- the agent doesn't know about redirect, delete explicitly + redirect_ <- withStore' $ \db -> lookupFileTransferRedirectMeta db user fileId + forM_ redirect_ $ \FileTransferMeta {fileId = fileIdRedirect, xftpSndFile = sndFileRedirect_} -> + forM_ sndFileRedirect_ $ \sndFileRedirect -> + agentXFTPDeleteSndFileRemote user sndFileRedirect fileIdRedirect userProfileToSend :: User -> Maybe Profile -> Maybe Contact -> Bool -> Profile userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 9b36da2460..b91698d1da 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -72,6 +72,7 @@ module Simplex.Chat.Store.Files getFileTransfer, getFileTransferProgress, getFileTransferMeta, + lookupFileTransferRedirectMeta, getSndFileTransfer, getSndFileTransfers, getContactFileInfo, @@ -929,7 +930,7 @@ getFileTransferMeta db User {userId} = getFileTransferMeta_ db userId getFileTransferMeta_ :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO FileTransferMeta getFileTransferMeta_ db userId fileId = - ExceptT . firstRow fileTransferMeta (SEFileNotFound fileId) $ + ExceptT . firstRow (toFileTransferMeta userId) (SEFileNotFound fileId) $ DB.query db [sql| @@ -938,12 +939,26 @@ getFileTransferMeta_ db userId fileId = WHERE user_id = ? AND file_id = ? |] (userId, fileId) - where - fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta - fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = - let cryptoArgs = CFArgs <$> fileKey <*> fileNonce - xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ - in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + +lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO (Maybe FileTransferMeta) +lookupFileTransferRedirectMeta db User {userId} fileId = + maybeFirstRow (\(Only fileIdRedirect :. row) -> toFileTransferMeta fileIdRedirect row) $ + DB.query + db + [sql| + SELECT + file_id, + file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled, redirect_file_id + FROM files + WHERE user_id = ? AND redirect_file_id = ? + |] + (userId, fileId) + +toFileTransferMeta :: Int64 -> (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta +toFileTransferMeta fileId (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = + let cryptoArgs = CFArgs <$> fileKey <*> fileNonce + xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ + in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 94b9c9f5c4..0d4216544a 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1587,9 +1587,9 @@ testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" src <## "file 1 (testfile.in) upload complete. download with:" uri <- getTermLine src - _uri <- getTermLine src - _uri <- getTermLine src - _uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src logNote "receiving" let dstFile = "./tests/tmp/testfile.out" @@ -1601,6 +1601,36 @@ testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst srcBody <- B.readFile "./tests/tmp/testfile.in" B.readFile dstFile `shouldReturn` srcBody +testXFTPStandaloneCancelSnd :: HasCallStack => FilePath -> IO () +testXFTPStandaloneCancelSnd = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "cancelling" + src ##> "/fc 1" + src <## "cancelled sending file 1 (testfile.in)" + threadDelay 1000000 + + logNote "trying to receive cancelled" + dst ##> ("/_download 1 " <> uri <> " " <> "./tests/tmp/should.not.extist") + dst <## "started standalone receiving file 1 (should.not.extist)" + threadDelay 100000 + logWarn "no error?" + dst <## "error receiving file 1 (should.not.extist)" + dst <## "INTERNAL {internalErr = \"XFTP {xftpErr = AUTH}\"}" + startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" From 9257a758bf60849d0d43d21290253dd7b24f26da Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 18:54:33 +0200 Subject: [PATCH 25/33] untangle standalone views --- src/Simplex/Chat.hs | 4 +--- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/View.hs | 37 ++++++++++++++++++++-------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 25ec627ac5..9851fd3a80 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3428,9 +3428,7 @@ processAgentMsgRcvFile _corrId aFileId msg = updateCIFileStatus db user fileId CIFSRcvComplete lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - case ci_ of - Nothing -> toView $ CRRcvFileCompleteXFTP user fsTargetPath ft - Just ci -> toView $ CRRcvFileComplete user ci + toView $ maybe (CRRcvStandaloneFileComplete user fsTargetPath ft) (CRRcvFileComplete user) ci_ RFERR e | temporaryAgentError e -> throwChatError $ CEXFTPRcvFile fileId (AgentRcvFileId aFileId) e diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 13bf65ff67..df69fd127f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -599,7 +599,7 @@ data ChatResponse | CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} - | CRRcvFileCompleteXFTP {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} + | CRRcvStandaloneFileComplete {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2dffb94e8c..5a4327ab9d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -198,22 +198,24 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile - CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFile_' hu testView "started standalone" (Left ft) - CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" (Right ci) - CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" (Right ci) - CRRcvFileCompleteXFTP u _ ft -> ttyUser u $ receivingFile_' hu testView "completed" (Left ft) + CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFileStandalone "started" ft + CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci + CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci + CRRcvStandaloneFileComplete u _ ft -> ttyUser u $ receivingFileStandalone "completed" ft CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u ci e ft -> ttyUser u $ receivingFile_' hu testView "error" (maybe (Left ft) Right ci) <> [sShow e] + CRRcvFileError u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] + CRRcvFileError u Nothing e ft -> ttyUser u $ receivingFileStandalone "error" ft <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft - CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFile "started standalone" (Left ft) + CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFileStandalone "started" ft CRSndFileStartXFTP {} -> [] CRSndFileProgressXFTP {} -> [] CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect CRSndStandaloneFileComplete u ft uris -> ttyUser u $ standaloneUploadComplete ft uris - CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" (Right ci) + CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] - CRSndFileError u ci ft -> ttyUser u $ uploadingFile "error" (maybe (Left ft) Right ci) + CRSndFileError u Nothing ft -> ttyUser u $ uploadingFileStandalone "error" ft + CRSndFileError u (Just ci) _ -> ttyUser u $ uploadingFile "error" ci CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] CRContactConnecting u _ -> ttyUser u [] @@ -1562,15 +1564,17 @@ sendingFile_ :: StyledString -> SndFileTransfer -> [StyledString] sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} = [status <> " sending " <> sndFile ft <> " to " <> ttyContact c] -uploadingFile :: StyledString -> Either FileTransferMeta AChatItem -> [StyledString] +uploadingFile :: StyledString -> AChatItem -> [StyledString] uploadingFile status = \case - Right (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) -> + AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd} -> [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] - Right (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) -> + AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd} -> [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] - Left FileTransferMeta {fileId, fileName} -> [status <> " uploading " <> fileTransferStr fileId fileName] _ -> [status <> " uploading file"] +uploadingFileStandalone :: StyledString -> FileTransferMeta -> [StyledString] +uploadingFileStandalone status FileTransferMeta {fileId, fileName} = [status <> " standalone uploading " <> fileTransferStr fileId fileName] + standaloneUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] standaloneUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = [fileTransferStr fileId fileName <> " uploaded, preparing redirect file " <> sShow redirectId] @@ -1612,8 +1616,8 @@ savingFile' (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource ["saving file " <> sShow fileId <> fileFrom chat chatDir <> " to " <> plain filePath] savingFile' _ = ["saving file"] -- shouldn't happen -receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> Either RcvFileTransfer AChatItem -> [StyledString] -receivingFile_' hu testView status (Right (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir})) = +receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> AChatItem -> [StyledString] +receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir}) = [plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr where cfArgsStr (Just cfArgs) = [plain (cryptoFileArgsStr testView cfArgs) | status == "completed"] @@ -1626,9 +1630,12 @@ receivingFile_' hu testView status (Right (AChatItem _ _ chat ChatItem {file = J highlight ("/get remote file " <> show rhId <> " " <> LB.unpack (J.encode RemoteFile {userId, fileId, sent = False, fileSource = f})) ] _ -> [] -receivingFile_' _ _ status (Left RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}}) = [plain status <> " receiving " <> fileTransferStr fileId fileName] receivingFile_' _ _ status _ = [plain status <> " receiving file"] +receivingFileStandalone :: String -> RcvFileTransfer -> [StyledString] +receivingFileStandalone status RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} = + [plain status <> " standalone receiving " <> fileTransferStr fileId fileName] + viewLocalFile :: StyledString -> CIFile d -> CurrentTime -> TimeZone -> CIMeta c d -> [StyledString] viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of Just (CryptoFile fPath _) -> sentWithTime_ ts tz [to <> fileTransferStr fileId fPath] From 2ecc0d92e8765fbf73769e5c4160267012a75bd4 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 15 Feb 2024 19:53:09 +0200 Subject: [PATCH 26/33] fix confused id --- src/Simplex/Chat/Store/Files.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index b91698d1da..e21ba479a5 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -930,7 +930,7 @@ getFileTransferMeta db User {userId} = getFileTransferMeta_ db userId getFileTransferMeta_ :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO FileTransferMeta getFileTransferMeta_ db userId fileId = - ExceptT . firstRow (toFileTransferMeta userId) (SEFileNotFound fileId) $ + ExceptT . firstRow (toFileTransferMeta fileId) (SEFileNotFound fileId) $ DB.query db [sql| From 901569a5858ceaadf6035c199ff8d47335d1be14 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 16 Feb 2024 13:12:45 +0200 Subject: [PATCH 27/33] fix /fc and /fs --- src/Simplex/Chat.hs | 33 +++++++++++++++++++++------------ src/Simplex/Chat/Controller.hs | 2 +- tests/ChatTests/Files.hs | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9851fd3a80..bf1b628ef6 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1966,12 +1966,18 @@ processChatCommand' vr = \case fileCancelledOrCompleteSMP SndFileTransfer {fileStatus = s} = s == FSCancelled || (s == FSComplete && isNothing xftpSndFile) FTRcv ftr@RcvFileTransfer {cancelled, fileStatus, xftpRcvFile} - | cancelled -> throwChatError $ CEFileCancel fileId "file already cancelled" - | rcvFileComplete fileStatus -> throwChatError $ CEFileCancel fileId "file transfer is complete" - | otherwise -> case xftpRcvFile of + | cancelled -> do + logWarn "already cancelled FTRcv" + throwChatError $ CEFileCancel fileId "file already cancelled" + | rcvFileComplete fileStatus -> do + logWarn "already complete FTRcv" + throwChatError $ CEFileCancel fileId "file transfer is complete" + | otherwise -> do + logWarn "cancelling FTRcv" + case xftpRcvFile of Nothing -> do cancelRcvFileTransfer user ftr >>= mapM_ (deleteAgentConnectionAsync user) - ci <- withStore $ \db -> getChatItemByFileId db vr user fileId + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId pure $ CRRcvFileCancelled user ci ftr Just XFTPRcvFile {agentRcvFileId} -> do forM_ (liveRcvFileTransferPath ftr) $ \filePath -> do @@ -1984,18 +1990,21 @@ processChatCommand' vr = \case updateCIFileStatus db user fileId CIFSRcvInvitation updateRcvFileStatus db fileId FSNew updateRcvFileAgentId db fileId Nothing - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId pure $ CRRcvFileCancelled user ci ftr FileStatus fileId -> withUser $ \user -> do - ci@(AChatItem _ _ _ ChatItem {file}) <- withStore $ \db -> getChatItemByFileId db vr user fileId - case file of - Just CIFile {fileProtocol = FPLocal} -> - throwChatError $ CECommandError "not supported for local files" - Just CIFile {fileProtocol = FPXFTP} -> - pure $ CRFileTransferStatusXFTP user ci - _ -> do + withStore (\db -> lookupChatItemByFileId db vr user fileId) >>= \case + Nothing -> do fileStatus <- withStore $ \db -> getFileTransferProgress db user fileId pure $ CRFileTransferStatus user fileStatus + Just ci@(AChatItem _ _ _ ChatItem {file}) -> case file of + Just CIFile {fileProtocol = FPLocal} -> + throwChatError $ CECommandError "not supported for local files" + Just CIFile {fileProtocol = FPXFTP} -> + pure $ CRFileTransferStatusXFTP user ci + _ -> do + fileStatus <- withStore $ \db -> getFileTransferProgress db user fileId + pure $ CRFileTransferStatus user fileStatus ShowProfile -> withUser $ \user@User {profile} -> pure $ CRUserProfile user (fromLocalProfile profile) UpdateProfile displayName fullName -> withUser $ \user@User {profile} -> do let p = (fromLocalProfile profile :: Profile) {displayName = displayName, fullName = fullName} diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index df69fd127f..17eb83d757 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -600,7 +600,7 @@ data ChatResponse | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} | CRRcvStandaloneFileComplete {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} + | CRRcvFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 0d4216544a..b433d57134 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -81,6 +81,8 @@ chatFileTests = do describe "file transfer over XFTP without chat items" $ do it "send and receive small standalone file" testXFTPStandaloneSmall it "send and receive large standalone file" testXFTPStandaloneLarge + xit "removes sent file from server" testXFTPStandaloneCancelSnd -- no error shown in tests + xit "removes received temporary files" testXFTPStandaloneCancelRcv -- /fc gets sent after download in tests runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () runTestFileTransfer alice bob = do @@ -1631,6 +1633,33 @@ testXFTPStandaloneCancelSnd = testChat2 aliceProfile aliceDesktopProfile $ \src dst <## "error receiving file 1 (should.not.extist)" dst <## "INTERNAL {internalErr = \"XFTP {xftpErr = AUTH}\"}" +testXFTPStandaloneCancelRcv :: HasCallStack => FilePath -> IO () +testXFTPStandaloneCancelRcv = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "64mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/testfile.out" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) + dst <## "started standalone receiving file 1 (testfile.out)" + logNote "cancelling" + dst <## "/fc 1" + dst <## "cancelled receiving file 1 (testfile.out)" + threadDelay 250000 + doesFileExist dstFile `shouldReturn` False + startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" From 6e432a1cfa531b083289479b13a7321b6d86196d Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 16 Feb 2024 17:12:13 +0200 Subject: [PATCH 28/33] resolve comments --- src/Simplex/Chat.hs | 19 +++++++++++-------- src/Simplex/Chat/Store/Files.hs | 33 ++++++++++++--------------------- tests/ChatTests/Files.hs | 4 ++-- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index bf1b628ef6..9544b31022 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6310,17 +6310,20 @@ agentXFTPDeleteRcvFile aFileId fileId = do withStore' $ \db -> setRcvFTAgentDeleted db fileId agentXFTPDeleteSndFileRemote :: ChatMonad m => User -> XFTPSndFile -> FileTransferId -> m () -agentXFTPDeleteSndFileRemote user XFTPSndFile {agentSndFileId = AgentSndFileId aFileId, privateSndFileDescr, agentSndFileDeleted} fileId = - unless agentSndFileDeleted $ do - forM_ privateSndFileDescr $ \sfdText -> do - sd <- parseFileDescription sfdText - withAgent $ \a -> xftpDeleteSndFileRemote a (aUserId user) aFileId sd - withStore' $ \db -> setSndFTAgentDeleted db user fileId +agentXFTPDeleteSndFileRemote user sndFile fileId = do -- the agent doesn't know about redirect, delete explicitly + handleError (const $ pure ()) $ do redirect_ <- withStore' $ \db -> lookupFileTransferRedirectMeta db user fileId forM_ redirect_ $ \FileTransferMeta {fileId = fileIdRedirect, xftpSndFile = sndFileRedirect_} -> - forM_ sndFileRedirect_ $ \sndFileRedirect -> - agentXFTPDeleteSndFileRemote user sndFileRedirect fileIdRedirect + mapM_ (remove fileIdRedirect) sndFileRedirect_ + remove fileId sndFile + where + remove fId XFTPSndFile {agentSndFileId = AgentSndFileId aFileId, privateSndFileDescr, agentSndFileDeleted} = + unless agentSndFileDeleted $ do + forM_ privateSndFileDescr $ \sfdText -> do + sd <- parseFileDescription sfdText + withAgent $ \a -> xftpDeleteSndFileRemote a (aUserId user) aFileId sd + withStore' $ \db -> setSndFTAgentDeleted db user fId userProfileToSend :: User -> Maybe Profile -> Maybe Contact -> Bool -> Profile userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index e21ba479a5..40519fe75d 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -930,7 +930,7 @@ getFileTransferMeta db User {userId} = getFileTransferMeta_ db userId getFileTransferMeta_ :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO FileTransferMeta getFileTransferMeta_ db userId fileId = - ExceptT . firstRow (toFileTransferMeta fileId) (SEFileNotFound fileId) $ + ExceptT . firstRow fileTransferMeta (SEFileNotFound fileId) $ DB.query db [sql| @@ -939,26 +939,17 @@ getFileTransferMeta_ db userId fileId = WHERE user_id = ? AND file_id = ? |] (userId, fileId) - -lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO (Maybe FileTransferMeta) -lookupFileTransferRedirectMeta db User {userId} fileId = - maybeFirstRow (\(Only fileIdRedirect :. row) -> toFileTransferMeta fileIdRedirect row) $ - DB.query - db - [sql| - SELECT - file_id, - file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled, redirect_file_id - FROM files - WHERE user_id = ? AND redirect_file_id = ? - |] - (userId, fileId) - -toFileTransferMeta :: Int64 -> (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta -toFileTransferMeta fileId (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = - let cryptoArgs = CFArgs <$> fileKey <*> fileNonce - xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ - in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + where + fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta + fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = + let cryptoArgs = CFArgs <$> fileKey <*> fileNonce + xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ + in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + +lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO [FileTransferMeta] +lookupFileTransferRedirectMeta db User {userId} fileId = do + redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId) + rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index b433d57134..f1038575ab 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -1572,7 +1572,7 @@ testXFTPStandaloneSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst dst <## "started standalone receiving file 1 (test.jpg)" -- silent progress events threadDelay 250000 - dst <## "completed receiving file 1 (test.jpg)" + dst <## "completed standalone receiving file 1 (test.jpg)" srcBody <- B.readFile "./tests/fixtures/test.jpg" B.readFile dstFile `shouldReturn` srcBody @@ -1599,7 +1599,7 @@ testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst dst <## "started standalone receiving file 1 (testfile.out)" -- silent progress events threadDelay 250000 - dst <## "completed receiving file 1 (testfile.out)" + dst <## "completed standalone receiving file 1 (testfile.out)" srcBody <- B.readFile "./tests/tmp/testfile.in" B.readFile dstFile `shouldReturn` srcBody From 360c339bcaf360a918be906c5ce3475165553a0b Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 16 Feb 2024 17:44:37 +0200 Subject: [PATCH 29/33] misc fixes --- src/Simplex/Chat.hs | 12 +++--------- src/Simplex/Chat/Store/Files.hs | 2 +- src/Simplex/Chat/View.hs | 2 +- tests/ChatTests/Files.hs | 9 +++++---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9544b31022..afeb5c21b5 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1966,15 +1966,9 @@ processChatCommand' vr = \case fileCancelledOrCompleteSMP SndFileTransfer {fileStatus = s} = s == FSCancelled || (s == FSComplete && isNothing xftpSndFile) FTRcv ftr@RcvFileTransfer {cancelled, fileStatus, xftpRcvFile} - | cancelled -> do - logWarn "already cancelled FTRcv" - throwChatError $ CEFileCancel fileId "file already cancelled" - | rcvFileComplete fileStatus -> do - logWarn "already complete FTRcv" - throwChatError $ CEFileCancel fileId "file transfer is complete" - | otherwise -> do - logWarn "cancelling FTRcv" - case xftpRcvFile of + | cancelled -> throwChatError $ CEFileCancel fileId "file already cancelled" + | rcvFileComplete fileStatus -> throwChatError $ CEFileCancel fileId "file transfer is complete" + | otherwise -> case xftpRcvFile of Nothing -> do cancelRcvFileTransfer user ftr >>= mapM_ (deleteAgentConnectionAsync user) ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 40519fe75d..c44c652f9a 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -698,7 +698,7 @@ getRcvFileTransfer_ db userId fileId = do FSCancelled -> ft name . RFSCancelled <$> rfi_ where standaloneName_ = case (connId_, agentRcvFileId, filePath_) of - (Nothing, Just _, Just _) -> Just "direct" -- filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer + (Nothing, Just _, Just _) -> Just "" -- filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer _ -> Nothing ft senderDisplayName fileStatus = let fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5a4327ab9d..b6bb7807cb 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1653,7 +1653,7 @@ fileFrom _ _ = "" receivingFile_ :: StyledString -> RcvFileTransfer -> [StyledString] receivingFile_ status ft@RcvFileTransfer {senderDisplayName = c} = - [status <> " receiving " <> rcvFile ft <> " from " <> ttyContact c] + [status <> " receiving " <> rcvFile ft <> if c == "" then "" else " from " <> ttyContact c] rcvFile :: RcvFileTransfer -> StyledString rcvFile RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} = fileTransferStr fileId fileName diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index f1038575ab..7a3536b1ee 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -82,7 +82,7 @@ chatFileTests = do it "send and receive small standalone file" testXFTPStandaloneSmall it "send and receive large standalone file" testXFTPStandaloneLarge xit "removes sent file from server" testXFTPStandaloneCancelSnd -- no error shown in tests - xit "removes received temporary files" testXFTPStandaloneCancelRcv -- /fc gets sent after download in tests + it "removes received temporary files" testXFTPStandaloneCancelRcv runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () runTestFileTransfer alice bob = do @@ -1636,7 +1636,7 @@ testXFTPStandaloneCancelSnd = testChat2 aliceProfile aliceDesktopProfile $ \src testXFTPStandaloneCancelRcv :: HasCallStack => FilePath -> IO () testXFTPStandaloneCancelRcv = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do withXFTPServer $ do - xftpCLI ["rand", "./tests/tmp/testfile.in", "64mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] logNote "sending" src ##> "/_upload 1 ./tests/tmp/testfile.in" @@ -1654,10 +1654,11 @@ testXFTPStandaloneCancelRcv = testChat2 aliceProfile aliceDesktopProfile $ \src let dstFile = "./tests/tmp/testfile.out" dst ##> ("/_download 1 " <> uri <> " " <> dstFile) dst <## "started standalone receiving file 1 (testfile.out)" + threadDelay 25000 -- give workers some time to avoid internal errors from starting tasks logNote "cancelling" - dst <## "/fc 1" + dst ##> "/fc 1" dst <## "cancelled receiving file 1 (testfile.out)" - threadDelay 250000 + threadDelay 25000 doesFileExist dstFile `shouldReturn` False startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO () From edff527fdec5324c9f0917100b950a63c4943ba9 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 16 Feb 2024 18:02:17 +0200 Subject: [PATCH 30/33] bump simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 854071e6ce..cbcf80ca8b 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 6f62d7ff05b9d25bca4f822c04ee83a102c34e42 + tag: 194a7bb58e59c6389a30b13f8865914bbfe88eb9 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 07becc382e..4c5c431bf0 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."6f62d7ff05b9d25bca4f822c04ee83a102c34e42" = "1d1wbfk1p7r5mxs0mrgzg1f6sy89r4vvqfn911vss3c02nhaj080"; + "https://github.com/simplex-chat/simplexmq.git"."194a7bb58e59c6389a30b13f8865914bbfe88eb9" = "1mf118h619hjdnqnicrp99pkifvpg15a2wy5zs1n5j0r46f51sja"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 46bdcd10601231525c4498d7c2426e8def45a66f Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Fri, 16 Feb 2024 18:05:38 +0200 Subject: [PATCH 31/33] fix build --- tests/ChatClient.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index c15a39d627..91b3989b06 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -384,7 +384,7 @@ serverCfg = logStatsStartTime = 0, serverStatsLogFile = "tests/smp-server-stats.daily.log", serverStatsBackupFile = Nothing, - smpServerVRange = supportedSMPServerVRange, + smpServerVRange = supportedServerSMPRelayVRange, transportConfig = defaultTransportServerConfig, smpHandshakeTimeout = 1000000, controlPort = Nothing From 901e72c2552bf8e69d6be6f85cc0f9a9f8c86c25 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Sat, 17 Feb 2024 18:40:20 +0200 Subject: [PATCH 32/33] handle redirect errors independently --- src/Simplex/Chat.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index afeb5c21b5..09b0f8f11f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6306,10 +6306,9 @@ agentXFTPDeleteRcvFile aFileId fileId = do agentXFTPDeleteSndFileRemote :: ChatMonad m => User -> XFTPSndFile -> FileTransferId -> m () agentXFTPDeleteSndFileRemote user sndFile fileId = do -- the agent doesn't know about redirect, delete explicitly - handleError (const $ pure ()) $ do - redirect_ <- withStore' $ \db -> lookupFileTransferRedirectMeta db user fileId - forM_ redirect_ $ \FileTransferMeta {fileId = fileIdRedirect, xftpSndFile = sndFileRedirect_} -> - mapM_ (remove fileIdRedirect) sndFileRedirect_ + redirect_ <- withStore' $ \db -> lookupFileTransferRedirectMeta db user fileId + forM_ redirect_ $ \FileTransferMeta {fileId = fileIdRedirect, xftpSndFile = sndFileRedirect_} -> + mapM_ (handleError (const $ pure ()) . remove fileIdRedirect) sndFileRedirect_ remove fileId sndFile where remove fId XFTPSndFile {agentSndFileId = AgentSndFileId aFileId, privateSndFileDescr, agentSndFileDeleted} = From cb0198e42b4e2fb5a1f553baaf150492d830feb3 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Sat, 17 Feb 2024 20:24:07 +0200 Subject: [PATCH 33/33] fix missing file status in tests --- tests/ChatTests/Local.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index 1d0c540d76..4467ae9372 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -152,7 +152,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/clear *" alice ##> "/fs 1" - alice <## "chat db error: SEChatItemNotFoundByFileId {fileId = 1}" + alice <## "file 1 not found" alice ##> "/tail" doesFileExist stored `shouldReturn` False