core: Mobile.hs postgres interface (#5545)

* core: Mobile.hs postgres interface

* sqlite

* fix

* errors

* postgres

* rename

* rename, refactor

* merge files

* rename

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy 2025-01-20 17:41:48 +04:00 committed by GitHub
parent 0e940719c1
commit 20fa30eacc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 177 additions and 182 deletions

View file

@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: fdde9863cdc87dc47609a3a5f51a4c2c4c038858 tag: 488c7082f3b8cd1447e2e6f02bd913d2790f3c61
source-repository-package source-repository-package
type: git type: git

View file

@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."fdde9863cdc87dc47609a3a5f51a4c2c4c038858" = "00jx2zy7b8c8mav01h0ycj6qm5298pxhd960x0p1r1ram4a0nhww"; "https://github.com/simplex-chat/simplexmq.git"."488c7082f3b8cd1447e2e6f02bd913d2790f3c61" = "10x7byv49c5aj0c9ikvmnfsdi41czgffdwikizy339426b3mq4qx";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "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/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";

View file

@ -48,8 +48,10 @@ library
Simplex.Chat.Messages.Batch Simplex.Chat.Messages.Batch
Simplex.Chat.Messages.CIContent Simplex.Chat.Messages.CIContent
Simplex.Chat.Messages.CIContent.Events Simplex.Chat.Messages.CIContent.Events
Simplex.Chat.Mobile
Simplex.Chat.Mobile.File Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared Simplex.Chat.Mobile.Shared
Simplex.Chat.Mobile.WebRTC
Simplex.Chat.Operators Simplex.Chat.Operators
Simplex.Chat.Operators.Conditions Simplex.Chat.Operators.Conditions
Simplex.Chat.Options Simplex.Chat.Options
@ -96,8 +98,6 @@ library
else else
exposed-modules: exposed-modules:
Simplex.Chat.Archive Simplex.Chat.Archive
Simplex.Chat.Mobile
Simplex.Chat.Mobile.WebRTC
Simplex.Chat.Options.SQLite Simplex.Chat.Options.SQLite
Simplex.Chat.Store.SQLite.Migrations Simplex.Chat.Store.SQLite.Migrations
Simplex.Chat.Store.SQLite.Migrations.M20220101_initial Simplex.Chat.Store.SQLite.Migrations.M20220101_initial

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
@ -51,9 +50,6 @@ import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), ProtocolType (..),
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import qualified UnliftIO.Exception as E import qualified UnliftIO.Exception as E
import UnliftIO.STM import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo)
#endif
operatorSimpleXChat :: NewServerOperator operatorSimpleXChat :: NewServerOperator
operatorSimpleXChat = operatorSimpleXChat =
@ -188,19 +184,10 @@ logCfg :: LogConfig
logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} logCfg = LogConfig {lc_file = Nothing, lc_stderr = True}
createChatDatabase :: ChatDbOpts -> MigrationConfirmation -> IO (Either MigrationError ChatDatabase) createChatDatabase :: ChatDbOpts -> MigrationConfirmation -> IO (Either MigrationError ChatDatabase)
createChatDatabase dbOpts confirmMigrations = runExceptT $ do createChatDatabase chatDbOpts confirmMigrations = runExceptT $ do
#if defined(dbPostgres) chatStore <- ExceptT $ createChatStore (toDBOpts chatDbOpts chatSuffix False) confirmMigrations
let ChatDbOpts {dbName, dbUser, dbSchemaPrefix} = dbOpts agentStore <- ExceptT $ createAgentStore (toDBOpts chatDbOpts agentSuffix False) confirmMigrations
connectInfo = defaultConnectInfo {connectUser = dbUser, connectDatabase = dbName}
chatStore <- ExceptT $ createChatStore connectInfo (chatSchema dbSchemaPrefix) confirmMigrations
agentStore <- ExceptT $ createAgentStore connectInfo (agentSchema dbSchemaPrefix) confirmMigrations
pure ChatDatabase {chatStore, agentStore} pure ChatDatabase {chatStore, agentStore}
#else
let ChatDbOpts {dbFilePrefix, dbKey, vacuumOnMigration} = dbOpts
chatStore <- ExceptT $ createChatStore (chatStoreFile dbFilePrefix) dbKey False confirmMigrations vacuumOnMigration
agentStore <- ExceptT $ createAgentStore (agentStoreFile dbFilePrefix) dbKey False confirmMigrations vacuumOnMigration
pure ChatDatabase {chatStore, agentStore}
#endif
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Bool -> IO ChatController newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Bool -> IO ChatController
newChatController newChatController

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
@ -22,19 +21,13 @@ import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64) import Data.Int (Int64)
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Types (Contact, ContactId, User) import Simplex.Chat.Types (Contact, ContactId, User)
import Simplex.Messaging.Agent.Store.DB (Binary (..)) import Simplex.Messaging.Agent.Store.DB (Binary (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
import Simplex.Messaging.Util (decodeJSON, encodeJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data Call = Call data Call = Call
{ contactId :: ContactId, { contactId :: ContactId,

View file

@ -86,8 +86,8 @@ import Simplex.FileTransfer.Description (FileDescriptionURI (..), maxFileSize, m
import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent as Agent
import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles)
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store.Interface (execSQL)
import Simplex.Messaging.Agent.Store.Shared (upMigration) import Simplex.Messaging.Agent.Store.Shared (upMigration)
import Simplex.Messaging.Agent.Store (execSQL)
import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Agent.Store.Migrations as Migrations import qualified Simplex.Messaging.Agent.Store.Migrations as Migrations
import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (SMAlways), textToHostMode) import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (SMAlways), textToHostMode)

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -43,6 +42,7 @@ import GHC.TypeLits (ErrorMessage (ShowType, type (:<>:)), TypeError)
import qualified GHC.TypeLits as Type import qualified GHC.TypeLits as Type
import Simplex.Chat.Markdown import Simplex.Chat.Markdown
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
@ -54,13 +54,6 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON)
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody) import Simplex.Messaging.Protocol (BlockingInfo, MsgBody)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -25,6 +24,7 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Type.Equality import Data.Type.Equality
import Data.Word (Word32) import Data.Word (Word32)
import Simplex.Chat.Messages.CIContent.Events import Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
@ -34,13 +34,6 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOff, pattern
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>)) import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data MsgDirection = MDRcv | MDSnd data MsgDirection = MDRcv | MDSnd
deriving (Eq, Show) deriving (Eq, Show)

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
@ -16,7 +17,6 @@ import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ import qualified Data.Aeson.TH as JQ
import Data.Bifunctor (first) import Data.Bifunctor (first)
import Data.ByteArray (ScrubbedBytes) import Data.ByteArray (ScrubbedBytes)
import qualified Data.ByteArray as BA
import qualified Data.ByteString.Base64.URL as U import qualified Data.ByteString.Base64.URL as U
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
@ -26,8 +26,6 @@ import Data.List (find)
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Word (Word8) import Data.Word (Word8)
import Database.SQLite.Simple (SQLError (..))
import qualified Database.SQLite.Simple as DB
import Foreign.C.String import Foreign.C.String
import Foreign.C.Types (CInt (..)) import Foreign.C.Types (CInt (..))
import Foreign.Ptr import Foreign.Ptr
@ -49,7 +47,7 @@ import Simplex.Chat.Store.Profiles
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Messaging.Agent.Client (agentClientStore) import Simplex.Messaging.Agent.Client (agentClientStore)
import Simplex.Messaging.Agent.Env.SQLite (createAgentStore) import Simplex.Messaging.Agent.Env.SQLite (createAgentStore)
import Simplex.Messaging.Agent.Store (closeStore, reopenStore) import Simplex.Messaging.Agent.Store.Interface (closeDBStore, reopenDBStore)
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
@ -58,6 +56,10 @@ import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..)
import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8) import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8)
import System.IO (utf8) import System.IO (utf8)
import System.Timeout (timeout) import System.Timeout (timeout)
#if !defined(dbPostgres)
import Database.SQLite.Simple (SQLError (..))
import qualified Database.SQLite.Simple as DB
#endif
data DBMigrationResult data DBMigrationResult
= DBMOk = DBMOk
@ -112,9 +114,11 @@ foreign export ccall "chat_encrypt_file" cChatEncryptFile :: StablePtr ChatContr
foreign export ccall "chat_decrypt_file" cChatDecryptFile :: CString -> CString -> CString -> CString -> IO CString foreign export ccall "chat_decrypt_file" cChatDecryptFile :: CString -> CString -> CString -> CString -> IO CString
-- | check / migrate database and initialize chat controller on success -- | check / migrate database and initialize chat controller on success
-- For postgres first param is schema prefix, second param is database connection string.
cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
cChatMigrateInit fp key conf = cChatMigrateInitKey fp key 0 conf 0 cChatMigrateInit fp key conf = cChatMigrateInitKey fp key 0 conf 0
-- For postgres first param is schema prefix, second param is database connection string.
cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> CInt -> Ptr (StablePtr ChatController) -> IO CJSONString cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> CInt -> Ptr (StablePtr ChatController) -> IO CJSONString
cChatMigrateInitKey fp key keepKey conf background ctrl = do cChatMigrateInitKey fp key keepKey conf background ctrl = do
-- ensure we are set to UTF-8; iOS does not have locale, and will default to -- ensure we are set to UTF-8; iOS does not have locale, and will default to
@ -123,11 +127,10 @@ cChatMigrateInitKey fp key keepKey conf background ctrl = do
setFileSystemEncoding utf8 setFileSystemEncoding utf8
setForeignEncoding utf8 setForeignEncoding utf8
dbPath <- peekCString fp chatDbOpts <- mobileDbOpts fp key
dbKey <- BA.convert <$> B.packCString key
confirm <- peekCAString conf confirm <- peekCAString conf
r <- r <-
chatMigrateInitKey dbPath dbKey (keepKey /= 0) confirm (background /= 0) >>= \case chatMigrateInitKey chatDbOpts (keepKey /= 0) confirm (background /= 0) >>= \case
Right cc -> (newStablePtr cc >>= poke ctrl) $> DBMOk Right cc -> (newStablePtr cc >>= poke ctrl) $> DBMOk
Left e -> pure e Left e -> pure e
newCStringFromLazyBS $ J.encode r newCStringFromLazyBS $ J.encode r
@ -185,17 +188,12 @@ cChatValidName cName = newCString . mkValidName =<< peekCString cName
cChatJsonLength :: CString -> IO CInt cChatJsonLength :: CString -> IO CInt
cChatJsonLength s = fromIntegral . subtract 2 . LB.length . J.encode . safeDecodeUtf8 <$> B.packCString s cChatJsonLength s = fromIntegral . subtract 2 . LB.length . J.encode . safeDecodeUtf8 <$> B.packCString s
mobileChatOpts :: String -> ChatOpts mobileChatOpts :: ChatDbOpts -> ChatOpts
mobileChatOpts dbFilePrefix = mobileChatOpts dbOptions =
ChatOpts ChatOpts
{ coreOptions = { coreOptions =
CoreChatOpts CoreChatOpts
{ dbOptions = { dbOptions,
ChatDbOpts
{ dbFilePrefix,
dbKey = "", -- for API database is already opened, and the key in options is not used
vacuumOnMigration = True
},
smpServers = [], smpServers = [],
xftpServers = [], xftpServers = [],
simpleNetCfg = defaultSimpleNetCfg, simpleNetCfg = defaultSimpleNetCfg,
@ -235,40 +233,50 @@ defaultMobileConfig =
getActiveUser_ :: DBStore -> IO (Maybe User) getActiveUser_ :: DBStore -> IO (Maybe User)
getActiveUser_ st = find activeUser <$> withTransaction st getUsers getActiveUser_ st = find activeUser <$> withTransaction st getUsers
#if !defined(dbPostgres)
-- only used in tests
chatMigrateInit :: String -> ScrubbedBytes -> String -> IO (Either DBMigrationResult ChatController) chatMigrateInit :: String -> ScrubbedBytes -> String -> IO (Either DBMigrationResult ChatController)
chatMigrateInit dbFilePrefix dbKey confirm = chatMigrateInitKey dbFilePrefix dbKey False confirm False chatMigrateInit dbFilePrefix dbKey confirm = do
let chatDBOpts = ChatDbOpts {dbFilePrefix, dbKey, vacuumOnMigration = True}
chatMigrateInitKey chatDBOpts False confirm False
#endif
chatMigrateInitKey :: String -> ScrubbedBytes -> Bool -> String -> Bool -> IO (Either DBMigrationResult ChatController) chatMigrateInitKey :: ChatDbOpts -> Bool -> String -> Bool -> IO (Either DBMigrationResult ChatController)
chatMigrateInitKey dbFilePrefix dbKey keepKey confirm backgroundMode = runExceptT $ do chatMigrateInitKey chatDbOpts keepKey confirm backgroundMode = runExceptT $ do
confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode $ B.pack confirm confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode $ B.pack confirm
chatStore <- migrate createChatStore (chatStoreFile dbFilePrefix) confirmMigrations chatStore <- migrate createChatStore (toDBOpts chatDbOpts chatSuffix keepKey) confirmMigrations
agentStore <- migrate createAgentStore (agentStoreFile dbFilePrefix) confirmMigrations agentStore <- migrate createAgentStore (toDBOpts chatDbOpts agentSuffix keepKey) confirmMigrations
liftIO $ initialize chatStore ChatDatabase {chatStore, agentStore} liftIO $ initialize chatStore ChatDatabase {chatStore, agentStore}
where where
opts = mobileChatOpts dbFilePrefix opts = mobileChatOpts $ removeDbKey chatDbOpts
initialize st db = do initialize st db = do
user_ <- getActiveUser_ st user_ <- getActiveUser_ st
newChatController db user_ defaultMobileConfig opts backgroundMode newChatController db user_ defaultMobileConfig opts backgroundMode
migrate createStore dbFile confirmMigrations = migrate createStore dbOpts confirmMigrations =
ExceptT $ ExceptT $
(first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations (vacuumOnMigration $ dbOptions $ coreOptions opts)) (first (DBMErrorMigration errDbStr) <$> createStore dbOpts confirmMigrations)
#if !defined(dbPostgres)
`catch` (pure . checkDBError) `catch` (pure . checkDBError)
#endif
`catchAll` (pure . dbError) `catchAll` (pure . dbError)
where where
errDbStr = errorDbStr dbOpts
#if !defined(dbPostgres)
checkDBError e = case sqlError e of checkDBError e = case sqlError e of
DB.ErrorNotADatabase -> Left $ DBMErrorNotADatabase dbFile DB.ErrorNotADatabase -> Left $ DBMErrorNotADatabase errDbStr
_ -> dbError e _ -> dbError e
dbError e = Left . DBMErrorSQL dbFile $ show e #endif
dbError e = Left . DBMErrorSQL errDbStr $ show e
chatCloseStore :: ChatController -> IO String chatCloseStore :: ChatController -> IO String
chatCloseStore ChatController {chatStore, smpAgent} = handleErr $ do chatCloseStore ChatController {chatStore, smpAgent} = handleErr $ do
closeStore chatStore closeDBStore chatStore
closeStore $ agentClientStore smpAgent closeDBStore $ agentClientStore smpAgent
chatReopenStore :: ChatController -> IO String chatReopenStore :: ChatController -> IO String
chatReopenStore ChatController {chatStore, smpAgent} = handleErr $ do chatReopenStore ChatController {chatStore, smpAgent} = handleErr $ do
reopenStore chatStore reopenDBStore chatStore
reopenStore (agentClientStore smpAgent) reopenDBStore (agentClientStore smpAgent)
handleErr :: IO () -> IO String handleErr :: IO () -> IO String
handleErr a = (a $> "") `catch` (pure . show @SomeException) handleErr a = (a $> "") `catch` (pure . show @SomeException)

View file

@ -46,6 +46,7 @@ import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime, nominalDay) import Data.Time.Clock (UTCTime, nominalDay)
import Language.Haskell.TH.Syntax (lift) import Language.Haskell.TH.Syntax (lift)
import Simplex.Chat.Operators.Conditions import Simplex.Chat.Operators.Conditions
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Types (User) import Simplex.Chat.Types (User)
import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Chat.Types.Util (textParseJSON)
import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles)
@ -54,13 +55,6 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTy
import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol)
import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Client (TransportHost (..))
import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8) import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
usageConditionsCommit :: Text usageConditionsCommit :: Text
usageConditionsCommit = "a5061f3147165a05979d6ace33960aced2d6ac03" usageConditionsCommit = "a5061f3147165a05979d6ace33960aced2d6ac03"

View file

@ -1,14 +1,25 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
module Simplex.Chat.Options.DB module Simplex.Chat.Options.DB
#if defined(dbPostgres) #if defined(dbPostgres)
( module Simplex.Chat.Options.Postgres, ( module Simplex.Chat.Options.Postgres,
FromField (..),
ToField (..),
) )
where where
import Simplex.Chat.Options.Postgres import Simplex.Chat.Options.Postgres
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else #else
( module Simplex.Chat.Options.SQLite, ( module Simplex.Chat.Options.SQLite,
FromField (..),
ToField (..),
) )
where where
import Simplex.Chat.Options.SQLite import Simplex.Chat.Options.SQLite
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif #endif

View file

@ -1,37 +1,68 @@
{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Simplex.Chat.Options.Postgres where module Simplex.Chat.Options.Postgres where
import qualified Data.ByteString.Char8 as B
import Foreign.C.String
import Options.Applicative import Options.Applicative
import Simplex.Messaging.Agent.Store.Interface (DBOpts (..))
data ChatDbOpts = ChatDbOpts data ChatDbOpts = ChatDbOpts
{ dbName :: String, { dbConnstr :: String,
dbUser :: String,
dbSchemaPrefix :: String dbSchemaPrefix :: String
} }
chatDbOptsP :: FilePath -> String -> Parser ChatDbOpts chatDbOptsP :: FilePath -> String -> Parser ChatDbOpts
chatDbOptsP _appDir defaultDbName = do chatDbOptsP _appDir defaultDbName = do
dbName <- dbConnstr <-
strOption strOption
( long "database" ( long "database"
<> short 'd' <> short 'd'
<> metavar "DB_NAME" <> metavar "DB_CONN"
<> help "Database name" <> help "Database connection string"
<> value defaultDbName <> value ("postgresql://simplex@/" <> defaultDbName)
<> showDefault <> showDefault
) )
dbUser <- dbSchemaPrefix <-
strOption strOption
( long "database-user" ( long "schema-prefix"
<> short 'u' <> metavar "DB_SCHEMA_PREFIX"
<> metavar "DB_USER" <> help "Database schema prefix"
<> help "Database user" <> value "simplex_v1"
<> value "simplex"
<> showDefault <> showDefault
) )
pure ChatDbOpts {dbName, dbUser, dbSchemaPrefix = ""} pure ChatDbOpts {dbConnstr, dbSchemaPrefix}
dbString :: ChatDbOpts -> String dbString :: ChatDbOpts -> String
dbString ChatDbOpts {dbName} = dbName dbString ChatDbOpts {dbConnstr} = dbConnstr
toDBOpts :: ChatDbOpts -> String -> Bool -> DBOpts
toDBOpts ChatDbOpts {dbConnstr, dbSchemaPrefix} dbSuffix _keepKey =
DBOpts
{ connstr = B.pack dbConnstr,
schema = if null dbSchemaPrefix then "simplex_v1" <> dbSuffix else dbSchemaPrefix <> dbSuffix
}
chatSuffix :: String
chatSuffix = "_chat_schema"
agentSuffix :: String
agentSuffix = "_agent_schema"
mobileDbOpts :: CString -> CString -> IO ChatDbOpts
mobileDbOpts schemaPrefix connstr = do
dbSchemaPrefix <- peekCString schemaPrefix
dbConnstr <- peekCString connstr
pure $
ChatDbOpts
{ dbConnstr,
dbSchemaPrefix
}
removeDbKey :: ChatDbOpts -> ChatDbOpts
removeDbKey = id
errorDbStr :: DBOpts -> String
errorDbStr DBOpts {schema} = schema

View file

@ -1,11 +1,16 @@
{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Simplex.Chat.Options.SQLite where module Simplex.Chat.Options.SQLite where
import Data.ByteArray (ScrubbedBytes) import Data.ByteArray (ScrubbedBytes)
import qualified Data.ByteArray as BA
import qualified Data.ByteString.Char8 as B
import Foreign.C.String
import Options.Applicative import Options.Applicative
import Simplex.Messaging.Agent.Store.Interface (DBOpts (..))
import System.FilePath (combine) import System.FilePath (combine)
data ChatDbOpts = ChatDbOpts data ChatDbOpts = ChatDbOpts
@ -42,3 +47,42 @@ chatDbOptsP appDir defaultDbName = do
dbString :: ChatDbOpts -> String dbString :: ChatDbOpts -> String
dbString ChatDbOpts {dbFilePrefix} = dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" dbString ChatDbOpts {dbFilePrefix} = dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db"
toDBOpts :: ChatDbOpts -> String -> Bool -> DBOpts
toDBOpts ChatDbOpts {dbFilePrefix, dbKey, vacuumOnMigration} dbSuffix keepKey = do
DBOpts
{ dbFilePath = dbFilePrefix <> dbSuffix,
dbKey,
keepKey,
vacuum = vacuumOnMigration
}
chatSuffix :: String
chatSuffix = "_chat.db"
agentSuffix :: String
agentSuffix = "_agent.db"
mobileDbOpts :: CString -> CString -> IO ChatDbOpts
mobileDbOpts fp key = do
dbFilePrefix <- peekCString fp
dbKey <- BA.convert <$> B.packCString key
pure $
ChatDbOpts
{ dbFilePrefix,
dbKey,
vacuumOnMigration = True
}
-- used to create new chat controller,
-- at that point database is already opened, and the key in options is not used
removeDbKey :: ChatDbOpts -> ChatDbOpts
removeDbKey ChatDbOpts {dbFilePrefix, vacuumOnMigration} =
ChatDbOpts
{ dbFilePrefix,
dbKey = "",
vacuumOnMigration
}
errorDbStr :: DBOpts -> String
errorDbStr DBOpts {dbFilePath} = dbFilePath

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
@ -46,6 +45,7 @@ import Data.Type.Equality
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Data.Word (Word32) import Data.Word (Word32)
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Shared
@ -58,13 +58,6 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstTo
import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Protocol (MsgBody)
import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>))
import Simplex.Messaging.Version hiding (version) import Simplex.Messaging.Version hiding (version)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
-- Chat version history: -- Chat version history:
-- 1 - support chat versions in connections (9/1/2023) -- 1 - support chat versions in connections (9/1/2023)

View file

@ -9,13 +9,6 @@ module Simplex.Chat.Store
AutoAccept (..), AutoAccept (..),
createChatStore, createChatStore,
migrations, -- used in tests migrations, -- used in tests
#if defined(dbPostgres)
chatSchema,
agentSchema,
#else
chatStoreFile,
agentStoreFile,
#endif
withTransaction, withTransaction,
) )
where where
@ -23,35 +16,13 @@ where
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
import Simplex.Chat.Store.Shared import Simplex.Chat.Store.Shared
import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction) import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction)
import Simplex.Messaging.Agent.Store.Interface (DBOpts, createDBStore)
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, MigrationError) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, MigrationError)
#if defined(dbPostgres) #if defined(dbPostgres)
import Database.PostgreSQL.Simple (ConnectInfo (..))
import Simplex.Chat.Store.Postgres.Migrations import Simplex.Chat.Store.Postgres.Migrations
import Simplex.Messaging.Agent.Store.Postgres (createDBStore)
#else #else
import Data.ByteArray (ScrubbedBytes)
import Simplex.Chat.Store.SQLite.Migrations import Simplex.Chat.Store.SQLite.Migrations
import Simplex.Messaging.Agent.Store.SQLite (createDBStore)
#endif #endif
#if defined(dbPostgres) createChatStore :: DBOpts -> MigrationConfirmation -> IO (Either MigrationError DBStore)
createChatStore :: ConnectInfo -> String -> MigrationConfirmation -> IO (Either MigrationError DBStore) createChatStore dbCreateOpts = createDBStore dbCreateOpts migrations
createChatStore connectInfo schema = createDBStore connectInfo schema migrations
chatSchema :: String -> String
chatSchema "" = "chat_schema"
chatSchema prefix = prefix <> "_chat_schema"
agentSchema :: String -> String
agentSchema "" = "agent_schema"
agentSchema prefix = prefix <> "_agent_schema"
#else
createChatStore :: FilePath -> ScrubbedBytes -> Bool -> MigrationConfirmation -> Bool -> IO (Either MigrationError DBStore)
createChatStore dbPath key keepKey = createDBStore dbPath key keepKey migrations
chatStoreFile :: FilePath -> FilePath
chatStoreFile = (<> "_chat.db")
agentStoreFile :: FilePath -> FilePath
agentStoreFile = (<> "_agent.db")
#endif

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -31,17 +30,11 @@ import Data.Maybe (fromMaybe, isJust)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import GHC.Records.Compat import GHC.Records.Compat
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Shared
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON)
import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data ChatFeature data ChatFeature
= CFTimedMessages = CFTimedMessages

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
@ -7,16 +6,10 @@ module Simplex.Chat.Types.Shared where
import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (blobFieldDecoder) import Simplex.Messaging.Parsers (blobFieldDecoder)
import Simplex.Messaging.Util ((<$?>)) import Simplex.Messaging.Util ((<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data GroupMemberRole data GroupMemberRole
= GRObserver -- connects to all group members and receives all messages, can't send messages = GRObserver -- connects to all group members and receives all messages, can't send messages

View file

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
@ -14,17 +13,11 @@ import qualified Data.Aeson.TH as JQ
import Data.Char (toLower) import Data.Char (toLower)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_)
import Simplex.Messaging.Util (decodeJSON, encodeJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data UITheme = UITheme data UITheme = UITheme
{ themeId :: Text, { themeId :: Text,

View file

@ -44,7 +44,7 @@ import Simplex.Messaging.Agent (disposeAgentClient)
import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange)
import Simplex.Messaging.Agent.RetryInterval import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Store (closeStore) import Simplex.Messaging.Agent.Store.Interface (closeDBStore)
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError)
import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Client (ProtocolClientConfig (..)) import Simplex.Messaging.Client (ProtocolClientConfig (..))
@ -72,17 +72,14 @@ import System.FilePath ((</>))
#endif #endif
#if defined(dbPostgres) #if defined(dbPostgres)
testDBName :: String testDBConnstr :: String
testDBName = "test_chat_db" testDBConnstr = "postgresql://test_chat_user@/test_chat_db"
testDBUser :: String
testDBUser = "test_chat_user"
testDBConnectInfo :: ConnectInfo testDBConnectInfo :: ConnectInfo
testDBConnectInfo = testDBConnectInfo =
defaultConnectInfo { defaultConnectInfo {
connectUser = testDBUser, connectUser = "test_chat_user",
connectDatabase = testDBName connectDatabase = "test_chat_db"
} }
#endif #endif
@ -114,8 +111,7 @@ testCoreOpts =
{ {
dbOptions = ChatDbOpts dbOptions = ChatDbOpts
#if defined(dbPostgres) #if defined(dbPostgres)
{ dbName = testDBName, { dbConnstr = testDBConnstr,
dbUser = testDBUser,
-- dbSchemaPrefix is not used in tests (except bot tests where it's redefined), -- dbSchemaPrefix is not used in tests (except bot tests where it's redefined),
-- instead different schema prefix is passed per client so that single test database is used -- instead different schema prefix is passed per client so that single test database is used
dbSchemaPrefix = "" dbSchemaPrefix = ""
@ -323,7 +319,7 @@ stopTestChat TestCC {chatController = cc@ChatController {smpAgent, chatStore}, c
uninterruptibleCancel termAsync uninterruptibleCancel termAsync
uninterruptibleCancel chatAsync uninterruptibleCancel chatAsync
liftIO $ disposeAgentClient smpAgent liftIO $ disposeAgentClient smpAgent
closeStore chatStore closeDBStore chatStore
threadDelay 200000 threadDelay 200000
withNewTestChat :: HasCallStack => FilePath -> String -> Profile -> (HasCallStack => TestCC -> IO a) -> IO a withNewTestChat :: HasCallStack => FilePath -> String -> Profile -> (HasCallStack => TestCC -> IO a) -> IO a

View file

@ -28,6 +28,7 @@ import Simplex.Chat.Call
import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..))
import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Messages (ChatItemId)
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.Options.DB
import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat)
import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.Env.SQLite
@ -44,7 +45,6 @@ import Test.Hspec hiding (it)
import Database.PostgreSQL.Simple (Only (..)) import Database.PostgreSQL.Simple (Only (..))
#else #else
import Database.SQLite.Simple (Only (..)) import Database.SQLite.Simple (Only (..))
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
import System.FilePath ((</>)) import System.FilePath ((</>))
#endif #endif
@ -2793,8 +2793,8 @@ setupDesynchronizedRatchet tmp alice = do
(alice </) (alice </)
where where
copyDb from to = do copyDb from to = do
copyFile (chatStoreFile $ tmp </> from) (chatStoreFile $ tmp </> to) copyFile (tmp </> (from <> chatSuffix)) (tmp </> (to <> chatSuffix))
copyFile (agentStoreFile $ tmp </> from) (agentStoreFile $ tmp </> to) copyFile (tmp </> (from <> agentSuffix)) (tmp </> (to <> agentSuffix))
testSyncRatchet :: HasCallStack => FilePath -> IO () testSyncRatchet :: HasCallStack => FilePath -> IO ()
testSyncRatchet tmp = testSyncRatchet tmp =

View file

@ -20,6 +20,7 @@ import qualified Data.Text as T
import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Controller (ChatConfig (..))
import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Messages (ChatItemId)
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.Options.DB
import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Types (VersionRangeChat) import Simplex.Chat.Types (VersionRangeChat)
import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..))
@ -33,7 +34,6 @@ import Test.Hspec hiding (it)
import Database.PostgreSQL.Simple (Only (..)) import Database.PostgreSQL.Simple (Only (..))
#else #else
import Database.SQLite.Simple (Only (..)) import Database.SQLite.Simple (Only (..))
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
import System.Directory (copyFile) import System.Directory (copyFile)
import System.FilePath ((</>)) import System.FilePath ((</>))
#endif #endif
@ -3606,8 +3606,8 @@ setupDesynchronizedRatchet tmp alice = do
bob <# "#team alice> decryption error, possibly due to the device change (header, 3 messages)" bob <# "#team alice> decryption error, possibly due to the device change (header, 3 messages)"
where where
copyDb from to = do copyDb from to = do
copyFile (chatStoreFile $ tmp </> from) (chatStoreFile $ tmp </> to) copyFile (tmp </> (from <> chatSuffix)) (tmp </> (to <> chatSuffix))
copyFile (agentStoreFile $ tmp </> from) (agentStoreFile $ tmp </> to) copyFile (tmp </> (from <> agentSuffix)) (tmp </> (to <> agentSuffix))
testGroupSyncRatchet :: HasCallStack => FilePath -> IO () testGroupSyncRatchet :: HasCallStack => FilePath -> IO ()
testGroupSyncRatchet tmp = testGroupSyncRatchet tmp =

View file

@ -32,9 +32,11 @@ import Simplex.Chat.Mobile
import Simplex.Chat.Mobile.File import Simplex.Chat.Mobile.File
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Mobile.WebRTC
import Simplex.Chat.Options.DB
import Simplex.Chat.Store import Simplex.Chat.Store
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
import Simplex.Chat.Types (AgentUserId (..), Profile (..)) import Simplex.Chat.Types (AgentUserId (..), Profile (..))
import Simplex.Messaging.Agent.Store.Interface
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..)) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile(..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile(..), CryptoFileArgs (..))
@ -157,8 +159,8 @@ testChatApiNoUser tmp = do
testChatApi :: FilePath -> IO () testChatApi :: FilePath -> IO ()
testChatApi tmp = do testChatApi tmp = do
let dbPrefix = tmp </> "1" let dbPrefix = tmp </> "1"
f = chatStoreFile dbPrefix f = dbPrefix <> chatSuffix
Right st <- createChatStore f "myKey" False MCYesUp True Right st <- createChatStore (DBOpts f "myKey" False True) MCYesUp
Right _ <- withTransaction st $ \db -> runExceptT $ createUserRecord db (AgentUserId 1) aliceProfile {preferences = Nothing} True Right _ <- withTransaction st $ \db -> runExceptT $ createUserRecord db (AgentUserId 1) aliceProfile {preferences = Nothing} True
Right cc <- chatMigrateInit dbPrefix "myKey" "yesUp" Right cc <- chatMigrateInit dbPrefix "myKey" "yesUp"
Left (DBMErrorNotADatabase _) <- chatMigrateInit dbPrefix "" "yesUp" Left (DBMErrorNotADatabase _) <- chatMigrateInit dbPrefix "" "yesUp"

View file

@ -10,8 +10,8 @@ import Data.List (dropWhileEnd)
import Data.Maybe (fromJust, isJust) import Data.Maybe (fromJust, isJust)
import Simplex.Chat.Store (createChatStore) import Simplex.Chat.Store (createChatStore)
import qualified Simplex.Chat.Store as Store import qualified Simplex.Chat.Store as Store
import Simplex.Messaging.Agent.Store.Interface
import Simplex.Messaging.Agent.Store.Shared (Migration (..), MigrationConfirmation (..), MigrationsToRun (..), toDownMigration) import Simplex.Messaging.Agent.Store.Shared (Migration (..), MigrationConfirmation (..), MigrationsToRun (..), toDownMigration)
import Simplex.Messaging.Agent.Store.SQLite (closeDBStore, createDBStore)
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
import Simplex.Messaging.Util (ifM, whenM) import Simplex.Messaging.Util (ifM, whenM)
import System.Directory (doesFileExist, removeFile) import System.Directory (doesFileExist, removeFile)
@ -53,7 +53,7 @@ testVerifySchemaDump :: IO ()
testVerifySchemaDump = withTmpFiles $ do testVerifySchemaDump = withTmpFiles $ do
savedSchema <- ifM (doesFileExist appSchema) (readFile appSchema) (pure "") savedSchema <- ifM (doesFileExist appSchema) (readFile appSchema) (pure "")
savedSchema `deepseq` pure () savedSchema `deepseq` pure ()
void $ createChatStore testDB "" False MCError True void $ createChatStore (DBOpts testDB "" False True) MCError
getSchema testDB appSchema `shouldReturn` savedSchema getSchema testDB appSchema `shouldReturn` savedSchema
removeFile testDB removeFile testDB
@ -61,14 +61,14 @@ testVerifyLintFKeyIndexes :: IO ()
testVerifyLintFKeyIndexes = withTmpFiles $ do testVerifyLintFKeyIndexes = withTmpFiles $ do
savedLint <- ifM (doesFileExist appLint) (readFile appLint) (pure "") savedLint <- ifM (doesFileExist appLint) (readFile appLint) (pure "")
savedLint `deepseq` pure () savedLint `deepseq` pure ()
void $ createChatStore testDB "" False MCError True void $ createChatStore (DBOpts testDB "" False True) MCError
getLintFKeyIndexes testDB "tests/tmp/chat_lint.sql" `shouldReturn` savedLint getLintFKeyIndexes testDB "tests/tmp/chat_lint.sql" `shouldReturn` savedLint
removeFile testDB removeFile testDB
testSchemaMigrations :: IO () testSchemaMigrations :: IO ()
testSchemaMigrations = withTmpFiles $ do testSchemaMigrations = withTmpFiles $ do
let noDownMigrations = dropWhileEnd (\Migration {down} -> isJust down) Store.migrations let noDownMigrations = dropWhileEnd (\Migration {down} -> isJust down) Store.migrations
Right st <- createDBStore testDB "" False noDownMigrations MCError True Right st <- createDBStore (DBOpts testDB "" False True) noDownMigrations MCError
mapM_ (testDownMigration st) $ drop (length noDownMigrations) Store.migrations mapM_ (testDownMigration st) $ drop (length noDownMigrations) Store.migrations
closeDBStore st closeDBStore st
removeFile testDB removeFile testDB