core, ios: mark files to receive from NSE, receive marked files on chat start (#2218)

This commit is contained in:
spaced4ndy 2023-04-20 16:52:55 +04:00 committed by GitHub
parent 17bdd2a1d2
commit 4d700d113d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 152 additions and 22 deletions

View File

@ -1324,6 +1324,8 @@ func processReceivedMsg(_ res: ChatResponse) async {
if active(user) {
m.updateGroup(groupInfo)
}
case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE
chatItemSimpleUpdate(user, aChatItem)
case let .rcvFileStart(user, aChatItem):
chatItemSimpleUpdate(user, aChatItem)
case let .rcvFileComplete(user, aChatItem):

View File

@ -272,28 +272,25 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1))
}
if case .image = cItem.content.msgContent {
if let file = cItem.file,
file.fileProtocol == .smp,
file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV,
privacyAcceptImagesGroupDefault.get() {
cItem = apiReceiveFile(fileId: file.fileId)?.chatItem ?? cItem
}
if let file = cItem.file,
file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV,
privacyAcceptImagesGroupDefault.get() {
cItem = autoReceiveFile(file) ?? cItem
}
} else if case .video = cItem.content.msgContent {
if let file = cItem.file,
file.fileProtocol == .smp,
file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV,
privacyAcceptImagesGroupDefault.get() {
cItem = apiReceiveFile(fileId: file.fileId)?.chatItem ?? cItem
cItem = autoReceiveFile(file) ?? cItem
}
} else if case .voice = cItem.content.msgContent { // TODO check inlineFileMode != IFMSent
if let file = cItem.file,
file.fileProtocol == .smp,
file.fileSize <= MAX_IMAGE_SIZE,
file.fileSize > MAX_VOICE_MESSAGE_SIZE_INLINE_SEND,
privacyAcceptImagesGroupDefault.get() {
cItem = apiReceiveFile(fileId: file.fileId)?.chatItem ?? cItem
cItem = autoReceiveFile(file) ?? cItem
}
}
}
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(notification: createMessageReceivedNtf(user, cInfo, cItem)) : .empty
return cItem.showMutableNotification ? (aChatItem.chatId, ntf) : nil
case let .rcvFileSndCancelled(_, aChatItem, _):
@ -401,6 +398,22 @@ func apiReceiveFile(fileId: Int64, inline: Bool? = nil) -> AChatItem? {
return nil
}
func apiSetFileToReceive(fileId: Int64) {
let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId))
if case .cmdOk = r { return }
logger.error("setFileToReceive error: \(responseError(r))")
}
func autoReceiveFile(_ file: CIFile) -> ChatItem? {
switch file.fileProtocol {
case .smp:
return apiReceiveFile(fileId: file.fileId)?.chatItem
case .xftp:
apiSetFileToReceive(fileId: file.fileId)
return nil
}
}
func setNetworkConfig(_ cfg: NetCfg) throws {
let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg))
if case .cmdOk = r { return }

View File

@ -100,6 +100,7 @@ public enum ChatCommand {
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, inline: Bool?)
case setFileToReceive(fileId: Int64)
case cancelFile(fileId: Int64)
case showVersion
case string(String)
@ -206,6 +207,7 @@ public enum ChatCommand {
return "/freceive \(fileId) inline=\(onOff(inline))"
}
return "/freceive \(fileId)"
case let .setFileToReceive(fileId): return "/_set_file_to_receive \(fileId)"
case let .cancelFile(fileId): return "/fcancel \(fileId)"
case .showVersion: return "/version"
case let .string(str): return str
@ -302,6 +304,7 @@ public enum ChatCommand {
case .apiChatRead: return "apiChatRead"
case .apiChatUnread: return "apiChatUnread"
case .receiveFile: return "receiveFile"
case .setFileToReceive: return "setFileToReceive"
case .cancelFile: return "cancelFile"
case .showVersion: return "showVersion"
case .string: return "console command"

View File

@ -91,6 +91,7 @@ library
Simplex.Chat.Migrations.M20230328_files_protocol
Simplex.Chat.Migrations.M20230402_protocol_servers
Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions
Simplex.Chat.Migrations.M20230420_rcv_files_to_receive
Simplex.Chat.Mobile
Simplex.Chat.Mobile.WebRTC
Simplex.Chat.Options

View File

@ -225,7 +225,9 @@ startChatController subConns enableExpireCIs startXFTPWorkers = do
then Just <$> async (subscribeUsers users)
else pure Nothing
atomically . writeTVar s $ Just (a1, a2)
when startXFTPWorkers startXFTP
when startXFTPWorkers $ do
startXFTP
void $ forkIO $ startFilesToReceive users
startCleanupManager
when enableExpireCIs $ startExpireCIs users
pure a1
@ -257,6 +259,22 @@ subscribeUsers users = do
subscribe :: [User] -> m ()
subscribe = mapM_ $ runExceptT . subscribeUserConnections Agent.subscribeConnections
startFilesToReceive :: forall m. ChatMonad' m => [User] -> m ()
startFilesToReceive users = do
let (us, us') = partition activeUser users
startReceive us
startReceive us'
where
startReceive :: [User] -> m ()
startReceive = mapM_ $ runExceptT . startReceiveUserFiles
startReceiveUserFiles :: forall m. ChatMonad m => User -> m ()
startReceiveUserFiles user = do
filesToReceive <- withStore' (`getRcvFilesToReceive` user)
forM_ filesToReceive $ \ft ->
flip catchError (toView . CRChatError (Just user)) $
toView =<< receiveFile' user ft Nothing Nothing
restoreCalls :: ChatMonad' m => m ()
restoreCalls = do
savedCalls <- fromRight [] <$> runExceptT (withStore' $ \db -> getCalls db)
@ -1385,13 +1403,11 @@ processChatCommand = \case
ReceiveFile fileId rcvInline_ filePath_ -> withUser $ \_ ->
withChatLock "receiveFile" . procCmd $ do
(user, ft) <- withStore $ \db -> getRcvFileTransferById db fileId
(CRRcvFileAccepted user <$> acceptFileReceive user ft rcvInline_ filePath_) `catchError` processError user ft
where
processError user ft = \case
-- TODO AChatItem in Cancelled events
ChatErrorAgent (SMP SMP.AUTH) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft
ChatErrorAgent (CONN DUPLICATE) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft
e -> throwError e
receiveFile' user ft rcvInline_ filePath_
SetFileToReceive fileId -> withUser $ \_ -> do
withChatLock "setFileToReceive" . procCmd $ do
withStore' (`setRcvFileToReceive` fileId)
ok_
CancelFile fileId -> withUser $ \user@User {userId} ->
withChatLock "cancelFile" . procCmd $
withStore (\db -> getFileTransfer db user fileId) >>= \case
@ -1904,6 +1920,16 @@ toFSFilePath :: ChatMonad m => FilePath -> m FilePath
toFSFilePath f =
maybe f (</> f) <$> (readTVarIO =<< asks filesFolder)
receiveFile' :: ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m ChatResponse
receiveFile' user ft rcvInline_ filePath_ = do
(CRRcvFileAccepted user <$> acceptFileReceive user ft rcvInline_ filePath_) `catchError` processError
where
processError = \case
-- TODO AChatItem in Cancelled events
ChatErrorAgent (SMP SMP.AUTH) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft
ChatErrorAgent (CONN DUPLICATE) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft
e -> throwError e
acceptFileReceive :: forall m. ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m AChatItem
acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = FileInvitation {fileName = fName, fileConnReq, fileInline, fileSize}, fileStatus, grpMemberId} rcvInline_ filePath_ = do
unless (fileStatus == RFSNew) $ case fileStatus of
@ -4668,6 +4694,7 @@ chatCommandP =
("/image_forward " <|> "/imgf ") *> (ForwardImage <$> chatNameP' <* A.space <*> A.decimal),
("/fdescription " <|> "/fd") *> (SendFileDescription <$> chatNameP' <* A.space <*> filePath),
("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)),
"/_set_file_to_receive " *> (SetFileToReceive <$> A.decimal),
("/fcancel " <|> "/fc ") *> (CancelFile <$> A.decimal),
("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal),
"/simplex" $> ConnectSimplex,

View File

@ -346,6 +346,7 @@ data ChatCommand
| ForwardImage ChatName FileTransferId
| SendFileDescription ChatName FilePath
| ReceiveFile {fileId :: FileTransferId, fileInline :: Maybe Bool, filePath :: Maybe FilePath}
| SetFileToReceive FileTransferId
| CancelFile FileTransferId
| FileStatus FileTransferId
| ShowProfile -- UserId (not used in UI)

View File

@ -0,0 +1,18 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230420_rcv_files_to_receive where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20230420_rcv_files_to_receive :: Query
m20230420_rcv_files_to_receive =
[sql|
ALTER TABLE rcv_files ADD COLUMN to_receive INTEGER;
|]
down_m20230420_rcv_files_to_receive :: Query
down_m20230420_rcv_files_to_receive =
[sql|
ALTER TABLE rcv_files DROP COLUMN to_receive;
|]

View File

@ -229,7 +229,8 @@ CREATE TABLE rcv_files(
file_descr_id INTEGER NULL
REFERENCES xftp_file_descriptions ON DELETE SET NULL,
agent_rcv_file_id BLOB NULL,
agent_rcv_file_deleted INTEGER DEFAULT 0 CHECK(agent_rcv_file_deleted NOT NULL)
agent_rcv_file_deleted INTEGER DEFAULT 0 CHECK(agent_rcv_file_deleted NOT NULL),
to_receive INTEGER
);
CREATE TABLE snd_file_chunks(
file_id INTEGER NOT NULL,

View File

@ -190,6 +190,8 @@ module Simplex.Chat.Store
acceptRcvInlineFT,
startRcvInlineFT,
xftpAcceptRcvFT,
setRcvFileToReceive,
getRcvFilesToReceive,
setRcvFTAgentDeleted,
updateRcvFileStatus,
createRcvFileChunk,
@ -302,7 +304,7 @@ import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay)
import Data.Time.LocalTime (TimeZone, getCurrentTimeZone)
import Data.Type.Equality
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), SQLError, (:.) (..))
@ -370,6 +372,7 @@ import Simplex.Chat.Migrations.M20230321_agent_file_deleted
import Simplex.Chat.Migrations.M20230328_files_protocol
import Simplex.Chat.Migrations.M20230402_protocol_servers
import Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions
import Simplex.Chat.Migrations.M20230420_rcv_files_to_receive
import Simplex.Chat.Protocol
import Simplex.Chat.Types
import Simplex.Chat.Util (week)
@ -443,7 +446,8 @@ schemaMigrations =
("20230321_agent_file_deleted", m20230321_agent_file_deleted, Just down_m20230321_agent_file_deleted),
("20230328_files_protocol", m20230328_files_protocol, Just down_m20230328_files_protocol),
("20230402_protocol_servers", m20230402_protocol_servers, Just down_m20230402_protocol_servers),
("20230411_extra_xftp_file_descriptions", m20230411_extra_xftp_file_descriptions, Just down_m20230411_extra_xftp_file_descriptions)
("20230411_extra_xftp_file_descriptions", m20230411_extra_xftp_file_descriptions, Just down_m20230411_extra_xftp_file_descriptions),
("20230420_rcv_files_to_receive", m20230420_rcv_files_to_receive, Just down_m20230420_rcv_files_to_receive)
]
-- | The list of migrations in ascending order by date
@ -3216,6 +3220,31 @@ acceptRcvFT_ db User {userId} fileId filePath rcvFileInline currentTs = do
"UPDATE rcv_files SET rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?"
(rcvFileInline, FSAccepted, currentTs, fileId)
setRcvFileToReceive :: DB.Connection -> FileTransferId -> IO ()
setRcvFileToReceive db fileId = do
currentTs <- getCurrentTime
DB.execute
db
"UPDATE rcv_files SET to_receive = 1, updated_at = ? WHERE file_id = ?"
(currentTs, fileId)
getRcvFilesToReceive :: DB.Connection -> User -> IO [RcvFileTransfer]
getRcvFilesToReceive db user@User {userId} = do
cutoffTs <- addUTCTime (- (2 * nominalDay)) <$> getCurrentTime
fileIds :: [Int64] <-
map fromOnly
<$> DB.query
db
[sql|
SELECT r.file_id
FROM rcv_files r
JOIN files f ON f.file_id = r.file_id
WHERE f.user_id = ? AND r.file_status = ?
AND r.to_receive = 1 AND r.created_at > ?
|]
(userId, FSNew, cutoffTs)
rights <$> mapM (runExceptT . getRcvFileTransfer db user) fileIds
setRcvFTAgentDeleted :: DB.Connection -> FileTransferId -> IO ()
setRcvFTAgentDeleted db fileId = do
currentTs <- getCurrentTime

View File

@ -65,6 +65,7 @@ chatFileTests = do
it "with changed XFTP config: send and receive file" testXFTPWithChangedConfig
it "with relative paths: send and receive file" testXFTPWithRelativePaths
xit' "continue receiving file after restart" testXFTPContinueRcv
it "receive file marked to receive on chat start" testXFTPMarkToReceive
it "error receiving file" testXFTPRcvError
it "cancel receiving file, repeat receive" testXFTPCancelRcvRepeat
@ -1233,6 +1234,40 @@ testXFTPContinueRcv tmp = do
where
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
testXFTPMarkToReceive :: HasCallStack => FilePath -> IO ()
testXFTPMarkToReceive = do
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
withXFTPServer $ do
connectUsers alice bob
alice #> "/f @bob ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
-- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ?
alice <## "completed uploading file 1 (test.pdf) for bob"
bob #$> ("/_set_file_to_receive 1", id, "ok")
bob ##> "/_stop"
bob <## "chat stopped"
bob #$> ("/_files_folder ./tests/tmp/bob_files", id, "ok")
bob #$> ("/_temp_folder ./tests/tmp/bob_xftp", id, "ok")
bob ##> "/_start"
bob <## "chat started"
bob
<### [ "1 contacts connected (use /cs for the list)",
"started receiving file 1 (test.pdf) from alice",
"saving file 1 from alice to test.pdf"
]
bob <## "completed receiving file 1 (test.pdf) from alice"
src <- B.readFile "./tests/fixtures/test.pdf"
dest <- B.readFile "./tests/tmp/bob_files/test.pdf"
dest `shouldBe` src
where
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}}
testXFTPRcvError :: HasCallStack => FilePath -> IO ()
testXFTPRcvError tmp = do
withXFTPServer $ do