mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-08-28 21:48:19 +00:00
xftp: recoverable send - spike (#707)
This commit is contained in:
@@ -53,6 +53,9 @@ module Simplex.Messaging.Agent.Client
|
||||
agentNtfCheckSubscription,
|
||||
agentNtfDeleteSubscription,
|
||||
agentXFTPDownloadChunk,
|
||||
agentXFTPCreateChunk,
|
||||
agentXFTPUploadChunk,
|
||||
agentXFTPAddRecipients,
|
||||
agentCbEncrypt,
|
||||
agentCbDecrypt,
|
||||
cryptoError,
|
||||
@@ -119,12 +122,12 @@ import Data.Word (Word16)
|
||||
import qualified Database.SQLite.Simple as DB
|
||||
import GHC.Generics (Generic)
|
||||
import Network.Socket (HostName)
|
||||
import Simplex.FileTransfer.Client (XFTPClient, XFTPClientConfig (..), XFTPClientError)
|
||||
import Simplex.FileTransfer.Client (XFTPChunkSpec, XFTPClient, XFTPClientConfig (..), XFTPClientError)
|
||||
import qualified Simplex.FileTransfer.Client as X
|
||||
import Simplex.FileTransfer.Description (ChunkReplicaId (..), kb)
|
||||
import Simplex.FileTransfer.Protocol (FileInfo (..), FileResponse, XFTPErrorType (DIGEST))
|
||||
import Simplex.FileTransfer.Transport (XFTPRcvChunkSpec (..))
|
||||
import Simplex.FileTransfer.Types (RcvFileChunkReplica (..))
|
||||
import Simplex.FileTransfer.Types (RcvFileChunkReplica (..), SndFileChunkReplica (..))
|
||||
import Simplex.FileTransfer.Util (uniqueCombine)
|
||||
import Simplex.Messaging.Agent.Env.SQLite
|
||||
import Simplex.Messaging.Agent.Lock
|
||||
@@ -163,8 +166,11 @@ import Simplex.Messaging.Protocol
|
||||
QueueIdsKeys (..),
|
||||
RcvMessage (..),
|
||||
RcvNtfPublicDhKey,
|
||||
RecipientId,
|
||||
SMPMsgMeta (..),
|
||||
SenderId,
|
||||
SndPublicVerifyKey,
|
||||
XFTPServer,
|
||||
XFTPServerWithAuth,
|
||||
)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
@@ -606,8 +612,8 @@ closeClient_ c cVar = do
|
||||
Just (Right client) -> closeProtocolServerClient client `catchAll_` pure ()
|
||||
_ -> pure ()
|
||||
|
||||
closeXFTPServerClient :: AgentMonad' m => AgentClient -> UserId -> RcvFileChunkReplica -> m ()
|
||||
closeXFTPServerClient c userId RcvFileChunkReplica {server, replicaId = ChunkReplicaId fId} =
|
||||
closeXFTPServerClient :: AgentMonad' m => AgentClient -> UserId -> XFTPServer -> ChunkReplicaId -> m ()
|
||||
closeXFTPServerClient c userId server (ChunkReplicaId fId) =
|
||||
mkTransportSession c userId server fId >>= liftIO . closeClient c xftpClients
|
||||
|
||||
cancelActions :: (Foldable f, Monoid (f (Async ()))) => TVar (f (Async ())) -> IO ()
|
||||
@@ -1064,6 +1070,18 @@ agentXFTPDownloadChunk :: AgentMonad m => AgentClient -> UserId -> RcvFileChunkR
|
||||
agentXFTPDownloadChunk c userId RcvFileChunkReplica {server, replicaId = ChunkReplicaId fId, replicaKey} chunkSpec =
|
||||
withXFTPClient c (userId, server, fId) "FGET" $ \xftp -> X.downloadXFTPChunk xftp replicaKey fId chunkSpec
|
||||
|
||||
agentXFTPCreateChunk :: AgentMonad m => AgentClient -> UserId -> XFTPServerWithAuth -> C.APrivateSignKey -> FileInfo -> NonEmpty C.APublicVerifyKey -> m (SenderId, NonEmpty RecipientId)
|
||||
agentXFTPCreateChunk c userId srv spKey file rcps =
|
||||
undefined
|
||||
|
||||
agentXFTPUploadChunk :: AgentMonad m => AgentClient -> UserId -> SndFileChunkReplica -> XFTPChunkSpec -> m ()
|
||||
agentXFTPUploadChunk c usedId SndFileChunkReplica {server, replicaId = ChunkReplicaId fId, replicaKey} chunkSpec =
|
||||
undefined
|
||||
|
||||
agentXFTPAddRecipients :: AgentMonad m => AgentClient -> UserId -> SndFileChunkReplica -> NonEmpty C.APublicVerifyKey -> m (NonEmpty RecipientId)
|
||||
agentXFTPAddRecipients c usedId SndFileChunkReplica {server, replicaId = ChunkReplicaId fId, replicaKey} rcps =
|
||||
undefined
|
||||
|
||||
agentCbEncrypt :: AgentMonad m => SndQueue -> Maybe C.PublicKeyX25519 -> ByteString -> m ByteString
|
||||
agentCbEncrypt SndQueue {e2eDhSecret, smpClientVersion} e2ePubKey msg = do
|
||||
cmNonce <- liftIO C.randomCbNonce
|
||||
|
||||
@@ -84,6 +84,7 @@ data AgentConfig = AgentConfig
|
||||
cleanupInterval :: Int64,
|
||||
rcvFilesTTL :: NominalDiffTime,
|
||||
xftpNotifyErrsOnRetry :: Bool,
|
||||
xftpMaxRecipientsPerRequest :: Int,
|
||||
deleteErrorCount :: Int,
|
||||
ntfCron :: Word16,
|
||||
ntfWorkerDelay :: Int,
|
||||
@@ -145,6 +146,7 @@ defaultAgentConfig =
|
||||
cleanupInterval = 30 * 60 * 1000000, -- 30 minutes
|
||||
rcvFilesTTL = 2 * nominalDay,
|
||||
xftpNotifyErrsOnRetry = True,
|
||||
xftpMaxRecipientsPerRequest = 200,
|
||||
deleteErrorCount = 10,
|
||||
ntfCron = 20, -- minutes
|
||||
ntfWorkerDelay = 100000, -- microseconds
|
||||
@@ -205,17 +207,15 @@ newNtfSubSupervisor qSize = do
|
||||
data XFTPAgent = XFTPAgent
|
||||
{ -- if set, XFTP file paths will be considered as relative to this directory
|
||||
xftpWorkDir :: TVar (Maybe FilePath),
|
||||
xftpWorkers :: TMap (Maybe XFTPServer) (TMVar (), Async ())
|
||||
xftpRcvWorkers :: TMap (Maybe XFTPServer) (TMVar (), Async ()),
|
||||
-- separate send workers for unhindered concurrency between download and upload,
|
||||
-- clients can also be separate by passing direction to withXFTPClient, and differentiating by it
|
||||
-- xftpSndWorkers :: TMap (Maybe XFTPServer) (TMVar (), Async ()),
|
||||
-- files currently in upload - to throttle upload of other files' chunks,
|
||||
-- this optimization can be dropped for the MVP
|
||||
-- xftpSndFiles :: TVar (Set DBSndFileId)
|
||||
xftpSndWorkers :: TMap (Maybe XFTPServer) (TMVar (), Async ())
|
||||
}
|
||||
|
||||
newXFTPAgent :: STM XFTPAgent
|
||||
newXFTPAgent = do
|
||||
xftpWorkDir <- newTVar Nothing
|
||||
xftpWorkers <- TM.empty
|
||||
pure XFTPAgent {xftpWorkDir, xftpWorkers}
|
||||
xftpRcvWorkers <- TM.empty
|
||||
xftpSndWorkers <- TM.empty
|
||||
pure XFTPAgent {xftpWorkDir, xftpRcvWorkers, xftpSndWorkers}
|
||||
|
||||
@@ -129,9 +129,11 @@ module Simplex.Messaging.Agent.Store.SQLite
|
||||
getActiveNtfToken,
|
||||
getNtfRcvQueue,
|
||||
setConnectionNtfs,
|
||||
-- File transfer
|
||||
|
||||
-- * File transfer
|
||||
|
||||
-- Rcv files
|
||||
createRcvFile,
|
||||
getRcvFile,
|
||||
getRcvFileByEntityId,
|
||||
updateRcvChunkReplicaDelay,
|
||||
updateRcvFileChunkReceived,
|
||||
@@ -147,6 +149,19 @@ module Simplex.Messaging.Agent.Store.SQLite
|
||||
getCleanupRcvFilesTmpPaths,
|
||||
getCleanupRcvFilesDeleted,
|
||||
getRcvFilesExpired,
|
||||
-- Snd files
|
||||
createSndFile,
|
||||
getSndFile,
|
||||
getNextSndFileToPrepare,
|
||||
updateSndFileError,
|
||||
updateSndFileStatus,
|
||||
updateSndFileEncrypted,
|
||||
updateSndFileComplete,
|
||||
createSndFileReplica,
|
||||
getNextSndChunkToUpload,
|
||||
updateSndChunkReplicaDelay,
|
||||
addSndChunkReplicaRecipients,
|
||||
updateSndChunkReplicaStatus,
|
||||
|
||||
-- * utilities
|
||||
withConnection,
|
||||
@@ -191,6 +206,7 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
||||
import qualified Database.SQLite3 as SQLite3
|
||||
import GHC.Generics (Generic)
|
||||
import Network.Socket (ServiceName)
|
||||
import Simplex.FileTransfer.Client (XFTPChunkSpec (..))
|
||||
import Simplex.FileTransfer.Description
|
||||
import Simplex.FileTransfer.Protocol (FileParty (..))
|
||||
import Simplex.FileTransfer.Types
|
||||
@@ -1868,9 +1884,9 @@ getRcvFileIdByEntityId_ db userId rcvFileEntityId =
|
||||
|
||||
getRcvFile :: DB.Connection -> DBRcvFileId -> IO (Either StoreError RcvFile)
|
||||
getRcvFile db rcvFileId = runExceptT $ do
|
||||
fd@RcvFile {rcvFileEntityId, userId, tmpPath} <- ExceptT getFile
|
||||
f@RcvFile {rcvFileEntityId, userId, tmpPath} <- ExceptT getFile
|
||||
chunks <- maybe (pure []) (liftIO . getChunks rcvFileEntityId userId) tmpPath
|
||||
pure (fd {chunks} :: RcvFile)
|
||||
pure (f {chunks} :: RcvFile)
|
||||
where
|
||||
getFile :: IO (Either StoreError RcvFile)
|
||||
getFile = do
|
||||
@@ -2075,3 +2091,218 @@ getRcvFilesExpired db ttl = do
|
||||
WHERE created_at < ?
|
||||
|]
|
||||
(Only cutoffTs)
|
||||
|
||||
createSndFile :: DB.Connection -> TVar ChaChaDRG -> UserId -> Int -> FilePath -> FilePath -> C.SbKey -> C.CbNonce -> IO (Either StoreError SndFileId)
|
||||
createSndFile db gVar userId numRecipients path prefixPath key nonce =
|
||||
createWithRandomId gVar $ \sndFileEntityId ->
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO snd_files (snd_file_entity_id, user_id, num_recipients, key, nonce, path, prefix_path, status) VALUES (?,?,?,?,?,?,?,?)"
|
||||
(sndFileEntityId, userId, numRecipients, key, nonce, path, prefixPath, SFSNew)
|
||||
|
||||
getSndFile :: DB.Connection -> DBSndFileId -> IO (Either StoreError SndFile)
|
||||
getSndFile db sndFileId = runExceptT $ do
|
||||
f@SndFile {sndFileEntityId, userId, numRecipients, prefixPath} <- ExceptT getFile
|
||||
chunks <- maybe (pure []) (liftIO . getChunks sndFileEntityId userId numRecipients) prefixPath
|
||||
pure (f {chunks} :: SndFile)
|
||||
where
|
||||
getFile :: IO (Either StoreError SndFile)
|
||||
getFile = do
|
||||
firstRow toFile SEFileNotFound $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT snd_file_entity_id, user_id, num_recipients, digest, key, nonce, path, prefix_path, status, deleted
|
||||
FROM snd_files
|
||||
WHERE snd_file_id = ?
|
||||
|]
|
||||
(Only sndFileId)
|
||||
where
|
||||
toFile :: (SndFileId, UserId, Int, Maybe FileDigest, C.SbKey, C.CbNonce, FilePath, Maybe FilePath, SndFileStatus, Bool) -> SndFile
|
||||
toFile (sndFileEntityId, userId, numRecipients, digest, key, nonce, filePath, prefixPath, status, deleted) =
|
||||
SndFile {sndFileId, sndFileEntityId, userId, numRecipients, digest, key, nonce, filePath, prefixPath, status, deleted, chunks = []}
|
||||
getChunks :: SndFileId -> UserId -> Int -> FilePath -> IO [SndFileChunk]
|
||||
getChunks sndFileEntityId userId numRecipients filePrefixPath = do
|
||||
chunks <-
|
||||
map toChunk
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT snd_file_chunk_id, chunk_no, chunk_offset, chunk_size, digest
|
||||
FROM snd_file_chunks
|
||||
WHERE snd_file_id = ?
|
||||
|]
|
||||
(Only sndFileId)
|
||||
forM chunks $ \chunk@SndFileChunk {sndChunkId} -> do
|
||||
replicas' <- getChunkReplicas sndChunkId
|
||||
pure (chunk {replicas = replicas'} :: SndFileChunk)
|
||||
where
|
||||
toChunk :: (Int64, Int, Int64, Word32, Maybe FileDigest) -> SndFileChunk
|
||||
toChunk (sndChunkId, chunkNo, chunkOffset, chunkSize, digest) =
|
||||
let chunkSpec = XFTPChunkSpec {filePath = sndFileEncPath filePrefixPath, chunkOffset, chunkSize}
|
||||
in SndFileChunk {sndFileId, sndFileEntityId, userId, numRecipients, sndChunkId, chunkNo, chunkSpec, filePrefixPath, digest, replicas = []}
|
||||
getChunkReplicas :: Int64 -> IO [SndFileChunkReplica]
|
||||
getChunkReplicas chunkId = do
|
||||
replicas <-
|
||||
map toReplica
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT
|
||||
r.snd_file_chunk_replica_id, r.replica_id, r.replica_key, r.replica_status, r.delay, r.retries,
|
||||
s.xftp_host, s.xftp_port, s.xftp_key_hash
|
||||
FROM snd_file_chunk_replicas r
|
||||
JOIN xftp_servers s ON s.xftp_server_id = r.xftp_server_id
|
||||
WHERE r.snd_file_chunk_id = ?
|
||||
|]
|
||||
(Only chunkId)
|
||||
forM replicas $ \replica@SndFileChunkReplica {sndChunkReplicaId} -> do
|
||||
rcvIdsKeys <- getChunkReplicaRecipients_ db sndChunkReplicaId
|
||||
pure replica {rcvIdsKeys}
|
||||
where
|
||||
toReplica :: (Int64, ChunkReplicaId, C.APrivateSignKey, SndFileReplicaStatus, Maybe Int64, Int, NonEmpty TransportHost, ServiceName, C.KeyHash) -> SndFileChunkReplica
|
||||
toReplica (sndChunkReplicaId, replicaId, replicaKey, replicaStatus, delay, retries, host, port, keyHash) =
|
||||
let server = XFTPServer host port keyHash
|
||||
in SndFileChunkReplica {sndChunkReplicaId, server, replicaId, replicaKey, replicaStatus, delay, retries, rcvIdsKeys = []}
|
||||
|
||||
getChunkReplicaRecipients_ :: DB.Connection -> Int64 -> IO [(ChunkReplicaId, C.APrivateSignKey)]
|
||||
getChunkReplicaRecipients_ db replicaId =
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT rcv_replica_id, rcv_replica_key
|
||||
FROM snd_file_chunk_replica_recipients
|
||||
WHERE snd_file_chunk_replica_id = ?
|
||||
|]
|
||||
(Only replicaId)
|
||||
|
||||
getNextSndFileToPrepare :: DB.Connection -> IO (Maybe SndFile)
|
||||
getNextSndFileToPrepare db = do
|
||||
fileId_ :: Maybe DBSndFileId <-
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT snd_file_id
|
||||
FROM snd_files
|
||||
WHERE status IN (?,?,?) AND deleted = 0
|
||||
ORDER BY created_at ASC LIMIT 1
|
||||
|]
|
||||
(SFSNew, SFSEncrypting, SFSEncrypted)
|
||||
case fileId_ of
|
||||
Nothing -> pure Nothing
|
||||
Just fileId -> eitherToMaybe <$> getSndFile db fileId
|
||||
|
||||
updateSndFileError :: DB.Connection -> DBSndFileId -> String -> IO ()
|
||||
updateSndFileError db sndFileId errStr = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_files SET prefix_path = NULL, error = ?, status = ?, updated_at = ? WHERE snd_file_id = ?" (errStr, SFSError, updatedAt, sndFileId)
|
||||
|
||||
updateSndFileStatus :: DB.Connection -> DBSndFileId -> SndFileStatus -> IO ()
|
||||
updateSndFileStatus db sndFileId status = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_files SET status = ?, updated_at = ? WHERE snd_file_id = ?" (status, updatedAt, sndFileId)
|
||||
|
||||
updateSndFileEncrypted :: DB.Connection -> DBSndFileId -> FileDigest -> [XFTPChunkSpec] -> IO ()
|
||||
updateSndFileEncrypted db sndFileId digest chunkSpecs = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_files SET status = ?, digest = ?, updated_at = ? WHERE snd_file_id = ?" (SFSEncrypted, digest, updatedAt, sndFileId)
|
||||
forM_ (zip [1 ..] chunkSpecs) $ \(chunkNo :: Int, XFTPChunkSpec {chunkOffset, chunkSize}) ->
|
||||
DB.execute db "INSERT INTO snd_file_chunks (snd_file_id, chunk_no, chunk_offset, chunk_size) VALUES (?,?,?,?)" (sndFileId, chunkNo, chunkOffset, chunkSize)
|
||||
|
||||
updateSndFileComplete :: DB.Connection -> DBSndFileId -> IO ()
|
||||
updateSndFileComplete db sndFileId = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_files SET prefix_path = NULL, status = ?, updated_at = ? WHERE snd_file_id = ?" (SFSComplete, updatedAt, sndFileId)
|
||||
|
||||
createSndFileReplica :: DB.Connection -> Int64 -> FileDigest -> XFTPServer -> ChunkReplicaId -> C.APrivateSignKey -> [(ChunkReplicaId, C.APrivateSignKey)] -> IO ()
|
||||
createSndFileReplica db sndChunkId digest xftpServer sndId spKey rcvIdsKeys = do
|
||||
srvId <- createXFTPServer_ db xftpServer
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_file_chunks SET digest = ?, updated_at = ? WHERE snd_file_chunk_id = ?" (digest, updatedAt, sndChunkId)
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO snd_file_chunk_replicas
|
||||
(snd_file_chunk_id, replica_number, xftp_server_id, replica_id, replica_key, replica_status)
|
||||
VALUES (?,?,?,?,?,?)
|
||||
|]
|
||||
(sndChunkId, 1 :: Int, srvId, sndId, spKey, SFRSCreated)
|
||||
rId <- insertedRowId db
|
||||
forM_ rcvIdsKeys $ \(rcvId, rcvKey) -> do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO snd_file_chunk_replica_recipients
|
||||
(snd_file_chunk_replica_id, rcv_replica_id, rcv_replica_key)
|
||||
VALUES (?,?,?)
|
||||
|]
|
||||
(rId, rcvId, rcvKey)
|
||||
|
||||
getNextSndChunkToUpload :: DB.Connection -> XFTPServer -> IO (Maybe SndFileChunk)
|
||||
getNextSndChunkToUpload db server@ProtocolServer {host, port, keyHash} = do
|
||||
chunk_ <-
|
||||
maybeFirstRow toChunk $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT
|
||||
f.snd_file_id, f.snd_file_entity_id, f.user_id, f.num_recipients, f.prefix_path,
|
||||
c.snd_file_chunk_id, c.chunk_no, c.chunk_offset, c.chunk_size, c.digest,
|
||||
r.snd_file_chunk_replica_id, r.replica_id, r.replica_key, r.replica_status, r.delay, r.retries
|
||||
FROM snd_file_chunk_replicas r
|
||||
JOIN xftp_servers s ON s.xftp_server_id = r.xftp_server_id
|
||||
JOIN snd_file_chunks c ON c.snd_file_chunk_id = r.snd_file_chunk_id
|
||||
JOIN snd_files f ON f.snd_file_id = c.snd_file_id
|
||||
WHERE s.xftp_host = ? AND s.xftp_port = ? AND s.xftp_key_hash = ?
|
||||
AND r.status = ? AND r.replica_number = 1
|
||||
AND (f.status = ? OR f.status = ?) AND f.deleted = 0
|
||||
ORDER BY r.created_at ASC
|
||||
LIMIT 1
|
||||
|]
|
||||
(host, port, keyHash, SFRSCreated, SFSEncrypted, SFSUploading)
|
||||
forM chunk_ $ \chunk@SndFileChunk {replicas} -> do
|
||||
replicas' <- forM replicas $ \replica@SndFileChunkReplica {sndChunkReplicaId} -> do
|
||||
rcvIdsKeys <- getChunkReplicaRecipients_ db sndChunkReplicaId
|
||||
pure replica {rcvIdsKeys}
|
||||
pure (chunk {replicas = replicas'} :: SndFileChunk)
|
||||
where
|
||||
toChunk :: ((DBSndFileId, SndFileId, UserId, Int, FilePath) :. (Int64, Int, Int64, Word32, Maybe FileDigest) :. (Int64, ChunkReplicaId, C.APrivateSignKey, SndFileReplicaStatus, Maybe Int64, Int)) -> SndFileChunk
|
||||
toChunk ((sndFileId, sndFileEntityId, userId, numRecipients, filePrefixPath) :. (sndChunkId, chunkNo, chunkOffset, chunkSize, digest) :. (sndChunkReplicaId, replicaId, replicaKey, replicaStatus, delay, retries)) =
|
||||
let chunkSpec = XFTPChunkSpec {filePath = sndFileEncPath filePrefixPath, chunkOffset, chunkSize}
|
||||
in SndFileChunk
|
||||
{ sndFileId,
|
||||
sndFileEntityId,
|
||||
userId,
|
||||
numRecipients,
|
||||
sndChunkId,
|
||||
chunkNo,
|
||||
chunkSpec,
|
||||
digest,
|
||||
filePrefixPath,
|
||||
replicas = [SndFileChunkReplica {sndChunkReplicaId, server, replicaId, replicaKey, replicaStatus, delay, retries, rcvIdsKeys = []}]
|
||||
}
|
||||
|
||||
updateSndChunkReplicaDelay :: DB.Connection -> Int64 -> Int64 -> IO ()
|
||||
updateSndChunkReplicaDelay db replicaId delay = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_file_chunk_replicas SET delay = ?, retries = retries + 1, updated_at = ? WHERE snd_file_chunk_replica_id = ?" (delay, updatedAt, replicaId)
|
||||
|
||||
addSndChunkReplicaRecipients :: DB.Connection -> SndFileChunkReplica -> [(ChunkReplicaId, C.APrivateSignKey)] -> IO SndFileChunkReplica
|
||||
addSndChunkReplicaRecipients db r@SndFileChunkReplica {sndChunkReplicaId} rcvIdsKeys = do
|
||||
forM_ rcvIdsKeys $ \(rcvId, rcvKey) -> do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO snd_file_chunk_replica_recipients
|
||||
(snd_file_chunk_replica_id, rcv_replica_id, rcv_replica_key)
|
||||
VALUES (?,?,?)
|
||||
|]
|
||||
(sndChunkReplicaId, rcvId, rcvKey)
|
||||
rcvIdsKeys' <- getChunkReplicaRecipients_ db sndChunkReplicaId
|
||||
pure r {rcvIdsKeys = rcvIdsKeys'}
|
||||
|
||||
updateSndChunkReplicaStatus :: DB.Connection -> Int64 -> SndFileReplicaStatus -> IO ()
|
||||
updateSndChunkReplicaStatus db replicaId status = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE snd_file_chunk_replicas SET status = ?, updated_at = ? WHERE snd_file_chunk_replica_id = ?" (status, updatedAt, replicaId)
|
||||
|
||||
@@ -57,6 +57,7 @@ import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230120_delete_errors
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230217_server_key_hash
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230223_files
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230320_retry_state
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230401_snd_files
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||
@@ -80,7 +81,8 @@ schemaMigrations =
|
||||
("m20230120_delete_errors", m20230120_delete_errors, Nothing),
|
||||
("m20230217_server_key_hash", m20230217_server_key_hash, Nothing),
|
||||
("m20230223_files", m20230223_files, Just down_m20230223_files),
|
||||
("m20230320_retry_state", m20230320_retry_state, Just down_m20230320_retry_state)
|
||||
("m20230320_retry_state", m20230320_retry_state, Just down_m20230320_retry_state),
|
||||
("m20230401_snd_files", m20230401_snd_files, Just down_m20230401_snd_files)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -5,21 +5,22 @@ module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230401_snd_files where
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- this migration is a draft - it is not included in the list of migrations
|
||||
m20230401_snd_files :: Query
|
||||
m20230401_snd_files =
|
||||
[sql|
|
||||
CREATE TABLE snd_files (
|
||||
snd_file_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
snd_file_id INTEGER PRIMARY KEY,
|
||||
snd_file_entity_id BLOB NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
size INTEGER NOT NULL,
|
||||
digest BLOB NOT NULL,
|
||||
key BLOB NOT NULL,
|
||||
nonce BLOB NOT NULL,
|
||||
chunk_size INTEGER NOT NULL,
|
||||
num_recipients INTEGER NOT NULL,
|
||||
digest BLOB,
|
||||
key BLOB NOT NUll,
|
||||
nonce BLOB NOT NUll,
|
||||
path TEXT NOT NULL,
|
||||
enc_path TEXT,
|
||||
prefix_path TEXT,
|
||||
status TEXT NOT NULL,
|
||||
deleted INTEGER NOT NULL DEFAULT 0,
|
||||
error TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
@@ -32,18 +33,13 @@ CREATE TABLE snd_file_chunks (
|
||||
chunk_no INTEGER NOT NULL,
|
||||
chunk_offset INTEGER NOT NULL,
|
||||
chunk_size INTEGER NOT NULL,
|
||||
digest BLOB NOT NULL,
|
||||
delay INTEGER,
|
||||
digest BLOB,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_snd_file_chunks_snd_file_id ON snd_file_chunks(snd_file_id);
|
||||
|
||||
-- ? add fk to snd_file_descriptions?
|
||||
-- ? probably it's not necessary since these entities are
|
||||
-- ? required at different stages of sending files -
|
||||
-- ? replicas on upload, description on notifying client
|
||||
CREATE TABLE snd_file_chunk_replicas (
|
||||
snd_file_chunk_replica_id INTEGER PRIMARY KEY,
|
||||
snd_file_chunk_id INTEGER NOT NULL REFERENCES snd_file_chunks ON DELETE CASCADE,
|
||||
@@ -51,8 +47,8 @@ CREATE TABLE snd_file_chunk_replicas (
|
||||
xftp_server_id INTEGER NOT NULL REFERENCES xftp_servers ON DELETE CASCADE,
|
||||
replica_id BLOB NOT NULL,
|
||||
replica_key BLOB NOT NULL,
|
||||
-- created INTEGER NOT NULL DEFAULT 0, -- as in XFTP create - registered on server
|
||||
uploaded INTEGER NOT NULL DEFAULT 0,
|
||||
replica_status TEXT NOT NULL,
|
||||
delay INTEGER,
|
||||
retries INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
@@ -71,15 +67,21 @@ CREATE TABLE snd_file_chunk_replica_recipients (
|
||||
);
|
||||
|
||||
CREATE INDEX idx_snd_file_chunk_replica_recipients_snd_file_chunk_replica_id ON snd_file_chunk_replica_recipients(snd_file_chunk_replica_id);
|
||||
|
||||
CREATE TABLE snd_file_descriptions (
|
||||
snd_file_description_id INTEGER PRIMARY KEY,
|
||||
snd_file_id INTEGER NOT NULL REFERENCES snd_files ON DELETE CASCADE,
|
||||
sender INTEGER NOT NULL, -- 1 for sender file description
|
||||
descr_text TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_snd_file_descriptions_snd_file_id ON snd_file_descriptions(snd_file_id);
|
||||
|]
|
||||
|
||||
down_m20230401_snd_files :: Query
|
||||
down_m20230401_snd_files =
|
||||
[sql|
|
||||
DROP INDEX idx_snd_file_chunk_replica_recipients_snd_file_chunk_replica_id;
|
||||
DROP TABLE snd_file_chunk_replica_recipients;
|
||||
|
||||
DROP INDEX idx_snd_file_chunk_replicas_snd_file_chunk_id;
|
||||
DROP INDEX idx_snd_file_chunk_replicas_xftp_server_id;
|
||||
DROP TABLE snd_file_chunk_replicas;
|
||||
|
||||
DROP INDEX idx_snd_file_chunks_snd_file_id;
|
||||
DROP TABLE snd_file_chunks;
|
||||
|
||||
DROP INDEX idx_snd_files_user_id;
|
||||
DROP TABLE snd_files;
|
||||
|]
|
||||
|
||||
@@ -345,3 +345,61 @@ CREATE INDEX idx_rcv_file_chunk_replicas_rcv_file_chunk_id ON rcv_file_chunk_rep
|
||||
CREATE INDEX idx_rcv_file_chunk_replicas_xftp_server_id ON rcv_file_chunk_replicas(
|
||||
xftp_server_id
|
||||
);
|
||||
CREATE TABLE snd_files(
|
||||
snd_file_id INTEGER PRIMARY KEY,
|
||||
snd_file_entity_id BLOB NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
num_recipients INTEGER NOT NULL,
|
||||
digest BLOB,
|
||||
key BLOB NOT NUll,
|
||||
nonce BLOB NOT NUll,
|
||||
path TEXT NOT NULL,
|
||||
prefix_path TEXT,
|
||||
status TEXT NOT NULL,
|
||||
deleted INTEGER NOT NULL DEFAULT 0,
|
||||
error TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
CREATE INDEX idx_snd_files_user_id ON snd_files(user_id);
|
||||
CREATE TABLE snd_file_chunks(
|
||||
snd_file_chunk_id INTEGER PRIMARY KEY,
|
||||
snd_file_id INTEGER NOT NULL REFERENCES snd_files ON DELETE CASCADE,
|
||||
chunk_no INTEGER NOT NULL,
|
||||
chunk_offset INTEGER NOT NULL,
|
||||
chunk_size INTEGER NOT NULL,
|
||||
digest BLOB,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
CREATE INDEX idx_snd_file_chunks_snd_file_id ON snd_file_chunks(snd_file_id);
|
||||
CREATE TABLE snd_file_chunk_replicas(
|
||||
snd_file_chunk_replica_id INTEGER PRIMARY KEY,
|
||||
snd_file_chunk_id INTEGER NOT NULL REFERENCES snd_file_chunks ON DELETE CASCADE,
|
||||
replica_number INTEGER NOT NULL,
|
||||
xftp_server_id INTEGER NOT NULL REFERENCES xftp_servers ON DELETE CASCADE,
|
||||
replica_id BLOB NOT NULL,
|
||||
replica_key BLOB NOT NULL,
|
||||
replica_status TEXT NOT NULL,
|
||||
delay INTEGER,
|
||||
retries INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
CREATE INDEX idx_snd_file_chunk_replicas_snd_file_chunk_id ON snd_file_chunk_replicas(
|
||||
snd_file_chunk_id
|
||||
);
|
||||
CREATE INDEX idx_snd_file_chunk_replicas_xftp_server_id ON snd_file_chunk_replicas(
|
||||
xftp_server_id
|
||||
);
|
||||
CREATE TABLE snd_file_chunk_replica_recipients(
|
||||
snd_file_chunk_replica_recipient_id INTEGER PRIMARY KEY,
|
||||
snd_file_chunk_replica_id INTEGER NOT NULL REFERENCES snd_file_chunk_replicas ON DELETE CASCADE,
|
||||
rcv_replica_id BLOB NOT NULL,
|
||||
rcv_replica_key BLOB NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
CREATE INDEX idx_snd_file_chunk_replica_recipients_snd_file_chunk_replica_id ON snd_file_chunk_replica_recipients(
|
||||
snd_file_chunk_replica_id
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user