diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 6d426bd8fc..4067cf0295 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1009,10 +1009,10 @@ processChatCommand = \case processChatCommand . APISendMessage chatRef $ ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) ForwardFile chatName fileId -> forwardFile chatName fileId SendFile ForwardImage chatName fileId -> forwardFile chatName fileId SendImage - ReceiveFile fileId filePath_ -> withUser $ \user -> + ReceiveFile fileId rcvInline_ filePath_ -> withUser $ \user -> withChatLock . procCmd $ do ft <- withStore $ \db -> getRcvFileTransfer db user fileId - (CRRcvFileAccepted <$> acceptFileReceive user ft filePath_) `catchError` processError ft + (CRRcvFileAccepted <$> acceptFileReceive user ft rcvInline_ filePath_) `catchError` processError ft where processError ft = \case -- TODO AChatItem in Cancelled events @@ -1256,8 +1256,8 @@ toFSFilePath :: ChatMonad m => FilePath -> m FilePath toFSFilePath f = maybe f (<> "/" <> f) <$> (readTVarIO =<< asks filesFolder) -acceptFileReceive :: forall m. ChatMonad m => User -> RcvFileTransfer -> Maybe FilePath -> m AChatItem -acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName = fName, fileConnReq, fileInline, fileSize}, fileStatus, grpMemberId} filePath_ = do +acceptFileReceive :: forall m. ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m AChatItem +acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName = fName, fileConnReq, fileInline, fileSize}, fileStatus, grpMemberId} rcvInline_ filePath_ = do unless (fileStatus == RFSNew) $ case fileStatus of RFSCancelled _ -> throwChatError $ CEFileCancelled fName _ -> throwChatError $ CEFileAlreadyReceiving fName @@ -1290,9 +1290,9 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = F acceptFile = do sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId filePath <- getRcvFilePath fileId filePath_ fName - ChatConfig {fileChunkSize, inlineFiles} <- asks config + inline <- receiveInline if - | fileInline == Just IFMOffer && fileSize <= fileChunkSize * receiveChunks inlineFiles -> do + | inline -> do -- accepting inline ci <- withStore $ \db -> acceptRcvInlineFT db user fileId filePath pure (XFileAcptInv sharedMsgId Nothing fName, ci) @@ -1302,6 +1302,15 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = F (agentConnId, fileInvConnReq) <- withAgent $ \a -> createConnection a True SCMInvitation ci <- withStore $ \db -> acceptRcvFileTransfer db user fileId agentConnId ConnNew filePath pure (XFileAcptInv sharedMsgId (Just fileInvConnReq) fName, ci) + receiveInline :: m Bool + receiveInline = do + ChatConfig {fileChunkSize, inlineFiles = InlineFilesConfig {receiveChunks, offerChunks}} <- asks config + pure $ + rcvInline_ /= Just False + && fileInline == Just IFMOffer + && ( fileSize <= fileChunkSize * receiveChunks + || (rcvInline_ == Just True && fileSize <= fileChunkSize * offerChunks) + ) getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> String -> m FilePath getRcvFilePath fileId fPath_ fn = case fPath_ of @@ -3155,7 +3164,7 @@ chatCommandP = ("/image " <|> "/img ") *> (SendImage <$> chatNameP' <* A.space <*> filePath), ("/fforward " <|> "/ff ") *> (ForwardFile <$> chatNameP' <* A.space <*> A.decimal), ("/image_forward " <|> "/imgf ") *> (ForwardImage <$> chatNameP' <* A.space <*> A.decimal), - ("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> optional (A.space *> filePath)), + ("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)), ("/fcancel " <|> "/fc ") *> (CancelFile <$> A.decimal), ("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal), "/simplex" $> ConnectSimplex, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 29458e2449..c6c32cc73a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -84,7 +84,7 @@ data InlineFilesConfig = InlineFilesConfig defaultInlineFilesConfig :: InlineFilesConfig defaultInlineFilesConfig = InlineFilesConfig - { offerChunks = 15, -- max when chunks are offered - limited to 255 on the encoding level + { offerChunks = 15, -- max when chunks are offered / received with the option - limited to 255 on the encoding level sendChunks = 0, -- max per file when chunks will be sent inline without acceptance totalSendChunks = 30, -- max per conversation when chunks will be sent inline without acceptance receiveChunks = 5 -- max when chunks are accepted @@ -233,7 +233,7 @@ data ChatCommand | SendImage ChatName FilePath | ForwardFile ChatName FileTransferId | ForwardImage ChatName FileTransferId - | ReceiveFile FileTransferId (Maybe FilePath) + | ReceiveFile {fileId :: FileTransferId, fileInline :: Maybe Bool, filePath :: Maybe FilePath} | CancelFile FileTransferId | FileStatus FileTransferId | ShowProfile diff --git a/tests/ChatTests.hs b/tests/ChatTests.hs index a2f4f77dac..c52b140804 100644 --- a/tests/ChatTests.hs +++ b/tests/ChatTests.hs @@ -73,6 +73,7 @@ chatTests = do describe "sending and receiving files" $ do describe "send and receive file" $ fileTestMatrix2 runTestFileTransfer it "send and receive file inline (without accepting)" testInlineFileTransfer + it "receive file inline with inline=on option" testReceiveInline describe "send and receive a small file" $ fileTestMatrix2 runTestSmallFileTransfer describe "sender cancelled file transfer before transfer" $ fileTestMatrix2 runTestFileSndCancelBeforeTransfer it "sender cancelled file transfer during transfer" testFileSndCancelDuringTransfer @@ -1417,6 +1418,26 @@ testInlineFileTransfer = where cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}} +testReceiveInline :: IO () +testReceiveInline = + testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + alice #> "/f @bob ./tests/fixtures/test.jpg" + alice <## "use /fc 1 to cancel sending" + bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + bob ##> "/fr 1 inline=on ./tests/tmp" + bob <## "saving file 1 from alice to ./tests/tmp/test.jpg" + alice <## "started sending file 1 (test.jpg) to bob" + alice <## "completed sending file 1 (test.jpg) to bob" + bob <## "started receiving file 1 (test.jpg) from alice" + bob <## "completed receiving file 1 (test.jpg) from alice" + src <- B.readFile "./tests/fixtures/test.jpg" + dest <- B.readFile "./tests/tmp/test.jpg" + dest `shouldBe` src + where + cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 10, receiveChunks = 5}} + runTestSmallFileTransfer :: TestCC -> TestCC -> IO () runTestSmallFileTransfer alice bob = do connectUsers alice bob