diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 619788f6e..4bb06afd8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -516,8 +516,8 @@ object ChatController { throw Exception("failed to delete the user ${r.responseType} ${r.details}") } - suspend fun apiStartChat(openDBWithKey: String? = null): Boolean { - val r = sendCmd(CC.StartChat(ChatCtrlCfg(subConns = true, enableExpireCIs = true, startXFTPWorkers = true, openDBWithKey = openDBWithKey))) + suspend fun apiStartChat(): Boolean { + val r = sendCmd(CC.StartChat(expire = true)) when (r) { is CR.ChatStarted -> return true is CR.ChatRunning -> return false @@ -525,8 +525,8 @@ object ChatController { } } - suspend fun apiStopChat(closeStore: Boolean): Boolean { - val r = sendCmd(CC.ApiStopChat(closeStore)) + suspend fun apiStopChat(): Boolean { + val r = sendCmd(CC.ApiStopChat()) when (r) { is CR.ChatStopped -> return true else -> throw Error("failed stopping chat: ${r.responseType} ${r.details}") @@ -1834,8 +1834,8 @@ sealed class CC { class ApiMuteUser(val userId: Long): CC() class ApiUnmuteUser(val userId: Long): CC() class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC() - class StartChat(val cfg: ChatCtrlCfg): CC() - class ApiStopChat(val closeStore: Boolean): CC() + class StartChat(val expire: Boolean): CC() + class ApiStopChat: CC() class SetTempFolder(val tempFolder: String): CC() class SetFilesFolder(val filesFolder: String): CC() class ApiSetXFTPConfig(val config: XFTPFileConfig?): CC() @@ -1938,9 +1938,8 @@ sealed class CC { is ApiMuteUser -> "/_mute user $userId" is ApiUnmuteUser -> "/_unmute user $userId" is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}" -// is StartChat -> "/_start ${json.encodeToString(cfg)}" // this can be used with the new core - is StartChat -> "/_start subscribe=on expire=${onOff(cfg.enableExpireCIs)} xftp=on" - is ApiStopChat -> if (closeStore) "/_stop close" else "/_stop" + is StartChat -> "/_start subscribe=on expire=${onOff(expire)} xftp=on" + is ApiStopChat -> "/_stop" is SetTempFolder -> "/_temp_folder $tempFolder" is SetFilesFolder -> "/_files_folder $filesFolder" is ApiSetXFTPConfig -> if (config != null) "/_xftp on ${json.encodeToString(config)}" else "/_xftp off" @@ -2157,14 +2156,6 @@ sealed class CC { } } -@Serializable -data class ChatCtrlCfg ( - val subConns: Boolean, - val enableExpireCIs: Boolean, - val startXFTPWorkers: Boolean, - val openDBWithKey: String? -) - @Serializable data class NewUser( val profile: Profile?, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 3eb2e7d73..fa0f8f54d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -419,7 +419,7 @@ private fun stopChat(m: ChatModel) { } suspend fun stopChatAsync(m: ChatModel) { - m.controller.apiStopChat(false) + m.controller.apiStopChat() m.chatRunning.value = false } diff --git a/cabal.project b/cabal.project index b9753c9c0..b4024f088 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: fda1284ae4b7c33cae2eb8ed0376a511aecc1d51 + tag: 8d47f690838371bc848e4b31a4b09ef6bf67ccc5 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index faa2401b1..26f4ea112 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."fda1284ae4b7c33cae2eb8ed0376a511aecc1d51" = "1gq7scv9z8x3xhzl914xr46na0kkrqd1i743xbw69lyx33kj9xb5"; + "https://github.com/simplex-chat/simplexmq.git"."8d47f690838371bc848e4b31a4b09ef6bf67ccc5" = "1pwasv22ii3wy4xchaknlwczmy5ws7adx7gg2g58lxzrgdjm3650"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 896edd26b..10f925471 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -34,7 +34,6 @@ import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.Char (isSpace, toLower) -import Data.Composition ((.:)) import Data.Constraint (Dict (..)) import Data.Either (fromRight, rights) import Data.Fixed (div') @@ -84,7 +83,7 @@ import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentCl import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore, defaultAgentConfig) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol -import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, SQLiteStore (dbNew), execSQL, upMigration, withConnection, closeSQLiteStore, openSQLiteStore) +import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, SQLiteStore (dbNew), execSQL, upMigration, withConnection) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations @@ -218,8 +217,8 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen where configServers :: DefaultAgentServers configServers = - let smp' = fromMaybe defaultServers.smp (nonEmpty smpServers) - xftp' = fromMaybe defaultServers.xftp (nonEmpty xftpServers) + let smp' = fromMaybe (defaultServers.smp) (nonEmpty smpServers) + xftp' = fromMaybe (defaultServers.xftp) (nonEmpty xftpServers) in defaultServers {smp = smp', xftp = xftp', netCfg = networkConfig} agentServers :: ChatConfig -> IO InitialAgentServers agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do @@ -252,7 +251,7 @@ cfgServers p s = case p of startChatController :: forall m. ChatMonad' m => Bool -> Bool -> Bool -> m (Async ()) startChatController subConns enableExpireCIs startXFTPWorkers = do - resumeAgentClient =<< asks smpAgent + asks smpAgent >>= resumeAgentClient unless subConns $ chatWriteVar subscriptionMode SMOnlyCreate users <- fromRight [] <$> runExceptT (withStoreCtx' (Just "startChatController, getUsers") getUsers) @@ -324,8 +323,8 @@ restoreCalls = do calls <- asks currentCalls atomically $ writeTVar calls callsMap -stopChatController :: forall m. MonadUnliftIO m => ChatController -> Bool -> m () -stopChatController ChatController {chatStore, smpAgent, agentAsync = s, sndFiles, rcvFiles, expireCIFlags} closeStore = do +stopChatController :: forall m. MonadUnliftIO m => ChatController -> m () +stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, expireCIFlags} = do disconnectAgentClient smpAgent readTVarIO s >>= mapM_ (\(a1, a2) -> uninterruptibleCancel a1 >> mapM_ uninterruptibleCancel a2) closeFiles sndFiles @@ -334,9 +333,6 @@ stopChatController ChatController {chatStore, smpAgent, agentAsync = s, sndFiles keys <- M.keys <$> readTVar expireCIFlags forM_ keys $ \k -> TM.insert k False expireCIFlags writeTVar s Nothing - when closeStore $ liftIO $ do - closeSQLiteStore chatStore - closeSQLiteStore $ agentClientStore smpAgent where closeFiles :: TVar (Map Int64 Handle) -> m () closeFiles files = do @@ -466,19 +462,12 @@ processChatCommand = \case checkDeleteChatUser user' withChatLock "deleteUser" . procCmd $ deleteChatUser user' delSMPQueues DeleteUser uName delSMPQueues viewPwd_ -> withUserName uName $ \userId -> APIDeleteUser userId delSMPQueues viewPwd_ - APIStartChat ChatCtrlCfg {subConns, enableExpireCIs, startXFTPWorkers, openDBWithKey} -> withUser' $ \_ -> + StartChat subConns enableExpireCIs startXFTPWorkers -> withUser' $ \_ -> asks agentAsync >>= readTVarIO >>= \case Just _ -> pure CRChatRunning - _ -> checkStoreNotChanged $ do - forM_ openDBWithKey $ \(DBEncryptionKey dbKey) -> do - ChatController {chatStore, smpAgent} <- ask - open chatStore dbKey - open (agentClientStore smpAgent) dbKey - startChatController subConns enableExpireCIs startXFTPWorkers $> CRChatStarted - where - open = handleDBError DBErrorOpen .: openSQLiteStore - APIStopChat closeStore -> do - ask >>= (`stopChatController` closeStore) + _ -> checkStoreNotChanged $ startChatController subConns enableExpireCIs startXFTPWorkers $> CRChatStarted + APIStopChat -> do + ask >>= stopChatController pure CRChatStopped APIActivateChat -> withUser $ \_ -> do restoreCalls @@ -5411,9 +5400,9 @@ chatCommandP = "/_delete user " *> (APIDeleteUser <$> A.decimal <* " del_smp=" <*> onOffP <*> optional (A.space *> jsonP)), "/delete user " *> (DeleteUser <$> displayName <*> pure True <*> optional (A.space *> pwdP)), ("/user" <|> "/u") $> ShowActiveUser, - "/_start" *> (APIStartChat <$> ((A.space *> jsonP) <|> chatCtrlCfgP)), - "/_stop close" $> APIStopChat {closeStore = True}, - "/_stop" $> APIStopChat False, + "/_start subscribe=" *> (StartChat <$> onOffP <* " expire=" <*> onOffP <* " xftp=" <*> onOffP), + "/_start" $> StartChat True True True, + "/_stop" $> APIStopChat, "/_app activate" $> APIActivateChat, "/_app suspend " *> (APISuspendChat <$> A.decimal), "/_resubscribe all" $> ResubscribeAllConnections, @@ -5641,12 +5630,6 @@ chatCommandP = ] where choice = A.choice . map (\p -> p <* A.takeWhile (== ' ') <* A.endOfInput) - chatCtrlCfgP = do - subConns <- (" subscribe=" *> onOffP) <|> pure True - enableExpireCIs <- (" expire=" *> onOffP) <|> pure True - startXFTPWorkers <- (" xftp=" *> onOffP) <|> pure True - openDBWithKey <- optional $ " key=" *> dbKeyP - pure ChatCtrlCfg {subConns, enableExpireCIs, startXFTPWorkers, openDBWithKey} incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index 8d1b99328..f8fa0d152 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -9,7 +9,6 @@ module Simplex.Chat.Archive importArchive, deleteStorage, sqlCipherExport, - handleDBError, ) where @@ -125,7 +124,7 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D checkFile `with` fs backup `with` fs (export chatDb chatEncrypted >> export agentDb agentEncrypted) - `catchChatError` \e -> tryChatError (restore `with` fs) >> throwError e + `catchChatError` \e -> (restore `with` fs) >> throwError e where action `with` StorageFiles {chatDb, agentDb} = action chatDb >> action agentDb backup f = copyFile f (f <> ".bak") @@ -140,7 +139,17 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D withDB (`SQL.exec` testSQL) DBErrorOpen atomically $ writeTVar dbEnc $ not (null key') where - withDB a err = handleDBError err $ bracket (SQL.open $ T.pack f) SQL.close a + withDB a err = + liftIO (bracket (SQL.open $ T.pack f) SQL.close a $> Nothing) + `catch` checkSQLError + `catch` (\(e :: SomeException) -> sqliteError' e) + >>= mapM_ (throwDBError . err) + where + checkSQLError e = case SQL.sqlError e of + SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase + _ -> sqliteError' e + sqliteError' :: Show e => e -> m (Maybe SQLiteError) + sqliteError' = pure . Just . SQLiteError . show exportSQL = T.unlines $ keySQL key @@ -157,16 +166,3 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D "SELECT count(*) FROM sqlite_master;" ] keySQL k = ["PRAGMA key = " <> sqlString k <> ";" | not (null k)] - -handleDBError :: forall m. ChatMonad m => (SQLiteError -> DatabaseError) -> IO () -> m () -handleDBError err a = - (liftIO a $> Nothing) - `catch` checkSQLError - `catch` (\(e :: SomeException) -> sqliteError' e) - >>= mapM_ (throwDBError . err) - where - checkSQLError e = case SQL.sqlError e of - SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase - _ -> sqliteError' e - sqliteError' :: Show e => e -> m (Maybe SQLiteError) - sqliteError' = pure . Just . SQLiteError . show diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3f3dae94f..122a4be3f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -221,8 +221,8 @@ data ChatCommand | UnmuteUser | APIDeleteUser UserId Bool (Maybe UserPwd) | DeleteUser UserName Bool (Maybe UserPwd) - | APIStartChat ChatCtrlCfg - | APIStopChat {closeStore :: Bool} + | StartChat {subscribeConnections :: Bool, enableExpireChatItems :: Bool, startXFTPWorkers :: Bool} + | APIStopChat | APIActivateChat | APISuspendChat {suspendTimeout :: Int} | ResubscribeAllConnections @@ -621,14 +621,6 @@ instance ToJSON ChatResponse where toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CR" toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CR" -data ChatCtrlCfg = ChatCtrlCfg - { subConns :: Bool, - enableExpireCIs :: Bool, - startXFTPWorkers :: Bool, - openDBWithKey :: Maybe DBEncryptionKey - } - deriving (Show, Generic, FromJSON) - newtype UserPwd = UserPwd {unUserPwd :: Text} deriving (Eq, Show) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 19d729bf7..01bdfba95 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1648,7 +1648,7 @@ viewChatError logLevel = \case DBErrorEncrypted -> ["error: chat database is already encrypted"] DBErrorPlaintext -> ["error: chat database is not encrypted"] DBErrorExport e -> ["error encrypting database: " <> sqliteError' e] - DBErrorOpen e -> ["error opening database: " <> sqliteError' e] + DBErrorOpen e -> ["error opening database after encryption: " <> sqliteError' e] e -> ["chat database error: " <> sShow e] ChatErrorAgent err entity_ -> case err of CMD PROHIBITED -> [withConnEntity <> "error: command is prohibited"] diff --git a/stack.yaml b/stack.yaml index a466178ce..0840970e4 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: fda1284ae4b7c33cae2eb8ed0376a511aecc1d51 + commit: 8d47f690838371bc848e4b31a4b09ef6bf67ccc5 - github: kazu-yamamoto/http2 commit: b5a1b7200cf5bc7044af34ba325284271f6dff25 # - ../direct-sqlcipher diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index d947fb63b..7da526325 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -171,7 +171,7 @@ startTestChat_ db cfg opts user = do stopTestChat :: TestCC -> IO () stopTestChat TestCC {chatController = cc, chatAsync, termAsync} = do - stopChatController cc False + stopChatController cc uninterruptibleCancel termAsync uninterruptibleCancel chatAsync threadDelay 200000 diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 445a5ab99..5c4d96cc9 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -992,17 +992,10 @@ testDatabaseEncryption tmp = do alice ##> "/_start" alice <## "chat started" testChatWorking alice bob - alice ##> "/_stop close" + alice ##> "/_stop" alice <## "chat stopped" alice ##> "/db key wrongkey nextkey" alice <## "error encrypting database: wrong passphrase or invalid database file" - alice ##> "/_start key=wrongkey" - alice <## "error opening database: wrong passphrase or invalid database file" - alice ##> "/_start key=mykey" - alice <## "chat started" - testChatWorking alice bob - alice ##> "/_stop close" - alice <## "chat stopped" alice ##> "/db key mykey nextkey" alice <## "ok" alice ##> "/_db encryption {\"currentKey\":\"nextkey\",\"newKey\":\"anotherkey\"}"