mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-07-18 07:16:13 +00:00
* agent: support client notices * improve * fix, test * rename * cleanup * send and process notices in more cases * dont delete * dont remove notice on other permanent errors * dont remove notice if there is no notice ID in queue * add server to error * allow deleting * only use notice if key hash matches
178 lines
8.0 KiB
Haskell
178 lines
8.0 KiB
Haskell
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE GADTs #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE StrictData #-}
|
|
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
|
|
|
module Simplex.Messaging.Notifications.Server.StoreLog
|
|
( StoreLog,
|
|
NtfStoreLogRecord (..),
|
|
readWriteNtfSTMStore,
|
|
logCreateToken,
|
|
logTokenStatus,
|
|
logUpdateToken,
|
|
logTokenCron,
|
|
logDeleteToken,
|
|
logUpdateTokenTime,
|
|
logCreateSubscription,
|
|
logSubscriptionStatus,
|
|
logDeleteSubscription,
|
|
closeStoreLog,
|
|
)
|
|
where
|
|
|
|
import Control.Applicative (optional, (<|>))
|
|
import Control.Concurrent.STM
|
|
import Control.Monad
|
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
|
import qualified Data.ByteString.Base64.URL as B64
|
|
import qualified Data.ByteString.Char8 as B
|
|
import Data.Functor (($>))
|
|
import qualified Data.Map.Strict as M
|
|
import Data.Maybe (fromMaybe)
|
|
import Data.Word (Word16)
|
|
import Simplex.Messaging.Encoding.String
|
|
import Simplex.Messaging.Notifications.Protocol
|
|
import Simplex.Messaging.Notifications.Server.Store
|
|
import Simplex.Messaging.Notifications.Server.Store.Types
|
|
import Simplex.Messaging.Protocol (EntityId (..), SMPServer, ServiceId)
|
|
import Simplex.Messaging.Server.StoreLog
|
|
import Simplex.Messaging.SystemTime
|
|
import System.IO
|
|
|
|
data NtfStoreLogRecord
|
|
= CreateToken NtfTknRec
|
|
| TokenStatus NtfTokenId NtfTknStatus
|
|
| UpdateToken NtfTokenId DeviceToken NtfRegCode
|
|
| TokenCron NtfTokenId Word16
|
|
| DeleteToken NtfTokenId
|
|
| UpdateTokenTime NtfTokenId SystemDate
|
|
| CreateSubscription NtfSubRec
|
|
| SubscriptionStatus NtfSubscriptionId NtfSubStatus NtfAssociatedService
|
|
| DeleteSubscription NtfSubscriptionId
|
|
| SetNtfService SMPServer (Maybe ServiceId)
|
|
deriving (Show)
|
|
|
|
instance StrEncoding NtfStoreLogRecord where
|
|
strEncode = \case
|
|
CreateToken tknRec -> strEncode (Str "TCREATE", tknRec)
|
|
TokenStatus tknId tknStatus -> strEncode (Str "TSTATUS", tknId, tknStatus)
|
|
UpdateToken tknId token regCode -> strEncode (Str "TUPDATE", tknId, token, regCode)
|
|
TokenCron tknId cronInt -> strEncode (Str "TCRON", tknId, cronInt)
|
|
DeleteToken tknId -> strEncode (Str "TDELETE", tknId)
|
|
UpdateTokenTime tknId ts -> strEncode (Str "TTIME", tknId, ts)
|
|
CreateSubscription subRec -> strEncode (Str "SCREATE", subRec)
|
|
SubscriptionStatus subId subStatus serviceAssoc -> strEncode (Str "SSTATUS", subId, subStatus) <> serviceStr
|
|
where
|
|
serviceStr = if serviceAssoc then " service=" <> strEncode True else ""
|
|
DeleteSubscription subId -> strEncode (Str "SDELETE", subId)
|
|
SetNtfService srv serviceId -> strEncode (Str "SERVICE", srv) <> " service=" <> maybe "off" strEncode serviceId
|
|
strP =
|
|
A.choice
|
|
[ "TCREATE " *> (CreateToken <$> strP),
|
|
"TSTATUS " *> (TokenStatus <$> strP_ <*> strP),
|
|
"TUPDATE " *> (UpdateToken <$> strP_ <*> strP_ <*> strP),
|
|
"TCRON " *> (TokenCron <$> strP_ <*> strP),
|
|
"TDELETE " *> (DeleteToken <$> strP),
|
|
"TTIME " *> (UpdateTokenTime <$> strP_ <*> strP),
|
|
"SCREATE " *> (CreateSubscription <$> strP),
|
|
"SSTATUS " *> (SubscriptionStatus <$> strP_ <*> strP <*> (fromMaybe False <$> optional (" service=" *> strP))),
|
|
"SDELETE " *> (DeleteSubscription <$> strP),
|
|
"SERVICE " *> (SetNtfService <$> strP <* " service=" <*> ("off" $> Nothing <|> strP))
|
|
]
|
|
|
|
logNtfStoreRecord :: StoreLog 'WriteMode -> NtfStoreLogRecord -> IO ()
|
|
logNtfStoreRecord = writeStoreLogRecord
|
|
{-# INLINE logNtfStoreRecord #-}
|
|
|
|
logCreateToken :: StoreLog 'WriteMode -> NtfTknRec -> IO ()
|
|
logCreateToken s = logNtfStoreRecord s . CreateToken
|
|
|
|
logTokenStatus :: StoreLog 'WriteMode -> NtfTokenId -> NtfTknStatus -> IO ()
|
|
logTokenStatus s tknId tknStatus = logNtfStoreRecord s $ TokenStatus tknId tknStatus
|
|
|
|
logUpdateToken :: StoreLog 'WriteMode -> NtfTokenId -> DeviceToken -> NtfRegCode -> IO ()
|
|
logUpdateToken s tknId token regCode = logNtfStoreRecord s $ UpdateToken tknId token regCode
|
|
|
|
logTokenCron :: StoreLog 'WriteMode -> NtfTokenId -> Word16 -> IO ()
|
|
logTokenCron s tknId cronInt = logNtfStoreRecord s $ TokenCron tknId cronInt
|
|
|
|
logDeleteToken :: StoreLog 'WriteMode -> NtfTokenId -> IO ()
|
|
logDeleteToken s tknId = logNtfStoreRecord s $ DeleteToken tknId
|
|
|
|
logUpdateTokenTime :: StoreLog 'WriteMode -> NtfTokenId -> SystemDate -> IO ()
|
|
logUpdateTokenTime s tknId t = logNtfStoreRecord s $ UpdateTokenTime tknId t
|
|
|
|
logCreateSubscription :: StoreLog 'WriteMode -> NtfSubRec -> IO ()
|
|
logCreateSubscription s = logNtfStoreRecord s . CreateSubscription
|
|
|
|
logSubscriptionStatus :: StoreLog 'WriteMode -> (NtfSubscriptionId, NtfSubStatus, NtfAssociatedService) -> IO ()
|
|
logSubscriptionStatus s (subId, subStatus, serviceAssoc) = logNtfStoreRecord s $ SubscriptionStatus subId subStatus serviceAssoc
|
|
|
|
logDeleteSubscription :: StoreLog 'WriteMode -> NtfSubscriptionId -> IO ()
|
|
logDeleteSubscription s subId = logNtfStoreRecord s $ DeleteSubscription subId
|
|
|
|
logSetNtfService :: StoreLog 'WriteMode -> SMPServer -> Maybe ServiceId -> IO ()
|
|
logSetNtfService s srv serviceId = logNtfStoreRecord s $ SetNtfService srv serviceId
|
|
|
|
readWriteNtfSTMStore :: Bool -> FilePath -> NtfSTMStore -> IO (StoreLog 'WriteMode)
|
|
readWriteNtfSTMStore tty = readWriteStoreLog (readNtfStore tty) writeNtfStore
|
|
|
|
readNtfStore :: Bool -> FilePath -> NtfSTMStore -> IO ()
|
|
readNtfStore tty f st = readLogLines tty f $ \_ -> processLine
|
|
where
|
|
processLine s = either printError procNtfLogRecord (strDecode s)
|
|
where
|
|
printError e = B.putStrLn $ "Error parsing log: " <> B.pack e <> " - " <> B.take 100 s
|
|
procNtfLogRecord = \case
|
|
CreateToken r@NtfTknRec {ntfTknId} -> do
|
|
tkn <- mkTknData r
|
|
atomically $ stmAddNtfToken st ntfTknId tkn
|
|
TokenStatus tknId status -> do
|
|
tkn_ <- stmGetNtfTokenIO st tknId
|
|
forM_ tkn_ $ \tkn@NtfTknData {tknStatus} -> do
|
|
atomically $ writeTVar tknStatus status
|
|
when (status == NTActive) $ void $ atomically $ stmRemoveInactiveTokenRegistrations st tkn
|
|
UpdateToken tknId token' tknRegCode -> do
|
|
stmGetNtfTokenIO st tknId
|
|
>>= mapM_
|
|
( \tkn@NtfTknData {tknStatus} -> do
|
|
atomically $ stmRemoveTokenRegistration st tkn
|
|
atomically $ writeTVar tknStatus NTRegistered
|
|
atomically $ stmAddNtfToken st tknId tkn {token = token', tknRegCode}
|
|
)
|
|
TokenCron tknId cronInt ->
|
|
stmGetNtfTokenIO st tknId
|
|
>>= mapM_ (\NtfTknData {tknCronInterval} -> atomically $ writeTVar tknCronInterval cronInt)
|
|
DeleteToken tknId ->
|
|
atomically $ void $ stmDeleteNtfToken st tknId
|
|
UpdateTokenTime tknId t ->
|
|
stmGetNtfTokenIO st tknId
|
|
>>= mapM_ (\NtfTknData {tknUpdatedAt} -> atomically $ writeTVar tknUpdatedAt $ Just t)
|
|
CreateSubscription r@NtfSubRec {tokenId, ntfSubId} -> do
|
|
sub <- mkSubData r
|
|
atomically (stmAddNtfSubscription st ntfSubId sub) >>= \case
|
|
Just () -> pure ()
|
|
Nothing -> B.putStrLn $ "Warning: no token " <> enc tokenId <> ", subscription " <> enc ntfSubId
|
|
where
|
|
enc = B64.encode . unEntityId
|
|
SubscriptionStatus subId status serviceAssoc -> do
|
|
stmGetNtfSubscriptionIO st subId >>= mapM_ update
|
|
where
|
|
update NtfSubData {subStatus, ntfServiceAssoc} = atomically $ do
|
|
writeTVar subStatus status
|
|
writeTVar ntfServiceAssoc serviceAssoc
|
|
DeleteSubscription subId ->
|
|
atomically $ stmDeleteNtfSubscription st subId
|
|
SetNtfService srv serviceId ->
|
|
atomically $ stmSetNtfService st srv serviceId
|
|
|
|
writeNtfStore :: StoreLog 'WriteMode -> NtfSTMStore -> IO ()
|
|
writeNtfStore s NtfSTMStore {tokens, subscriptions, ntfServices} = do
|
|
mapM_ (logCreateToken s <=< mkTknRec) =<< readTVarIO tokens
|
|
mapM_ (logCreateSubscription s <=< mkSubRec) =<< readTVarIO subscriptions
|
|
mapM_ (\(srv, serviceId) -> logSetNtfService s srv $ Just serviceId) . M.assocs =<< readTVarIO ntfServices
|