mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 02:05:40 +00:00
Merge branch 'master' into master-ghc8107
This commit is contained in:
+2
-2
@@ -6066,8 +6066,9 @@ chatCommandP =
|
||||
("/help groups" <|> "/help group" <|> "/hg") $> ChatHelp HSGroups,
|
||||
("/help contacts" <|> "/help contact" <|> "/hc") $> ChatHelp HSContacts,
|
||||
("/help address" <|> "/ha") $> ChatHelp HSMyAddress,
|
||||
"/help incognito" $> ChatHelp HSIncognito,
|
||||
("/help incognito" <|> "/hi") $> ChatHelp HSIncognito,
|
||||
("/help messages" <|> "/hm") $> ChatHelp HSMessages,
|
||||
("/help remote" <|> "/hr") $> ChatHelp HSRemote,
|
||||
("/help settings" <|> "/hs") $> ChatHelp HSSettings,
|
||||
("/help db" <|> "/hd") $> ChatHelp HSDatabase,
|
||||
("/help" <|> "/h") $> ChatHelp HSMain,
|
||||
@@ -6174,7 +6175,6 @@ chatCommandP =
|
||||
"/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))),
|
||||
("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito,
|
||||
"/set device name " *> (SetLocalDeviceName <$> textP),
|
||||
-- "/create remote host" $> CreateRemoteHost,
|
||||
"/list remote hosts" $> ListRemoteHosts,
|
||||
"/switch remote host " *> (SwitchRemoteHost <$> ("local" $> Nothing <|> (Just <$> A.decimal))),
|
||||
"/start remote host " *> (StartRemoteHost <$> ("new" $> Nothing <|> (Just <$> ((,) <$> A.decimal <*> (" multicast=" *> onOffP <|> pure False))))),
|
||||
|
||||
@@ -203,7 +203,7 @@ data ChatController = ChatController
|
||||
contactMergeEnabled :: TVar Bool
|
||||
}
|
||||
|
||||
data HelpSection = HSMain | HSFiles | HSGroups | HSContacts | HSMyAddress | HSIncognito | HSMarkdown | HSMessages | HSSettings | HSDatabase
|
||||
data HelpSection = HSMain | HSFiles | HSGroups | HSContacts | HSMyAddress | HSIncognito | HSMarkdown | HSMessages | HSRemote | HSSettings | HSDatabase
|
||||
deriving (Show)
|
||||
|
||||
data ChatCommand
|
||||
@@ -662,14 +662,14 @@ data ChatResponse
|
||||
| CRRemoteHostSessionCode {remoteHost_ :: Maybe RemoteHostInfo, sessionCode :: Text}
|
||||
| CRNewRemoteHost {remoteHost :: RemoteHostInfo}
|
||||
| CRRemoteHostConnected {remoteHost :: RemoteHostInfo}
|
||||
| CRRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId}
|
||||
| CRRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId, rhsState :: RemoteHostSessionState, rhStopReason :: RemoteHostStopReason}
|
||||
| CRRemoteFileStored {remoteHostId :: RemoteHostId, remoteFileSource :: CryptoFile}
|
||||
| CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]}
|
||||
| CRRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo, ctrlAppInfo_ :: Maybe CtrlAppInfo, appVersion :: AppVersion, compatible :: Bool}
|
||||
| CRRemoteCtrlConnecting {remoteCtrl_ :: Maybe RemoteCtrlInfo, ctrlAppInfo :: CtrlAppInfo, appVersion :: AppVersion}
|
||||
| CRRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text}
|
||||
| CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo}
|
||||
| CRRemoteCtrlStopped
|
||||
| CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason}
|
||||
| CRSQLResult {rows :: [Text]}
|
||||
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
|
||||
| CRDebugLocks {chatLockName :: Maybe String, agentLocks :: AgentLocks}
|
||||
@@ -700,14 +700,14 @@ allowRemoteEvent = \case
|
||||
CRRemoteHostSessionCode {} -> False
|
||||
CRNewRemoteHost _ -> False
|
||||
CRRemoteHostConnected _ -> False
|
||||
CRRemoteHostStopped _ -> False
|
||||
CRRemoteHostStopped {} -> False
|
||||
CRRemoteFileStored {} -> False
|
||||
CRRemoteCtrlList _ -> False
|
||||
CRRemoteCtrlFound {} -> False
|
||||
CRRemoteCtrlConnecting {} -> False
|
||||
CRRemoteCtrlSessionCode {} -> False
|
||||
CRRemoteCtrlConnected _ -> False
|
||||
CRRemoteCtrlStopped -> False
|
||||
CRRemoteCtrlStopped {} -> False
|
||||
CRSQLResult _ -> False
|
||||
CRSlowSQLQueries {} -> False
|
||||
_ -> True
|
||||
@@ -1083,6 +1083,12 @@ data RemoteHostError
|
||||
| RHEProtocolError RemoteProtocolError
|
||||
deriving (Show, Exception)
|
||||
|
||||
data RemoteHostStopReason
|
||||
= RHSRConnectionFailed ChatError
|
||||
| RHSRCrashed ChatError
|
||||
| RHSRDisconnected
|
||||
deriving (Show, Exception)
|
||||
|
||||
-- TODO review errors, some of it can be covered by HTTP2 errors
|
||||
data RemoteCtrlError
|
||||
= RCEInactive -- ^ No session is running
|
||||
@@ -1098,6 +1104,13 @@ data RemoteCtrlError
|
||||
| RCEProtocolError {protocolError :: RemoteProtocolError}
|
||||
deriving (Show, Exception)
|
||||
|
||||
data RemoteCtrlStopReason
|
||||
= RCSRDiscoveryFailed ChatError
|
||||
| RCSRConnectionFailed ChatError
|
||||
| RCSRSetupFailed ChatError
|
||||
| RCSRDisconnected
|
||||
deriving (Show, Exception)
|
||||
|
||||
data ArchiveError
|
||||
= AEImport {chatError :: ChatError}
|
||||
| AEImportFile {file :: String, chatError :: ChatError}
|
||||
@@ -1323,6 +1336,10 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RCS") ''RemoteCtrlSessionState)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''RemoteCtrlInfo)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RCSR") ''RemoteCtrlStopReason)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHSR") ''RemoteHostStopReason)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CR") ''ChatResponse)
|
||||
|
||||
$(JQ.deriveFromJSON defaultJSON ''ArchiveConfig)
|
||||
|
||||
@@ -10,6 +10,7 @@ module Simplex.Chat.Help
|
||||
myAddressHelpInfo,
|
||||
incognitoHelpInfo,
|
||||
messagesHelpInfo,
|
||||
remoteHelpInfo,
|
||||
markdownInfo,
|
||||
settingsInfo,
|
||||
databaseHelpInfo,
|
||||
@@ -87,7 +88,7 @@ chatHelpInfo =
|
||||
green "Create your address: " <> highlight "/address",
|
||||
"",
|
||||
green "Other commands:",
|
||||
indent <> highlight "/help <topic> " <> " - help on: " <> listHighlight ["groups", "contacts", "messages", "files", "address", "settings", "db"],
|
||||
indent <> highlight "/help <topic> " <> " - help on: " <> listHighlight ["groups", "contacts", "messages", "files", "address", "incognito", "remote", "settings", "db"],
|
||||
indent <> highlight "/profile " <> " - show / update user profile",
|
||||
indent <> highlight "/delete <contact>" <> " - delete contact and all messages with them",
|
||||
indent <> highlight "/chats " <> " - most recent chats",
|
||||
@@ -272,6 +273,34 @@ messagesHelpInfo =
|
||||
indent <> highlight "! #team (hi) <new msg> " <> " - to edit your message in the group #team"
|
||||
]
|
||||
|
||||
remoteHelpInfo :: [StyledString]
|
||||
remoteHelpInfo =
|
||||
map
|
||||
styleMarkdown
|
||||
[ green "Remote control",
|
||||
"You can use CLI as a remote controller for a mobile app or as a remote host for a desktop app (or another CLI).",
|
||||
"For example, you can run CLI on a server and use it from a desktop computer, connecting via SSH port forwarding.",
|
||||
"",
|
||||
indent <> highlight "/set device name <name> " <> " - set CLI name for remote connections",
|
||||
"",
|
||||
green "Using as remote controller",
|
||||
indent <> highlight "/start remote host new " <> " - pair and connect a new remote host",
|
||||
indent <> highlight "/start remote host <id> [multicast=on] " <> " - start connection with a known (paired) remote host",
|
||||
indent <> highlight "/stop remote host new " <> " - cancel pairing with a new remote host",
|
||||
indent <> highlight "/stop remote host <id> " <> " - stop connection with connected remote host",
|
||||
indent <> highlight "/switch remote host local " <> " - switch to using local database",
|
||||
indent <> highlight "/switch remote host <id> " <> " - switch to connected remote host",
|
||||
indent <> highlight "/list remote hosts " <> " - list known remote hosts",
|
||||
indent <> highlight "/delete remote host <id> " <> " - delete (unpair) known remote hosts - also deletes all host files from controller",
|
||||
"",
|
||||
green "Using as remote host",
|
||||
indent <> highlight "/connect remote ctrl <session_address> " <> " - connect to remote controller via the adress from /start remote host",
|
||||
indent <> highlight "/stop remote ctrl " <> " - stop connection with remote controller",
|
||||
indent <> highlight "/find remote ctrl " <> " - find known remote controller on the local network (it should be started with multicast=on)",
|
||||
indent <> highlight "/list remote ctrls " <> " - list known remote controllers",
|
||||
indent <> highlight "/delete remote ctrl <id> " <> " - delete known remote controller"
|
||||
]
|
||||
|
||||
markdownInfo :: [StyledString]
|
||||
markdownInfo =
|
||||
map
|
||||
|
||||
+24
-22
@@ -169,12 +169,12 @@ startRemoteHost rh_ = do
|
||||
handleConnectError :: ChatMonad m => RHKey -> SessionSeq -> m a -> m a
|
||||
handleConnectError rhKey sessSeq action = action `catchChatError` \err -> do
|
||||
logError $ "startRemoteHost.rcConnectHost crashed: " <> tshow err
|
||||
cancelRemoteHostSession (Just sessSeq) rhKey
|
||||
cancelRemoteHostSession (Just (sessSeq, RHSRConnectionFailed err)) rhKey
|
||||
throwError err
|
||||
handleHostError :: ChatMonad m => SessionSeq -> TVar RHKey -> m () -> m ()
|
||||
handleHostError sessSeq rhKeyVar action = action `catchChatError` \err -> do
|
||||
logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err
|
||||
readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just sessSeq)
|
||||
readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just (sessSeq, RHSRCrashed err))
|
||||
waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> SessionSeq -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m ()
|
||||
waitForHostSession remoteHost_ rhKey sseq rhKeyVar vars = do
|
||||
(sessId, tls, vars') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars
|
||||
@@ -220,7 +220,7 @@ startRemoteHost rh_ = do
|
||||
onDisconnected :: ChatMonad m => RHKey -> SessionSeq -> m ()
|
||||
onDisconnected rhKey sseq = do
|
||||
logDebug $ "HTTP2 client disconnected: " <> tshow (rhKey, sseq)
|
||||
cancelRemoteHostSession (Just sseq) rhKey
|
||||
cancelRemoteHostSession (Just (sseq, RHSRDisconnected)) rhKey
|
||||
pollEvents :: ChatMonad m => RemoteHostId -> RemoteHostClient -> m ()
|
||||
pollEvents rhId rhClient = do
|
||||
oq <- asks outputQ
|
||||
@@ -246,24 +246,25 @@ closeRemoteHost rhKey = do
|
||||
logNote $ "Closing remote host session for " <> tshow rhKey
|
||||
cancelRemoteHostSession Nothing rhKey
|
||||
|
||||
cancelRemoteHostSession :: ChatMonad m => Maybe SessionSeq -> RHKey -> m ()
|
||||
cancelRemoteHostSession sseq_ rhKey = do
|
||||
cancelRemoteHostSession :: ChatMonad m => Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> m ()
|
||||
cancelRemoteHostSession handlerInfo_ rhKey = do
|
||||
sessions <- asks remoteHostSessions
|
||||
crh <- asks currentRemoteHost
|
||||
deregistered <- atomically $
|
||||
TM.lookup rhKey sessions >>= \case
|
||||
Nothing -> pure Nothing
|
||||
Just (sessSeq, _) | maybe False (/= sessSeq) sseq_ -> pure Nothing -- ignore cancel from a ghost session handler
|
||||
Just (sessSeq, _) | maybe False (/= sessSeq) (fst <$> handlerInfo_) -> pure Nothing -- ignore cancel from a ghost session handler
|
||||
Just (_, rhs) -> do
|
||||
TM.delete rhKey sessions
|
||||
modifyTVar' crh $ \cur -> if (RHId <$> cur) == Just rhKey then Nothing else cur -- only wipe the closing RH
|
||||
pure $ Just rhs
|
||||
forM_ deregistered $ \session -> do
|
||||
liftIO $ cancelRemoteHost handlingError session `catchAny` (logError . tshow)
|
||||
when handlingError $ toView $ CRRemoteHostStopped rhId_
|
||||
forM_ (snd <$> handlerInfo_) $ \rhStopReason ->
|
||||
toView $ CRRemoteHostStopped {remoteHostId_, rhsState = rhsSessionState session, rhStopReason}
|
||||
where
|
||||
handlingError = isJust sseq_
|
||||
rhId_ = case rhKey of
|
||||
handlingError = isJust handlerInfo_
|
||||
remoteHostId_ = case rhKey of
|
||||
RHNew -> Nothing
|
||||
RHId rhId -> Just rhId
|
||||
|
||||
@@ -395,7 +396,7 @@ findKnownRemoteCtrl = do
|
||||
sseq <- startRemoteCtrlSession
|
||||
foundCtrl <- newEmptyTMVarIO
|
||||
cmdOk <- newEmptyTMVarIO
|
||||
action <- async $ handleCtrlError sseq "findKnownRemoteCtrl.discover" $ do
|
||||
action <- async $ handleCtrlError sseq RCSRDiscoveryFailed "findKnownRemoteCtrl.discover" $ do
|
||||
atomically $ takeTMVar cmdOk
|
||||
(RCCtrlPairing {ctrlFingerprint}, inv@(RCVerifiedInvitation RCInvitation {app})) <-
|
||||
timeoutThrow (ChatErrorRemoteCtrl RCETimeout) discoveryTimeout . withAgent $ \a -> rcDiscoverCtrl a pairings
|
||||
@@ -441,7 +442,7 @@ startRemoteCtrlSession = do
|
||||
Right sseq <$ writeTVar session (Just (sseq, RCSessionStarting))
|
||||
|
||||
connectRemoteCtrl :: ChatMonad m => RCVerifiedInvitation -> SessionSeq -> m (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
||||
connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq "connectRemoteCtrl" $ do
|
||||
connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq RCSRConnectionFailed "connectRemoteCtrl" $ do
|
||||
ctrlInfo@CtrlAppInfo {deviceName = ctrlDeviceName} <- parseCtrlAppInfo app
|
||||
v <- checkAppVersion ctrlInfo
|
||||
rc_ <- withStore' $ \db -> getRemoteCtrlByFingerprint db ca
|
||||
@@ -452,7 +453,7 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app})
|
||||
cmdOk <- newEmptyTMVarIO
|
||||
rcsWaitSession <- async $ do
|
||||
atomically $ takeTMVar cmdOk
|
||||
handleCtrlError sseq "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlDeviceName rcsClient vars
|
||||
handleCtrlError sseq RCSRConnectionFailed "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlDeviceName rcsClient vars
|
||||
updateRemoteCtrlSession sseq $ \case
|
||||
RCSessionStarting -> Right RCSessionConnecting {remoteCtrlId_ = remoteCtrlId' <$> rc_, rcsClient, rcsWaitSession}
|
||||
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
||||
@@ -602,7 +603,7 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do
|
||||
Nothing -> throwError $ ChatErrorRemoteCtrl RCEInactive
|
||||
Just (sseq, RCSessionPendingConfirmation {rcsClient, ctrlDeviceName = ctrlName, sessionCode, rcsWaitConfirmation}) -> pure (sseq, rcsClient, ctrlName, sessionCode, rcsWaitConfirmation)
|
||||
_ -> throwError $ ChatErrorRemoteCtrl RCEBadState
|
||||
handleCtrlError sseq "verifyRemoteCtrlSession" $ do
|
||||
handleCtrlError sseq RCSRSetupFailed "verifyRemoteCtrlSession" $ do
|
||||
let verified = sameVerificationCode sessCode' sessionCode
|
||||
timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout . liftIO $ confirmCtrlSession client verified -- signal verification result before crashing
|
||||
unless verified $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError PRESessionCode
|
||||
@@ -630,31 +631,32 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do
|
||||
monitor sseq server = do
|
||||
res <- waitCatch server
|
||||
logInfo $ "HTTP2 server stopped: " <> tshow res
|
||||
cancelActiveRemoteCtrl (Just sseq)
|
||||
cancelActiveRemoteCtrl $ Just (sseq, RCSRDisconnected)
|
||||
|
||||
stopRemoteCtrl :: ChatMonad m => m ()
|
||||
stopRemoteCtrl = cancelActiveRemoteCtrl Nothing
|
||||
|
||||
handleCtrlError :: ChatMonad m => SessionSeq -> Text -> m a -> m a
|
||||
handleCtrlError sseq name action =
|
||||
handleCtrlError :: ChatMonad m => SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> m a -> m a
|
||||
handleCtrlError sseq mkReason name action =
|
||||
action `catchChatError` \e -> do
|
||||
logError $ name <> " remote ctrl error: " <> tshow e
|
||||
cancelActiveRemoteCtrl (Just sseq)
|
||||
cancelActiveRemoteCtrl $ Just (sseq, mkReason e)
|
||||
throwError e
|
||||
|
||||
-- | Stop session controller, unless session update key is present but stale
|
||||
cancelActiveRemoteCtrl :: ChatMonad m => Maybe SessionSeq -> m ()
|
||||
cancelActiveRemoteCtrl sseq_ = handleAny (logError . tshow) $ do
|
||||
cancelActiveRemoteCtrl :: ChatMonad m => Maybe (SessionSeq, RemoteCtrlStopReason) -> m ()
|
||||
cancelActiveRemoteCtrl handlerInfo_ = handleAny (logError . tshow) $ do
|
||||
var <- asks remoteCtrlSession
|
||||
session_ <- atomically $ readTVar var >>= \case
|
||||
Nothing -> pure Nothing
|
||||
Just (oldSeq, _) | maybe False (/= oldSeq) sseq_ -> pure Nothing
|
||||
Just (oldSeq, _) | maybe False (/= oldSeq) (fst <$> handlerInfo_) -> pure Nothing
|
||||
Just (_, s) -> Just s <$ writeTVar var Nothing
|
||||
forM_ session_ $ \session -> do
|
||||
liftIO $ cancelRemoteCtrl handlingError session
|
||||
when handlingError $ toView CRRemoteCtrlStopped
|
||||
forM_ (snd <$> handlerInfo_) $ \rcStopReason ->
|
||||
toView CRRemoteCtrlStopped {rcsState = rcsSessionState session, rcStopReason}
|
||||
where
|
||||
handlingError = isJust sseq_
|
||||
handlingError = isJust handlerInfo_
|
||||
|
||||
cancelRemoteCtrl :: Bool -> RemoteCtrlSession -> IO ()
|
||||
cancelRemoteCtrl handlingError = \case
|
||||
|
||||
@@ -58,13 +58,15 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
|
||||
rh' = if either (const False) allowRemoteCommand cmd then rh else Nothing
|
||||
unless (isMessage cmd) $ echo s
|
||||
r <- runReaderT (execChatCommand rh' bs) cc
|
||||
processResp s cmd r
|
||||
processResp s cmd rh r
|
||||
printRespToTerminal ct cc False rh r
|
||||
startLiveMessage cmd r
|
||||
where
|
||||
echo s = printToTerminal ct [plain s]
|
||||
processResp s cmd = \case
|
||||
CRActiveUser _ -> setActive ct ""
|
||||
processResp s cmd rh = \case
|
||||
CRActiveUser u -> case rh of
|
||||
Nothing -> setActive ct ""
|
||||
Just rhId -> updateRemoteUser ct u rhId
|
||||
CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_
|
||||
CRNewChatItem u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
module Simplex.Chat.Terminal.Output where
|
||||
|
||||
import Control.Concurrent (ThreadId)
|
||||
import Control.Logger.Simple
|
||||
import Control.Monad
|
||||
import Control.Monad.Catch (MonadMask)
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.Reader
|
||||
@@ -18,21 +20,23 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Data.Time.LocalTime (getCurrentTimeZone)
|
||||
import Simplex.Chat (processChatCommand)
|
||||
import Simplex.Chat (execChatCommand, processChatCommand)
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Markdown
|
||||
import Simplex.Chat.Messages
|
||||
import Simplex.Chat.Messages.CIContent (CIContent(..), SMsgDirection (..))
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Protocol (MsgContent (..), msgContentText)
|
||||
import Simplex.Chat.Remote.Types (RemoteHostId)
|
||||
import Simplex.Chat.Remote.Types (RHKey (..), RemoteHostId, RemoteHostInfo (..), RemoteHostSession (..))
|
||||
import Simplex.Chat.Styled
|
||||
import Simplex.Chat.Terminal.Notification (Notification (..), initializeNotifications)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.View
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
|
||||
import System.Console.ANSI.Types
|
||||
import System.IO (IOMode (..), hPutStrLn, withFile)
|
||||
import System.Mem.Weak (Weak)
|
||||
@@ -48,7 +52,8 @@ data ChatTerminal = ChatTerminal
|
||||
nextMessageRow :: TVar Int,
|
||||
termLock :: TMVar (),
|
||||
sendNotification :: Maybe (Notification -> IO ()),
|
||||
activeTo :: TVar String
|
||||
activeTo :: TVar String,
|
||||
currentRemoteUsers :: TMap RemoteHostId User
|
||||
}
|
||||
|
||||
data TerminalState = TerminalState
|
||||
@@ -103,6 +108,7 @@ newChatTerminal t opts = do
|
||||
nextMessageRow <- newTVarIO lastRow
|
||||
sendNotification <- if muteNotifications opts then pure Nothing else Just <$> initializeNotifications
|
||||
activeTo <- newTVarIO ""
|
||||
currentRemoteUsers <- newTVarIO mempty
|
||||
-- threadDelay 500000 -- this delay is the same as timeout in getTerminalSize
|
||||
pure
|
||||
ChatTerminal
|
||||
@@ -113,7 +119,8 @@ newChatTerminal t opts = do
|
||||
nextMessageRow,
|
||||
termLock,
|
||||
sendNotification,
|
||||
activeTo
|
||||
activeTo,
|
||||
currentRemoteUsers
|
||||
}
|
||||
|
||||
mkTermState :: TerminalState
|
||||
@@ -142,12 +149,14 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} = d
|
||||
case r of
|
||||
CRNewChatItem u ci -> markChatItemRead u ci
|
||||
CRChatItemUpdated u ci -> markChatItemRead u ci
|
||||
CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId
|
||||
CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_
|
||||
_ -> pure ()
|
||||
let printResp = case logFilePath of
|
||||
Just path -> if logResponseToFile r then logResponse path else printToTerminal ct
|
||||
_ -> printToTerminal ct
|
||||
liveItems <- readTVarIO showLiveItems
|
||||
responseString cc liveItems outputRH r >>= printResp
|
||||
responseString ct cc liveItems outputRH r >>= printResp
|
||||
responseNotification ct cc r
|
||||
where
|
||||
markChatItemRead u (AChatItem _ _ chat ci@ChatItem {chatDir, meta = CIMeta {itemStatus}}) =
|
||||
@@ -158,6 +167,10 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} = d
|
||||
void $ runReaderT (runExceptT $ processChatCommand (APIChatRead chatRef (Just (itemId, itemId)))) cc
|
||||
_ -> pure ()
|
||||
logResponse path s = withFile path AppendMode $ \h -> mapM_ (hPutStrLn h . unStyle) s
|
||||
getRemoteUser rhId = runReaderT (execChatCommand (Just rhId) "/user") cc >>= \case
|
||||
CRActiveUser {user} -> updateRemoteUser ct user rhId
|
||||
cr -> logError $ "Unexpected reply while getting remote user: " <> tshow cr
|
||||
removeRemoteUser rhId = atomically $ TM.delete rhId (currentRemoteUsers ct)
|
||||
|
||||
responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO ()
|
||||
responseNotification t@ChatTerminal {sendNotification} cc = \case
|
||||
@@ -254,15 +267,29 @@ whenCurrUser cc u a = do
|
||||
sameUser User {userId = uId} = maybe False $ \User {userId} -> userId == uId
|
||||
|
||||
printRespToTerminal :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO ()
|
||||
printRespToTerminal ct cc liveItems outputRH r = responseString cc liveItems outputRH r >>= printToTerminal ct
|
||||
printRespToTerminal ct cc liveItems outputRH r = responseString ct cc liveItems outputRH r >>= printToTerminal ct
|
||||
|
||||
responseString :: ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO [StyledString]
|
||||
responseString cc liveItems outputRH r = do
|
||||
currentRH <- readTVarIO $ currentRemoteHost cc
|
||||
user <- readTVarIO $ currentUser cc -- XXX: local user, should be subsumed by remote when connected
|
||||
responseString :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO [StyledString]
|
||||
responseString ct cc liveItems outputRH r = do
|
||||
cu <- getCurrentUser ct cc
|
||||
ts <- getCurrentTime
|
||||
tz <- getCurrentTimeZone
|
||||
pure $ responseToView (currentRH, user) (config cc) liveItems ts tz outputRH r
|
||||
pure $ responseToView cu (config cc) liveItems ts tz outputRH r
|
||||
|
||||
updateRemoteUser :: ChatTerminal -> User -> RemoteHostId -> IO ()
|
||||
updateRemoteUser ct user rhId = atomically $ TM.insert rhId user (currentRemoteUsers ct)
|
||||
|
||||
getCurrentUser :: ChatTerminal -> ChatController -> IO (Maybe RemoteHostId, Maybe User)
|
||||
getCurrentUser ct cc = atomically $ do
|
||||
localUser_ <- readTVar (currentUser cc)
|
||||
readTVar (currentRemoteHost cc) >>= \case
|
||||
Nothing -> pure (Nothing, localUser_)
|
||||
Just rhId ->
|
||||
TM.lookup (RHId rhId) (remoteHostSessions cc) >>= \case
|
||||
Just (_, RHSessionConnected {}) -> do
|
||||
hostUser_ <- TM.lookup rhId (currentRemoteUsers ct)
|
||||
pure (Just rhId, hostUser_)
|
||||
_ -> pure (Nothing, localUser_)
|
||||
|
||||
printToTerminal :: ChatTerminal -> [StyledString] -> IO ()
|
||||
printToTerminal ct s =
|
||||
|
||||
@@ -135,6 +135,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
HSIncognito -> incognitoHelpInfo
|
||||
HSMessages -> messagesHelpInfo
|
||||
HSMarkdown -> markdownInfo
|
||||
HSRemote -> remoteHelpInfo
|
||||
HSSettings -> settingsInfo
|
||||
HSDatabase -> databaseHelpInfo
|
||||
CRWelcome user -> chatWelcome user
|
||||
@@ -298,8 +299,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
]
|
||||
CRNewRemoteHost RemoteHostInfo {remoteHostId = rhId, hostDeviceName} -> ["new remote host " <> sShow rhId <> " added: " <> plain hostDeviceName]
|
||||
CRRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} -> ["remote host " <> sShow rhId <> " connected"]
|
||||
CRRemoteHostStopped rhId_ ->
|
||||
[ maybe "new remote host" (mappend "remote host " . sShow) rhId_ <> " stopped"
|
||||
CRRemoteHostStopped {remoteHostId_} ->
|
||||
[ maybe "new remote host" (mappend "remote host " . sShow) remoteHostId_ <> " stopped"
|
||||
]
|
||||
CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) ->
|
||||
[plain $ "file " <> filePath <> " stored on remote host " <> show rhId]
|
||||
@@ -309,8 +310,9 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
[ "remote controller " <> sShow remoteCtrlId <> " found: "
|
||||
<> maybe (deviceName <> "not compatible") (\info -> viewRemoteCtrl info appVersion compatible) ctrlAppInfo_
|
||||
]
|
||||
<> [ "use " <> highlight ("/confirm remote ctrl " <> show remoteCtrlId) <> " to connect" | isJust ctrlAppInfo_ && compatible]
|
||||
where
|
||||
deviceName = if T.null ctrlDeviceName then "" else plain ctrlDeviceName <> ", "
|
||||
deviceName = if T.null ctrlDeviceName then "" else plain ctrlDeviceName <> ", "
|
||||
CRRemoteCtrlConnecting {remoteCtrl_, ctrlAppInfo, appVersion} ->
|
||||
[ (maybe "connecting new remote controller" (\RemoteCtrlInfo {remoteCtrlId} -> "connecting remote controller " <> sShow remoteCtrlId) remoteCtrl_ <> ": ")
|
||||
<> viewRemoteCtrl ctrlAppInfo appVersion True
|
||||
@@ -322,7 +324,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
]
|
||||
CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} ->
|
||||
["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName]
|
||||
CRRemoteCtrlStopped -> ["remote controller stopped"]
|
||||
CRRemoteCtrlStopped {} -> ["remote controller stopped"]
|
||||
CRSQLResult rows -> map plain rows
|
||||
CRSlowSQLQueries {chatQueries, agentQueries} ->
|
||||
let viewQuery SlowSQLQuery {query, queryStats = SlowQueryStats {count, timeMax, timeAvg}} =
|
||||
|
||||
Reference in New Issue
Block a user