mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
core: agent background mode for iOS NSE (#3574)
* core: agent background mode for iOS NSE * change parameter for APIActivateChat * fix * update lib * update lib * simplexmq * simplify
This commit is contained in:
parent
23989aca57
commit
f93f68e425
10 changed files with 98 additions and 93 deletions
|
@ -21,10 +21,10 @@ struct SimpleXApp: App {
|
||||||
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
|
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// DispatchQueue.global(qos: .background).sync {
|
DispatchQueue.global(qos: .background).sync {
|
||||||
haskell_init()
|
haskell_init()
|
||||||
// hs_init(0, nil)
|
// hs_init(0, nil)
|
||||||
// }
|
}
|
||||||
UserDefaults.standard.register(defaults: appDefaults)
|
UserDefaults.standard.register(defaults: appDefaults)
|
||||||
setGroupDefaults()
|
setGroupDefaults()
|
||||||
registerGroupDefaults()
|
registerGroupDefaults()
|
||||||
|
|
|
@ -442,7 +442,7 @@ func startChat() -> DBMigrationResult? {
|
||||||
func doStartChat() -> DBMigrationResult? {
|
func doStartChat() -> DBMigrationResult? {
|
||||||
logger.debug("NotificationService: doStartChat")
|
logger.debug("NotificationService: doStartChat")
|
||||||
hs_init(0, nil)
|
hs_init(0, nil)
|
||||||
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation())
|
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
||||||
if dbStatus != .ok {
|
if dbStatus != .ok {
|
||||||
resetChatCtrl()
|
resetChatCtrl()
|
||||||
NSEChatState.shared.set(.created)
|
NSEChatState.shared.set(.created)
|
||||||
|
|
|
@ -17,7 +17,7 @@ public func getChatCtrl(_ useKey: String? = nil) -> chat_ctrl {
|
||||||
fatalError("chat controller not initialized")
|
fatalError("chat controller not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: MigrationConfirmation? = nil) -> (Bool, DBMigrationResult) {
|
public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: MigrationConfirmation? = nil, backgroundMode: Bool = false) -> (Bool, DBMigrationResult) {
|
||||||
if let res = migrationResult { return res }
|
if let res = migrationResult { return res }
|
||||||
let dbPath = getAppDatabasePath().path
|
let dbPath = getAppDatabasePath().path
|
||||||
var dbKey = ""
|
var dbKey = ""
|
||||||
|
@ -41,7 +41,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio
|
||||||
var cKey = dbKey.cString(using: .utf8)!
|
var cKey = dbKey.cString(using: .utf8)!
|
||||||
var cConfirm = confirm.rawValue.cString(using: .utf8)!
|
var cConfirm = confirm.rawValue.cString(using: .utf8)!
|
||||||
// the last parameter of chat_migrate_init is used to return the pointer to chat controller
|
// the last parameter of chat_migrate_init is used to return the pointer to chat controller
|
||||||
let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, &chatController)!
|
let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, backgroundMode ? 1 : 0, &chatController)!
|
||||||
let dbRes = dbMigrationResult(fromCString(cjson))
|
let dbRes = dbMigrationResult(fromCString(cjson))
|
||||||
let encrypted = dbKey != ""
|
let encrypted = dbKey != ""
|
||||||
let keychainErr = dbRes == .ok && useKeychain && encrypted && !kcDatabasePassword.set(dbKey)
|
let keychainErr = dbRes == .ok && useKeychain && encrypted && !kcDatabasePassword.set(dbKey)
|
||||||
|
|
|
@ -16,7 +16,7 @@ extern void hs_init(int argc, char **argv[]);
|
||||||
typedef void* chat_ctrl;
|
typedef void* chat_ctrl;
|
||||||
|
|
||||||
// the last parameter is used to return the pointer to chat controller
|
// the last parameter is used to return the pointer to chat controller
|
||||||
extern char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm, chat_ctrl *ctrl);
|
extern char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm, int backgroundMode, chat_ctrl *ctrl);
|
||||||
extern char *chat_close_store(chat_ctrl ctl);
|
extern char *chat_close_store(chat_ctrl ctl);
|
||||||
extern char *chat_reopen_store(chat_ctrl ctl);
|
extern char *chat_reopen_store(chat_ctrl ctl);
|
||||||
extern char *chat_send_cmd(chat_ctrl ctl, char *cmd);
|
extern char *chat_send_cmd(chat_ctrl ctl, char *cmd);
|
||||||
|
|
|
@ -14,7 +14,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: 13a60d1d3944aa175311563e661161e759b92563
|
tag: 9ea9b2c7356a9b42be8ab685c343076ff3c452fe
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."13a60d1d3944aa175311563e661161e759b92563" = "08mvqrbjfnq7c6mhkj4hhy4cxn0cj21n49lqzh67ani71g2g1xwa";
|
"https://github.com/simplex-chat/simplexmq.git"."9ea9b2c7356a9b42be8ab685c343076ff3c452fe" = "16jgsh5wnf8q56hlsdpa5xf2qhlrv80j8088xys0sbwfa4br2nk8";
|
||||||
"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";
|
||||||
|
|
|
@ -197,79 +197,84 @@ createChatDatabase filePrefix key keepKey confirmMigrations = runExceptT $ do
|
||||||
agentStore <- ExceptT $ createAgentStore (agentStoreFile filePrefix) key keepKey confirmMigrations
|
agentStore <- ExceptT $ createAgentStore (agentStoreFile filePrefix) key keepKey confirmMigrations
|
||||||
pure ChatDatabase {chatStore, agentStore}
|
pure ChatDatabase {chatStore, agentStore}
|
||||||
|
|
||||||
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> IO ChatController
|
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Bool -> IO ChatController
|
||||||
newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, tempDir, deviceNameForRemote} ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize} = do
|
newChatController
|
||||||
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
|
ChatDatabase {chatStore, agentStore}
|
||||||
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable}
|
user
|
||||||
firstTime = dbNew chatStore
|
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, tempDir, deviceNameForRemote}
|
||||||
currentUser <- newTVarIO user
|
ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize}
|
||||||
currentRemoteHost <- newTVarIO Nothing
|
backgroundMode = do
|
||||||
servers <- agentServers config
|
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
|
||||||
smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore
|
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable}
|
||||||
agentAsync <- newTVarIO Nothing
|
firstTime = dbNew chatStore
|
||||||
random <- liftIO C.newRandom
|
currentUser <- newTVarIO user
|
||||||
inputQ <- newTBQueueIO tbqSize
|
currentRemoteHost <- newTVarIO Nothing
|
||||||
outputQ <- newTBQueueIO tbqSize
|
servers <- agentServers config
|
||||||
connNetworkStatuses <- atomically TM.empty
|
smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore backgroundMode
|
||||||
subscriptionMode <- newTVarIO SMSubscribe
|
agentAsync <- newTVarIO Nothing
|
||||||
chatLock <- newEmptyTMVarIO
|
random <- liftIO C.newRandom
|
||||||
sndFiles <- newTVarIO M.empty
|
inputQ <- newTBQueueIO tbqSize
|
||||||
rcvFiles <- newTVarIO M.empty
|
outputQ <- newTBQueueIO tbqSize
|
||||||
currentCalls <- atomically TM.empty
|
connNetworkStatuses <- atomically TM.empty
|
||||||
localDeviceName <- newTVarIO $ fromMaybe deviceNameForRemote deviceName
|
subscriptionMode <- newTVarIO SMSubscribe
|
||||||
multicastSubscribers <- newTMVarIO 0
|
chatLock <- newEmptyTMVarIO
|
||||||
remoteSessionSeq <- newTVarIO 0
|
sndFiles <- newTVarIO M.empty
|
||||||
remoteHostSessions <- atomically TM.empty
|
rcvFiles <- newTVarIO M.empty
|
||||||
remoteHostsFolder <- newTVarIO Nothing
|
currentCalls <- atomically TM.empty
|
||||||
remoteCtrlSession <- newTVarIO Nothing
|
localDeviceName <- newTVarIO $ fromMaybe deviceNameForRemote deviceName
|
||||||
filesFolder <- newTVarIO optFilesFolder
|
multicastSubscribers <- newTMVarIO 0
|
||||||
chatStoreChanged <- newTVarIO False
|
remoteSessionSeq <- newTVarIO 0
|
||||||
expireCIThreads <- newTVarIO M.empty
|
remoteHostSessions <- atomically TM.empty
|
||||||
expireCIFlags <- newTVarIO M.empty
|
remoteHostsFolder <- newTVarIO Nothing
|
||||||
cleanupManagerAsync <- newTVarIO Nothing
|
remoteCtrlSession <- newTVarIO Nothing
|
||||||
timedItemThreads <- atomically TM.empty
|
filesFolder <- newTVarIO optFilesFolder
|
||||||
showLiveItems <- newTVarIO False
|
chatStoreChanged <- newTVarIO False
|
||||||
encryptLocalFiles <- newTVarIO False
|
expireCIThreads <- newTVarIO M.empty
|
||||||
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
expireCIFlags <- newTVarIO M.empty
|
||||||
tempDirectory <- newTVarIO tempDir
|
cleanupManagerAsync <- newTVarIO Nothing
|
||||||
contactMergeEnabled <- newTVarIO True
|
timedItemThreads <- atomically TM.empty
|
||||||
pure
|
showLiveItems <- newTVarIO False
|
||||||
ChatController
|
encryptLocalFiles <- newTVarIO False
|
||||||
{ firstTime,
|
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
||||||
currentUser,
|
tempDirectory <- newTVarIO tempDir
|
||||||
currentRemoteHost,
|
contactMergeEnabled <- newTVarIO True
|
||||||
smpAgent,
|
pure
|
||||||
agentAsync,
|
ChatController
|
||||||
chatStore,
|
{ firstTime,
|
||||||
chatStoreChanged,
|
currentUser,
|
||||||
random,
|
currentRemoteHost,
|
||||||
inputQ,
|
smpAgent,
|
||||||
outputQ,
|
agentAsync,
|
||||||
connNetworkStatuses,
|
chatStore,
|
||||||
subscriptionMode,
|
chatStoreChanged,
|
||||||
chatLock,
|
random,
|
||||||
sndFiles,
|
inputQ,
|
||||||
rcvFiles,
|
outputQ,
|
||||||
currentCalls,
|
connNetworkStatuses,
|
||||||
localDeviceName,
|
subscriptionMode,
|
||||||
multicastSubscribers,
|
chatLock,
|
||||||
remoteSessionSeq,
|
sndFiles,
|
||||||
remoteHostSessions,
|
rcvFiles,
|
||||||
remoteHostsFolder,
|
currentCalls,
|
||||||
remoteCtrlSession,
|
localDeviceName,
|
||||||
config,
|
multicastSubscribers,
|
||||||
filesFolder,
|
remoteSessionSeq,
|
||||||
expireCIThreads,
|
remoteHostSessions,
|
||||||
expireCIFlags,
|
remoteHostsFolder,
|
||||||
cleanupManagerAsync,
|
remoteCtrlSession,
|
||||||
timedItemThreads,
|
config,
|
||||||
showLiveItems,
|
filesFolder,
|
||||||
encryptLocalFiles,
|
expireCIThreads,
|
||||||
userXFTPFileConfig,
|
expireCIFlags,
|
||||||
tempDirectory,
|
cleanupManagerAsync,
|
||||||
logFilePath = logFile,
|
timedItemThreads,
|
||||||
contactMergeEnabled
|
showLiveItems,
|
||||||
}
|
encryptLocalFiles,
|
||||||
|
userXFTPFileConfig,
|
||||||
|
tempDirectory,
|
||||||
|
logFilePath = logFile,
|
||||||
|
contactMergeEnabled
|
||||||
|
}
|
||||||
where
|
where
|
||||||
configServers :: DefaultAgentServers
|
configServers :: DefaultAgentServers
|
||||||
configServers =
|
configServers =
|
||||||
|
|
|
@ -28,7 +28,7 @@ simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {core
|
||||||
exitFailure
|
exitFailure
|
||||||
run db@ChatDatabase {chatStore} = do
|
run db@ChatDatabase {chatStore} = do
|
||||||
u <- getCreateActiveUser chatStore testView
|
u <- getCreateActiveUser chatStore testView
|
||||||
cc <- newChatController db (Just u) cfg opts
|
cc <- newChatController db (Just u) cfg opts False
|
||||||
runSimplexChat opts u cc chat
|
runSimplexChat opts u cc chat
|
||||||
|
|
||||||
runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO ()
|
runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO ()
|
||||||
|
|
|
@ -72,7 +72,7 @@ $(JQ.deriveToJSON defaultJSON ''APIResponse)
|
||||||
|
|
||||||
foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
||||||
|
|
||||||
foreign export ccall "chat_migrate_init_key" cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
foreign export ccall "chat_migrate_init_key" cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> CInt -> Ptr (StablePtr ChatController) -> IO CJSONString
|
||||||
|
|
||||||
foreign export ccall "chat_close_store" cChatCloseStore :: StablePtr ChatController -> IO CString
|
foreign export ccall "chat_close_store" cChatCloseStore :: StablePtr ChatController -> IO CString
|
||||||
|
|
||||||
|
@ -108,10 +108,10 @@ foreign export ccall "chat_decrypt_file" cChatDecryptFile :: CString -> CString
|
||||||
|
|
||||||
-- | check / migrate database and initialize chat controller on success
|
-- | check / migrate database and initialize chat controller on success
|
||||||
cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
||||||
cChatMigrateInit fp key = cChatMigrateInitKey fp key 0
|
cChatMigrateInit fp key conf = cChatMigrateInitKey fp key 0 conf 0
|
||||||
|
|
||||||
cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
|
cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> CInt -> Ptr (StablePtr ChatController) -> IO CJSONString
|
||||||
cChatMigrateInitKey fp key keepKey conf 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
|
||||||
-- US-ASCII all the time.
|
-- US-ASCII all the time.
|
||||||
setLocaleEncoding utf8
|
setLocaleEncoding utf8
|
||||||
|
@ -122,7 +122,7 @@ cChatMigrateInitKey fp key keepKey conf ctrl = do
|
||||||
dbKey <- BA.convert <$> B.packCString key
|
dbKey <- BA.convert <$> B.packCString key
|
||||||
confirm <- peekCAString conf
|
confirm <- peekCAString conf
|
||||||
r <-
|
r <-
|
||||||
chatMigrateInitKey dbPath dbKey (keepKey /= 0) confirm >>= \case
|
chatMigrateInitKey dbPath dbKey (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
|
||||||
|
@ -220,10 +220,10 @@ getActiveUser_ :: SQLiteStore -> IO (Maybe User)
|
||||||
getActiveUser_ st = find activeUser <$> withTransaction st getUsers
|
getActiveUser_ st = find activeUser <$> withTransaction st getUsers
|
||||||
|
|
||||||
chatMigrateInit :: String -> ScrubbedBytes -> String -> IO (Either DBMigrationResult ChatController)
|
chatMigrateInit :: String -> ScrubbedBytes -> String -> IO (Either DBMigrationResult ChatController)
|
||||||
chatMigrateInit dbFilePrefix dbKey = chatMigrateInitKey dbFilePrefix dbKey False
|
chatMigrateInit dbFilePrefix dbKey confirm = chatMigrateInitKey dbFilePrefix dbKey False confirm False
|
||||||
|
|
||||||
chatMigrateInitKey :: String -> ScrubbedBytes -> Bool -> String -> IO (Either DBMigrationResult ChatController)
|
chatMigrateInitKey :: String -> ScrubbedBytes -> Bool -> String -> Bool -> IO (Either DBMigrationResult ChatController)
|
||||||
chatMigrateInitKey dbFilePrefix dbKey keepKey confirm = runExceptT $ do
|
chatMigrateInitKey dbFilePrefix dbKey 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 (chatStoreFile dbFilePrefix) confirmMigrations
|
||||||
agentStore <- migrate createAgentStore (agentStoreFile dbFilePrefix) confirmMigrations
|
agentStore <- migrate createAgentStore (agentStoreFile dbFilePrefix) confirmMigrations
|
||||||
|
@ -231,7 +231,7 @@ chatMigrateInitKey dbFilePrefix dbKey keepKey confirm = runExceptT $ do
|
||||||
where
|
where
|
||||||
initialize st db = do
|
initialize st db = do
|
||||||
user_ <- getActiveUser_ st
|
user_ <- getActiveUser_ st
|
||||||
newChatController db user_ defaultMobileConfig (mobileChatOpts dbFilePrefix)
|
newChatController db user_ defaultMobileConfig (mobileChatOpts dbFilePrefix) backgroundMode
|
||||||
migrate createStore dbFile confirmMigrations =
|
migrate createStore dbFile confirmMigrations =
|
||||||
ExceptT $
|
ExceptT $
|
||||||
(first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations)
|
(first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations)
|
||||||
|
|
|
@ -175,7 +175,7 @@ startTestChat_ :: ChatDatabase -> ChatConfig -> ChatOpts -> User -> IO TestCC
|
||||||
startTestChat_ db cfg opts user = do
|
startTestChat_ db cfg opts user = do
|
||||||
t <- withVirtualTerminal termSettings pure
|
t <- withVirtualTerminal termSettings pure
|
||||||
ct <- newChatTerminal t opts
|
ct <- newChatTerminal t opts
|
||||||
cc <- newChatController db (Just user) cfg opts
|
cc <- newChatController db (Just user) cfg opts False
|
||||||
chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
|
chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
|
||||||
atomically . unless (maintenance opts) $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
|
atomically . unless (maintenance opts) $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
|
||||||
termQ <- newTQueueIO
|
termQ <- newTQueueIO
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue