mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
core: timed messages (#1561)
* docs: disappearing messages rfc * change schema * word * wip * wip * todos * todos * remove cancel, refactor * revert prefs * CITimed * schema * time on send direct * time on send group * add ttl to msg container, refactor * timed on receive * time on read * getTimedItems, fix tests * mark read in terminal - view, input, output, fix tests * refactor * comment * util * insert atomically * refactor * use guards * refactor startTimedItemThread Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
module Simplex.Chat.Controller where
|
||||
|
||||
import Control.Concurrent (ThreadId)
|
||||
import Control.Concurrent.Async (Async)
|
||||
import Control.Exception
|
||||
import Control.Monad.Except
|
||||
@@ -53,6 +54,7 @@ import Simplex.Messaging.Protocol (AProtocolType, CorrId, MsgFlags)
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||
import System.IO (Handle)
|
||||
import System.Mem.Weak (Weak)
|
||||
import UnliftIO.STM
|
||||
|
||||
versionNumber :: String
|
||||
@@ -121,7 +123,9 @@ data ChatController = ChatController
|
||||
filesFolder :: TVar (Maybe FilePath), -- path to files folder for mobile apps,
|
||||
incognitoMode :: TVar Bool,
|
||||
expireCIsAsync :: TVar (Maybe (Async ())),
|
||||
expireCIs :: TVar Bool
|
||||
expireCIs :: TVar Bool,
|
||||
cleanupManagerAsync :: TVar (Maybe (Async ())),
|
||||
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId)))
|
||||
}
|
||||
|
||||
data HelpSection = HSMain | HSFiles | HSGroups | HSMyAddress | HSMarkdown | HSMessages | HSSettings
|
||||
@@ -292,8 +296,9 @@ data ChatResponse
|
||||
| CRNewChatItem {chatItem :: AChatItem}
|
||||
| CRChatItemStatusUpdated {chatItem :: AChatItem}
|
||||
| CRChatItemUpdated {chatItem :: AChatItem}
|
||||
| CRChatItemDeleted {deletedChatItem :: AChatItem, toChatItem :: Maybe AChatItem, byUser :: Bool}
|
||||
| CRChatItemDeleted {deletedChatItem :: AChatItem, toChatItem :: Maybe AChatItem, byUser :: Bool, timed :: Bool}
|
||||
| CRChatItemDeletedNotFound {contact :: Contact, sharedMsgId :: SharedMsgId}
|
||||
| CRChatRead
|
||||
| CRBroadcastSent MsgContent Int ZonedTime
|
||||
| CRMsgIntegrityError {msgError :: MsgErrorType}
|
||||
| CRCmdAccepted {corr :: CorrId}
|
||||
@@ -566,6 +571,7 @@ data ChatErrorType
|
||||
| CEAgentNoSubResult {agentConnId :: AgentConnId}
|
||||
| CECommandError {message :: String}
|
||||
| CEAgentCommandError {message :: String}
|
||||
| CEInternalError {message :: String}
|
||||
deriving (Show, Exception, Generic)
|
||||
|
||||
instance ToJSON ChatErrorType where
|
||||
|
||||
@@ -39,13 +39,23 @@ import Simplex.Messaging.Protocol (MsgBody)
|
||||
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||
|
||||
data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection
|
||||
deriving (Show, Generic)
|
||||
deriving (Eq, Show, Ord, Generic)
|
||||
|
||||
serializeChatType :: ChatType -> String
|
||||
serializeChatType = \case
|
||||
CTDirect -> "@"
|
||||
CTGroup -> "#"
|
||||
CTContactRequest -> "?" -- this isn't being parsed
|
||||
CTContactConnection -> ":"
|
||||
|
||||
data ChatName = ChatName ChatType Text
|
||||
deriving (Show)
|
||||
|
||||
data ChatRef = ChatRef ChatType Int64
|
||||
deriving (Show)
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
serializeChatRef :: ChatRef -> String
|
||||
serializeChatRef (ChatRef cType chatId) = serializeChatType cType <> show chatId
|
||||
|
||||
instance ToJSON ChatType where
|
||||
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CT"
|
||||
@@ -66,6 +76,13 @@ chatInfoUpdatedAt = \case
|
||||
ContactRequest UserContactRequest {updatedAt} -> updatedAt
|
||||
ContactConnection PendingContactConnection {updatedAt} -> updatedAt
|
||||
|
||||
chatInfoToRef :: ChatInfo c -> ChatRef
|
||||
chatInfoToRef = \case
|
||||
DirectChat Contact {contactId} -> ChatRef CTDirect contactId
|
||||
GroupChat GroupInfo {groupId} -> ChatRef CTGroup groupId
|
||||
ContactRequest UserContactRequest {contactRequestId} -> ChatRef CTContactRequest contactRequestId
|
||||
ContactConnection PendingContactConnection {pccConnId} -> ChatRef CTContactConnection pccConnId
|
||||
|
||||
data JSONChatInfo
|
||||
= JCInfoDirect {contact :: Contact}
|
||||
| JCInfoGroup {groupInfo :: GroupInfo}
|
||||
@@ -259,20 +276,41 @@ data CIMeta (d :: MsgDirection) = CIMeta
|
||||
editable :: Bool,
|
||||
localItemTs :: ZonedTime,
|
||||
createdAt :: UTCTime,
|
||||
updatedAt :: UTCTime
|
||||
updatedAt :: UTCTime,
|
||||
timed :: Maybe CITimed
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Bool -> Bool -> TimeZone -> UTCTime -> ChatItemTs -> UTCTime -> UTCTime -> CIMeta d
|
||||
mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited tz currentTs itemTs createdAt updatedAt =
|
||||
mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Bool -> Bool -> TimeZone -> UTCTime -> ChatItemTs -> UTCTime -> UTCTime -> Maybe CITimed -> CIMeta d
|
||||
mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited tz currentTs itemTs createdAt updatedAt timed =
|
||||
let localItemTs = utcToZonedTime tz itemTs
|
||||
editable = case itemContent of
|
||||
CISndMsgContent _ -> diffUTCTime currentTs itemTs < nominalDay && not itemDeleted
|
||||
_ -> False
|
||||
in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, editable, localItemTs, createdAt, updatedAt}
|
||||
in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, editable, localItemTs, createdAt, updatedAt, timed}
|
||||
|
||||
instance ToJSON (CIMeta d) where toEncoding = J.genericToEncoding J.defaultOptions
|
||||
|
||||
data CITimed = CITimed
|
||||
{ ttl :: Int, -- seconds
|
||||
deleteAt :: Maybe UTCTime
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
instance ToJSON CITimed where toEncoding = J.genericToEncoding J.defaultOptions
|
||||
|
||||
contactCITimedTTL :: Contact -> Maybe Int
|
||||
contactCITimedTTL Contact {mergedPreferences = ContactUserPreferences {timedMessages = ContactUserPreference {enabled, userPreference}}}
|
||||
| forUser enabled && forContact enabled = case userPreference of
|
||||
CUPContact TimedMessagesPreference {ttl = t} -> Just t
|
||||
CUPUser TimedMessagesPreference {ttl = t} -> Just t
|
||||
| otherwise = Nothing
|
||||
|
||||
groupCITimedTTL :: GroupInfo -> Maybe Int
|
||||
groupCITimedTTL GroupInfo {fullGroupPreferences = FullGroupPreferences {timedMessages = TimedMessagesGroupPreference {enable, ttl}}}
|
||||
| enable == FEOn = Just ttl
|
||||
| otherwise = Nothing
|
||||
|
||||
data CIQuote (c :: ChatType) = CIQuote
|
||||
{ chatDir :: CIQDirection c,
|
||||
itemId :: Maybe ChatItemId, -- Nothing in case MsgRef references the item the user did not receive yet
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20221212_chat_items_timed where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20221212_chat_items_timed :: Query
|
||||
m20221212_chat_items_timed =
|
||||
[sql|
|
||||
ALTER TABLE chat_items ADD COLUMN timed_ttl INTEGER;
|
||||
|
||||
ALTER TABLE chat_items ADD COLUMN timed_delete_at TEXT;
|
||||
|
||||
CREATE INDEX idx_chat_items_timed_delete_at ON chat_items(timed_delete_at);
|
||||
|]
|
||||
@@ -365,7 +365,9 @@ CREATE TABLE chat_items(
|
||||
quoted_content TEXT,
|
||||
quoted_sent INTEGER,
|
||||
quoted_member_id BLOB,
|
||||
item_edited INTEGER
|
||||
item_edited INTEGER,
|
||||
timed_ttl INTEGER,
|
||||
timed_delete_at TEXT
|
||||
);
|
||||
CREATE TABLE chat_item_messages(
|
||||
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
|
||||
@@ -456,3 +458,4 @@ CREATE UNIQUE INDEX idx_snd_files_last_inline_msg_delivery_id ON snd_files(
|
||||
CREATE INDEX idx_messages_connection_id ON messages(connection_id);
|
||||
CREATE INDEX idx_chat_items_group_member_id ON chat_items(group_member_id);
|
||||
CREATE INDEX idx_chat_items_contact_id ON chat_items(contact_id);
|
||||
CREATE INDEX idx_chat_items_timed_delete_at ON chat_items(timed_delete_at);
|
||||
|
||||
@@ -367,8 +367,8 @@ parseMsgContainer v =
|
||||
where
|
||||
mc = ExtMsgContent <$> v .: "content" <*> v .:? "file" <*> v .:? "ttl" <*> v .:? "live"
|
||||
|
||||
extMsgContent :: MsgContent -> Maybe FileInvitation -> ExtMsgContent
|
||||
extMsgContent mc file = ExtMsgContent mc file Nothing Nothing
|
||||
extMsgContent :: MsgContent -> Maybe FileInvitation -> Maybe Int -> ExtMsgContent
|
||||
extMsgContent mc file ttl = ExtMsgContent mc file ttl Nothing
|
||||
|
||||
instance FromJSON MsgContent where
|
||||
parseJSON (J.Object v) =
|
||||
|
||||
+138
-43
@@ -208,7 +208,11 @@ module Simplex.Chat.Store
|
||||
deleteGroupChatItem,
|
||||
markGroupChatItemDeleted,
|
||||
updateDirectChatItemsRead,
|
||||
getDirectUnreadTimedItems,
|
||||
setDirectChatItemDeleteAt,
|
||||
updateGroupChatItemsRead,
|
||||
getGroupUnreadTimedItems,
|
||||
setGroupChatItemDeleteAt,
|
||||
getSMPServers,
|
||||
overwriteSMPServers,
|
||||
createCall,
|
||||
@@ -222,6 +226,7 @@ module Simplex.Chat.Store
|
||||
setConnConnReqInv,
|
||||
getXGrpMemIntroContDirect,
|
||||
getXGrpMemIntroContGroup,
|
||||
getTimedItems,
|
||||
getChatItemTTL,
|
||||
setChatItemTTL,
|
||||
getContactExpiredFileInfo,
|
||||
@@ -255,7 +260,7 @@ import Data.Functor (($>))
|
||||
import Data.Int (Int64)
|
||||
import Data.List (sortBy, sortOn)
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.Maybe (fromMaybe, isJust, isNothing, listToMaybe)
|
||||
import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe)
|
||||
import Data.Ord (Down (..))
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@@ -310,6 +315,7 @@ import Simplex.Chat.Migrations.M20221130_delete_item_deleted
|
||||
import Simplex.Chat.Migrations.M20221209_verified_connection
|
||||
import Simplex.Chat.Migrations.M20221210_idxs
|
||||
import Simplex.Chat.Migrations.M20221211_group_description
|
||||
import Simplex.Chat.Migrations.M20221212_chat_items_timed
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, AgentMsgId, ConnId, InvitationId, MsgMeta (..))
|
||||
@@ -363,7 +369,8 @@ schemaMigrations =
|
||||
("20221130_delete_item_deleted", m20221130_delete_item_deleted),
|
||||
("20221209_verified_connection", m20221209_verified_connection),
|
||||
("20221210_idxs", m20221210_idxs),
|
||||
("20221211_group_description", m20221211_group_description)
|
||||
("20221211_group_description", m20221211_group_description),
|
||||
("20221212_chat_items_timed", m20221212_chat_items_timed)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
@@ -3130,7 +3137,7 @@ deletePendingGroupMessage db groupMemberId messageId =
|
||||
|
||||
type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId)
|
||||
|
||||
createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> UTCTime -> IO ChatItemId
|
||||
createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> UTCTime -> Maybe CITimed -> IO ChatItemId
|
||||
createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem createdAt =
|
||||
createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow createdAt createdAt
|
||||
where
|
||||
@@ -3146,9 +3153,9 @@ createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciCon
|
||||
CIQGroupRcv (Just GroupMember {memberId}) -> (Just False, Just memberId)
|
||||
CIQGroupRcv Nothing -> (Just False, Nothing)
|
||||
|
||||
createNewRcvChatItem :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c))
|
||||
createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} sharedMsgId_ ciContent itemTs createdAt = do
|
||||
ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow itemTs createdAt
|
||||
createNewRcvChatItem :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> UTCTime -> UTCTime -> Maybe CITimed -> IO (ChatItemId, Maybe (CIQuote c))
|
||||
createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} sharedMsgId_ ciContent itemTs createdAt timed = do
|
||||
ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow itemTs createdAt timed
|
||||
quotedItem <- mapM (getChatItemQuote_ db user chatDirection) quotedMsg
|
||||
pure (ciId, quotedItem)
|
||||
where
|
||||
@@ -3163,14 +3170,14 @@ createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} shar
|
||||
(Just $ Just userMemberId == memberId, memberId)
|
||||
|
||||
createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId
|
||||
createNewChatItemNoMsg db user chatDirection ciContent =
|
||||
createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow
|
||||
createNewChatItemNoMsg db user chatDirection ciContent itemTs createdAt =
|
||||
createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow itemTs createdAt Nothing
|
||||
where
|
||||
quoteRow :: NewQuoteRow
|
||||
quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing)
|
||||
|
||||
createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> UTCTime -> UTCTime -> IO ChatItemId
|
||||
createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow itemTs createdAt = do
|
||||
createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> UTCTime -> UTCTime -> Maybe CITimed -> IO ChatItemId
|
||||
createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow itemTs createdAt timed = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -3178,18 +3185,22 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
|
||||
-- user and IDs
|
||||
user_id, created_by_msg_id, contact_id, group_id, group_member_id,
|
||||
-- meta
|
||||
item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at,
|
||||
item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at, timed_ttl, timed_delete_at,
|
||||
-- quote
|
||||
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
((userId, msgId_) :. idsRow :. itemRow :. quoteRow)
|
||||
ciId <- insertedRowId db
|
||||
forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt
|
||||
pure ciId
|
||||
where
|
||||
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId, UTCTime, UTCTime)
|
||||
itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId, createdAt, createdAt)
|
||||
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId, UTCTime, UTCTime, Maybe Int, Maybe UTCTime)
|
||||
itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId, createdAt, createdAt, timedTTL, timedDeleteAt)
|
||||
where
|
||||
(timedTTL, timedDeleteAt) = case timed of
|
||||
Just CITimed {ttl, deleteAt} -> (Just ttl, deleteAt)
|
||||
Nothing -> (Nothing, Nothing)
|
||||
idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64)
|
||||
idsRow = case chatDirection of
|
||||
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
|
||||
@@ -3292,7 +3303,7 @@ getDirectChatPreviews_ db user@User {userId} = do
|
||||
-- ChatStats
|
||||
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat,
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- DirectQuote
|
||||
@@ -3357,7 +3368,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
|
||||
-- ChatStats
|
||||
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat,
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- Maybe GroupMember - sender
|
||||
@@ -3516,7 +3527,7 @@ getDirectChatLast_ db user@User {userId} contactId count search = do
|
||||
[sql|
|
||||
SELECT
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- DirectQuote
|
||||
@@ -3547,7 +3558,7 @@ getDirectChatAfter_ db user@User {userId} contactId afterChatItemId count search
|
||||
[sql|
|
||||
SELECT
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- DirectQuote
|
||||
@@ -3579,7 +3590,7 @@ getDirectChatBefore_ db user@User {userId} contactId beforeChatItemId count sear
|
||||
[sql|
|
||||
SELECT
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- DirectQuote
|
||||
@@ -3925,7 +3936,7 @@ getDirectChatItem db userId contactId itemId = ExceptT $ do
|
||||
[sql|
|
||||
SELECT
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- DirectQuote
|
||||
@@ -4026,7 +4037,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
|
||||
[sql|
|
||||
SELECT
|
||||
-- ChatItem
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at,
|
||||
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at,
|
||||
-- CIFile
|
||||
f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status,
|
||||
-- GroupMember
|
||||
@@ -4153,8 +4164,8 @@ toChatItemRef = \case
|
||||
(itemId, Nothing, Just groupId) -> Right (itemId, ChatRef CTGroup groupId)
|
||||
(itemId, _, _) -> Left $ SEBadChatItem itemId
|
||||
|
||||
updateDirectChatItemsRead :: DB.Connection -> Int64 -> Maybe (ChatItemId, ChatItemId) -> IO ()
|
||||
updateDirectChatItemsRead db contactId itemsRange_ = do
|
||||
updateDirectChatItemsRead :: DB.Connection -> UserId -> ContactId -> Maybe (ChatItemId, ChatItemId) -> IO ()
|
||||
updateDirectChatItemsRead db userId contactId itemsRange_ = do
|
||||
currentTs <- getCurrentTime
|
||||
case itemsRange_ of
|
||||
Just (fromItemId, toItemId) ->
|
||||
@@ -4162,20 +4173,48 @@ updateDirectChatItemsRead db contactId itemsRange_ = do
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||
WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, contactId, fromItemId, toItemId, CISRcvNew)
|
||||
(userId, CISRcvRead, currentTs, contactId, fromItemId, toItemId, CISRcvNew)
|
||||
_ ->
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE contact_id = ? AND item_status = ?
|
||||
WHERE user_id = ? AND contact_id = ? AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, contactId, CISRcvNew)
|
||||
(userId, CISRcvRead, currentTs, contactId, CISRcvNew)
|
||||
|
||||
updateGroupChatItemsRead :: DB.Connection -> Int64 -> Maybe (ChatItemId, ChatItemId) -> IO ()
|
||||
updateGroupChatItemsRead db groupId itemsRange_ = do
|
||||
getDirectUnreadTimedItems :: DB.Connection -> User -> ContactId -> Maybe (ChatItemId, ChatItemId) -> IO [(ChatItemId, Int)]
|
||||
getDirectUnreadTimedItems db User {userId} contactId itemsRange_ = case itemsRange_ of
|
||||
Just (fromItemId, toItemId) ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, contactId, fromItemId, toItemId, CISRcvNew)
|
||||
_ ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND contact_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, contactId, CISRcvNew)
|
||||
|
||||
setDirectChatItemDeleteAt :: DB.Connection -> User -> ContactId -> ChatItemId -> UTCTime -> IO ()
|
||||
setDirectChatItemDeleteAt db User {userId} contactId chatItemId deleteAt =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?"
|
||||
(deleteAt, userId, contactId, chatItemId)
|
||||
|
||||
updateGroupChatItemsRead :: DB.Connection -> UserId -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO ()
|
||||
updateGroupChatItemsRead db userId groupId itemsRange_ = do
|
||||
currentTs <- getCurrentTime
|
||||
case itemsRange_ of
|
||||
Just (fromItemId, toItemId) ->
|
||||
@@ -4183,17 +4222,45 @@ updateGroupChatItemsRead db groupId itemsRange_ = do
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||
WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, groupId, fromItemId, toItemId, CISRcvNew)
|
||||
(userId, CISRcvRead, currentTs, groupId, fromItemId, toItemId, CISRcvNew)
|
||||
_ ->
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE group_id = ? AND item_status = ?
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, groupId, CISRcvNew)
|
||||
(userId, CISRcvRead, currentTs, groupId, CISRcvNew)
|
||||
|
||||
getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO [(ChatItemId, Int)]
|
||||
getGroupUnreadTimedItems db User {userId} groupId itemsRange_ = case itemsRange_ of
|
||||
Just (fromItemId, toItemId) ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, fromItemId, toItemId, CISRcvNew)
|
||||
_ ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, CISRcvNew)
|
||||
|
||||
setGroupChatItemDeleteAt :: DB.Connection -> User -> GroupId -> ChatItemId -> UTCTime -> IO ()
|
||||
setGroupChatItemDeleteAt db User {userId} groupId chatItemId deleteAt =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?"
|
||||
(deleteAt, userId, groupId, chatItemId)
|
||||
|
||||
type ChatStatsRow = (Int, ChatItemId, Bool)
|
||||
|
||||
@@ -4202,9 +4269,9 @@ toChatStats (unreadCount, minUnreadItemId, unreadChat) = ChatStats {unreadCount,
|
||||
|
||||
type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe ACIFileStatus)
|
||||
|
||||
type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, ACIStatus, Maybe SharedMsgId, Bool, Maybe Bool, UTCTime, UTCTime) :. MaybeCIFIleRow
|
||||
type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, ACIStatus, Maybe SharedMsgId, Bool, Maybe Bool, UTCTime, UTCTime, Maybe Int, Maybe UTCTime) :. MaybeCIFIleRow
|
||||
|
||||
type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe ACIStatus, Maybe SharedMsgId, Maybe Bool, Maybe Bool, Maybe UTCTime, Maybe UTCTime) :. MaybeCIFIleRow
|
||||
type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe ACIStatus, Maybe SharedMsgId, Maybe Bool, Maybe Bool, Maybe UTCTime, Maybe UTCTime, Maybe Int, Maybe UTCTime) :. MaybeCIFIleRow
|
||||
|
||||
type QuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool)
|
||||
|
||||
@@ -4218,7 +4285,7 @@ toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir
|
||||
CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent)
|
||||
|
||||
toDirectChatItem :: TimeZone -> UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect)
|
||||
toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_)) :. quoteRow) =
|
||||
toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt, timedTTL, timedDeleteAt) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_)) :. quoteRow) =
|
||||
case (itemContent, itemStatus, fileStatus_) of
|
||||
(ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, Just (AFS SMDSnd fileStatus)) ->
|
||||
Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent (maybeCIFile fileStatus)
|
||||
@@ -4240,11 +4307,16 @@ toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStat
|
||||
CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toDirectQuote quoteRow, file}
|
||||
badItem = Left $ SEBadChatItem itemId
|
||||
ciMeta :: CIContent d -> CIStatus d -> CIMeta d
|
||||
ciMeta content status = mkCIMeta itemId content itemText status sharedMsgId itemDeleted (fromMaybe False itemEdited) tz currentTs itemTs createdAt updatedAt
|
||||
ciMeta content status = mkCIMeta itemId content itemText status sharedMsgId itemDeleted (fromMaybe False itemEdited) tz currentTs itemTs createdAt updatedAt ciTimed
|
||||
ciTimed :: Maybe CITimed
|
||||
ciTimed =
|
||||
case (timedTTL, timedDeleteAt) of
|
||||
(Just ttl, deleteAt) -> Just CITimed {ttl, deleteAt}
|
||||
_ -> Nothing
|
||||
|
||||
toDirectChatItemList :: TimeZone -> UTCTime -> MaybeChatItemRow :. QuoteRow -> [CChatItem 'CTDirect]
|
||||
toDirectChatItemList tz currentTs (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. fileRow) :. quoteRow) =
|
||||
either (const []) (: []) $ toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. fileRow) :. quoteRow)
|
||||
toDirectChatItemList tz currentTs (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt, timedTTL, timedDeleteAt) :. fileRow) :. quoteRow) =
|
||||
either (const []) (: []) $ toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt, timedTTL, timedDeleteAt) :. fileRow) :. quoteRow)
|
||||
toDirectChatItemList _ _ _ = []
|
||||
|
||||
type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
|
||||
@@ -4260,7 +4332,7 @@ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction
|
||||
direction _ _ = Nothing
|
||||
|
||||
toGroupChatItem :: TimeZone -> UTCTime -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow -> Either StoreError (CChatItem 'CTGroup)
|
||||
toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_)) :. memberRow_ :. quoteRow :. quotedMemberRow_) = do
|
||||
toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt, timedTTL, timedDeleteAt) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_)) :. memberRow_ :. quoteRow :. quotedMemberRow_) = do
|
||||
let member_ = toMaybeGroupMember userContactId memberRow_
|
||||
let quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
|
||||
case (itemContent, itemStatus, member_, fileStatus_) of
|
||||
@@ -4284,11 +4356,16 @@ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemT
|
||||
CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, file}
|
||||
badItem = Left $ SEBadChatItem itemId
|
||||
ciMeta :: CIContent d -> CIStatus d -> CIMeta d
|
||||
ciMeta content status = mkCIMeta itemId content itemText status sharedMsgId itemDeleted (fromMaybe False itemEdited) tz currentTs itemTs createdAt updatedAt
|
||||
ciMeta content status = mkCIMeta itemId content itemText status sharedMsgId itemDeleted (fromMaybe False itemEdited) tz currentTs itemTs createdAt updatedAt ciTimed
|
||||
ciTimed :: Maybe CITimed
|
||||
ciTimed =
|
||||
case (timedTTL, timedDeleteAt) of
|
||||
(Just ttl, deleteAt) -> Just CITimed {ttl, deleteAt}
|
||||
_ -> Nothing
|
||||
|
||||
toGroupChatItemList :: TimeZone -> UTCTime -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup]
|
||||
toGroupChatItemList tz currentTs userContactId (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. fileRow) :. memberRow_ :. quoteRow :. quotedMemberRow_) =
|
||||
either (const []) (: []) $ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. fileRow) :. memberRow_ :. quoteRow :. quotedMemberRow_)
|
||||
toGroupChatItemList tz currentTs userContactId (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt, timedTTL, timedDeleteAt) :. fileRow) :. memberRow_ :. quoteRow :. quotedMemberRow_) =
|
||||
either (const []) (: []) $ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt, timedTTL, timedDeleteAt) :. fileRow) :. memberRow_ :. quoteRow :. quotedMemberRow_)
|
||||
toGroupChatItemList _ _ _ _ = []
|
||||
|
||||
getSMPServers :: DB.Connection -> User -> IO [ServerCfg]
|
||||
@@ -4485,6 +4562,24 @@ getXGrpMemIntroContGroup db User {userId} GroupMember {groupMemberId} = do
|
||||
Just connReq -> Just (hostConnId, connReq)
|
||||
_ -> Nothing
|
||||
|
||||
getTimedItems :: DB.Connection -> User -> UTCTime -> IO [((ChatRef, ChatItemId), UTCTime)]
|
||||
getTimedItems db User {userId} startTimedThreadCutoff =
|
||||
catMaybes . map toCIRefDeleteAt
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, contact_id, group_id, timed_delete_at
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND timed_delete_at IS NOT NULL AND timed_delete_at <= ?
|
||||
|]
|
||||
(userId, startTimedThreadCutoff)
|
||||
where
|
||||
toCIRefDeleteAt :: (ChatItemId, Maybe ContactId, Maybe GroupId, UTCTime) -> Maybe ((ChatRef, ChatItemId), UTCTime)
|
||||
toCIRefDeleteAt = \case
|
||||
(itemId, Just contactId, Nothing, deleteAt) -> Just ((ChatRef CTDirect contactId, itemId), deleteAt)
|
||||
(itemId, Nothing, Just groupId, deleteAt) -> Just ((ChatRef CTGroup groupId, itemId), deleteAt)
|
||||
_ -> Nothing
|
||||
|
||||
getChatItemTTL :: DB.Connection -> User -> IO (Maybe Int64)
|
||||
getChatItemTTL db User {userId} =
|
||||
fmap join . maybeFirstRow fromOnly $ DB.query db "SELECT chat_item_ttl FROM settings WHERE user_id = ? LIMIT 1" (Only userId)
|
||||
|
||||
@@ -35,7 +35,7 @@ runInputLoop ct cc = forever $ do
|
||||
s <- atomically . readTBQueue $ inputQ cc
|
||||
let bs = encodeUtf8 $ T.pack s
|
||||
cmd = parseChatCommand bs
|
||||
unless (isMessage cmd) $ echo s
|
||||
when (doEcho cmd) $ echo s
|
||||
r <- runReaderT (execChatCommand bs) cc
|
||||
case r of
|
||||
CRChatCmdError _ -> when (isMessage cmd) $ echo s
|
||||
@@ -46,6 +46,9 @@ runInputLoop ct cc = forever $ do
|
||||
printToTerminal ct $ responseToView user testV ts r
|
||||
where
|
||||
echo s = printToTerminal ct [plain s]
|
||||
doEcho cmd = case cmd of
|
||||
Right APIChatRead {} -> False
|
||||
_ -> not $ isMessage cmd
|
||||
isMessage = \case
|
||||
Right SendMessage {} -> True
|
||||
Right SendFile {} -> True
|
||||
|
||||
@@ -12,6 +12,7 @@ import Control.Monad.IO.Unlift
|
||||
import Control.Monad.Reader
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Messages hiding (NewChatItem (..))
|
||||
import Simplex.Chat.Styled
|
||||
import Simplex.Chat.View
|
||||
import System.Console.ANSI.Types
|
||||
@@ -74,13 +75,26 @@ withTermLock ChatTerminal {termLock} action = do
|
||||
atomically $ putTMVar termLock ()
|
||||
|
||||
runTerminalOutput :: ChatTerminal -> ChatController -> IO ()
|
||||
runTerminalOutput ct cc = do
|
||||
let testV = testView $ config cc
|
||||
runTerminalOutput ct ChatController {currentUser, inputQ, outputQ, config = ChatConfig {testView}} = do
|
||||
forever $ do
|
||||
(_, r) <- atomically . readTBQueue $ outputQ cc
|
||||
user <- readTVarIO $ currentUser cc
|
||||
(_, r) <- atomically $ readTBQueue outputQ
|
||||
case r of
|
||||
CRNewChatItem ci -> markChatItemRead ci
|
||||
CRChatItemUpdated ci -> markChatItemRead ci
|
||||
_ -> pure ()
|
||||
user <- readTVarIO currentUser
|
||||
ts <- getCurrentTime
|
||||
printToTerminal ct $ responseToView user testV ts r
|
||||
printToTerminal ct $ responseToView user testView ts r
|
||||
where
|
||||
markChatItemRead :: AChatItem -> IO ()
|
||||
markChatItemRead (AChatItem _ _ chat item@ChatItem {meta = CIMeta {itemStatus}}) =
|
||||
case (muted chat item, itemStatus) of
|
||||
(False, CISRcvNew) -> do
|
||||
let itemId = chatItemId' item
|
||||
chatRef = serializeChatRef $ chatInfoToRef chat
|
||||
cmd = "/_read chat " <> chatRef <> " from=" <> show itemId <> " to=" <> show itemId
|
||||
atomically $ writeTBQueue inputQ cmd
|
||||
_ -> pure ()
|
||||
|
||||
printToTerminal :: ChatTerminal -> [StyledString] -> IO ()
|
||||
printToTerminal ct s =
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module Simplex.Chat.Util
|
||||
( diffInMicros,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Fixed (Fixed (MkFixed), Pico)
|
||||
import Data.Time (nominalDiffTimeToSeconds)
|
||||
import Data.Time.Clock (UTCTime, diffUTCTime)
|
||||
|
||||
diffInMicros :: UTCTime -> UTCTime -> Int
|
||||
diffInMicros a b = (`div` 1000000) $ diffInPicos a b
|
||||
|
||||
diffInPicos :: UTCTime -> UTCTime -> Int
|
||||
diffInPicos a b = fromInteger . fromPico . nominalDiffTimeToSeconds $ diffUTCTime a b
|
||||
|
||||
fromPico :: Pico -> Integer
|
||||
fromPico (MkFixed i) = i
|
||||
@@ -82,8 +82,9 @@ responseToView user_ testView ts = \case
|
||||
CRLastMessages chatItems -> concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts) chatItems
|
||||
CRChatItemStatusUpdated _ -> []
|
||||
CRChatItemUpdated (AChatItem _ _ chat item) -> unmuted chat item $ viewItemUpdate chat item ts
|
||||
CRChatItemDeleted (AChatItem _ _ chat deletedItem) toItem byUser -> unmuted chat deletedItem $ viewItemDelete chat deletedItem (isJust toItem) byUser ts
|
||||
CRChatItemDeleted (AChatItem _ _ chat deletedItem) toItem byUser timed -> unmuted chat deletedItem $ viewItemDelete chat deletedItem (isJust toItem) byUser timed ts
|
||||
CRChatItemDeletedNotFound Contact {localDisplayName = c} _ -> [ttyFrom $ c <> "> [deleted - original message not found]"]
|
||||
CRChatRead -> []
|
||||
CRBroadcastSent mc n t -> viewSentBroadcast mc n ts t
|
||||
CRMsgIntegrityError mErr -> viewMsgIntegrityError mErr
|
||||
CRCmdAccepted _ -> []
|
||||
@@ -251,10 +252,16 @@ responseToView user_ testView ts = \case
|
||||
contactList :: [ContactRef] -> String
|
||||
contactList cs = T.unpack . T.intercalate ", " $ map (\ContactRef {localDisplayName = n} -> "@" <> n) cs
|
||||
unmuted :: ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString]
|
||||
unmuted chat ChatItem {chatDir} s = case (chat, chatDir) of
|
||||
(DirectChat Contact {chatSettings = DisableNtfs}, CIDirectRcv) -> []
|
||||
(GroupChat GroupInfo {chatSettings = DisableNtfs}, CIGroupRcv _) -> []
|
||||
_ -> s
|
||||
unmuted chat chatItem s =
|
||||
if muted chat chatItem
|
||||
then []
|
||||
else s
|
||||
|
||||
muted :: ChatInfo c -> ChatItem c d -> Bool
|
||||
muted chat ChatItem {chatDir} = case (chat, chatDir) of
|
||||
(DirectChat Contact {chatSettings = DisableNtfs}, CIDirectRcv) -> True
|
||||
(GroupChat GroupInfo {chatSettings = DisableNtfs}, CIGroupRcv _) -> True
|
||||
_ -> False
|
||||
|
||||
viewGroupSubscribed :: GroupInfo -> [StyledString]
|
||||
viewGroupSubscribed g@GroupInfo {membership} =
|
||||
@@ -343,8 +350,9 @@ viewItemUpdate chat ChatItem {chatDir, meta, content, quotedItem} ts = case chat
|
||||
CIGroupSnd -> ["message updated"]
|
||||
_ -> []
|
||||
|
||||
viewItemDelete :: ChatInfo c -> ChatItem c d -> Bool -> Bool -> CurrentTime -> [StyledString]
|
||||
viewItemDelete chat ChatItem {chatDir, meta, content = deletedContent} markedDeleted byUser ts
|
||||
viewItemDelete :: ChatInfo c -> ChatItem c d -> Bool -> Bool -> Bool -> CurrentTime -> [StyledString]
|
||||
viewItemDelete chat ChatItem {chatDir, meta, content = deletedContent} markedDeleted byUser timed ts
|
||||
| timed = []
|
||||
| byUser = if markedDeleted then ["message marked deleted"] else ["message deleted"]
|
||||
| otherwise = case chat of
|
||||
DirectChat Contact {localDisplayName = c} -> case (chatDir, deletedContent) of
|
||||
@@ -421,9 +429,9 @@ viewContactsList :: [Contact] -> [StyledString]
|
||||
viewContactsList =
|
||||
let ldn = T.toLower . (localDisplayName :: Contact -> ContactName)
|
||||
incognito ct = if contactConnIncognito ct then incognitoPrefix else ""
|
||||
in map (\ct -> incognito ct <> ttyFullContact ct <> muted ct <> alias ct) . sortOn ldn
|
||||
in map (\ct -> incognito ct <> ttyFullContact ct <> muted' ct <> alias ct) . sortOn ldn
|
||||
where
|
||||
muted Contact {chatSettings, localDisplayName = ldn}
|
||||
muted' Contact {chatSettings, localDisplayName = ldn}
|
||||
| enableNtfs chatSettings = ""
|
||||
| otherwise = " (muted, you can " <> highlight ("/unmute @" <> ldn) <> ")"
|
||||
alias Contact {profile = LocalProfile {localAlias}}
|
||||
@@ -1142,6 +1150,7 @@ viewChatError = \case
|
||||
CEAgentNoSubResult connId -> ["no subscription result for connection: " <> sShow connId]
|
||||
CECommandError e -> ["bad chat command: " <> plain e]
|
||||
CEAgentCommandError e -> ["agent command error: " <> plain e]
|
||||
CEInternalError e -> ["internal chat error: " <> plain e]
|
||||
-- e -> ["chat error: " <> sShow e]
|
||||
ChatErrorStore err -> case err of
|
||||
SEDuplicateName -> ["this display name is already used by user, contact or group"]
|
||||
|
||||
Reference in New Issue
Block a user