{-# LANGUAGE ExistentialQuantification #-}
module TDLib.EventLoop
(
runTDLibEventLoop,
runCommand,
loop,
)
where
import Control.Concurrent (forkIO, killThread)
import Control.Concurrent.Chan.Unagi
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import Control.Monad.Loops
import Data.Aeson
import qualified Data.ByteString.Char8 as BS
import Data.ByteString.Lazy (toStrict)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashMap.Strict as HM
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as M
import Data.Maybe
import Polysemy
import TDLib.Effect
import TDLib.Errors
import TDLib.Generated.Types hiding (Error (..))
import TDLib.TDJson
import TDLib.Types.Common hiding (Error)
type Ans = TVar (IntMap Value)
type Locks = TVar (IntMap ())
type Counter = TVar Int
newCounter :: IO Counter
newCounter :: IO Counter
newCounter = Int -> IO Counter
forall a. a -> IO (TVar a)
newTVarIO 0
countUp :: Counter -> IO Int
countUp :: Counter -> IO Int
countUp counter :: Counter
counter = STM Int -> IO Int
forall a. STM a -> IO a
atomically (STM Int -> IO Int) -> STM Int -> IO Int
forall a b. (a -> b) -> a -> b
$ do
Int
i <- Counter -> STM Int
forall a. TVar a -> STM a
readTVar Counter
counter
let n :: Int
n = Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1
Counter -> Int -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar Counter
counter Int
n
Int -> STM Int
forall (f :: * -> *) a. Applicative f => a -> f a
pure Int
n
lookupExtra :: Value -> Maybe Int
v :: Value
v@(Object hm :: Object
hm) =
case Text -> Object -> Maybe Value
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup "@extra" Object
hm of
Nothing -> Maybe Int
forall a. Maybe a
Nothing
Just v' :: Value
v' -> case Value -> Result Int
forall a. FromJSON a => Value -> Result a
fromJSON Value
v' of
Error _ -> TDLibError -> Maybe Int
forall a e. Exception e => e -> a
throw (TDLibError -> Maybe Int) -> TDLibError -> Maybe Int
forall a b. (a -> b) -> a -> b
$ Value -> TDLibError
ExtraFieldNotInt Value
v
Success i :: Int
i -> Int -> Maybe Int
forall a. a -> Maybe a
Just Int
i
lookupExtra _ = [Char] -> Maybe Int
forall a. HasCallStack => [Char] -> a
error "Not a object"
insertAns :: Int -> Locks -> Ans -> Value -> STM ()
insertAns :: Int -> Locks -> Ans -> Value -> STM ()
insertAns index :: Int
index lck :: Locks
lck ans :: Ans
ans val :: Value
val = do
IntMap ()
m <- Locks -> STM (IntMap ())
forall a. TVar a -> STM a
readTVar Locks
lck
let r :: Maybe ()
r = Int -> IntMap () -> Maybe ()
forall a. Int -> IntMap a -> Maybe a
M.lookup Int
index IntMap ()
m
if Maybe () -> Bool
forall a. Maybe a -> Bool
isJust Maybe ()
r
then Locks -> IntMap () -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar Locks
lck (Int -> IntMap () -> IntMap ()
forall a. Int -> IntMap a -> IntMap a
M.delete Int
index IntMap ()
m)
else Ans -> (IntMap Value -> IntMap Value) -> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar Ans
ans (Int -> Value -> IntMap Value -> IntMap Value
forall a. Int -> a -> IntMap a -> IntMap a
M.insert Int
index Value
val)
waitRead :: Int -> Ans -> STM Value
waitRead :: Int -> Ans -> STM Value
waitRead index :: Int
index ans :: Ans
ans = do
IntMap Value
m <- Ans -> STM (IntMap Value)
forall a. TVar a -> STM a
readTVar Ans
ans
let mr :: Maybe Value
mr = Int -> IntMap Value -> Maybe Value
forall a. Int -> IntMap a -> Maybe a
M.lookup Int
index IntMap Value
m
case Maybe Value
mr of
Nothing -> STM Value
forall a. STM a
retry
Just v :: Value
v -> do
Ans -> IntMap Value -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar Ans
ans (Int -> IntMap Value -> IntMap Value
forall a. Int -> IntMap a -> IntMap a
M.delete Int
index IntMap Value
m)
Value -> STM Value
forall (f :: * -> *) a. Applicative f => a -> f a
pure Value
v
lock :: Int -> Locks -> STM ()
lock :: Int -> Locks -> STM ()
lock index :: Int
index lck :: Locks
lck = Locks -> (IntMap () -> IntMap ()) -> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar Locks
lck (Int -> () -> IntMap () -> IntMap ()
forall a. Int -> a -> IntMap a -> IntMap a
M.insert Int
index ())
readAns :: Int -> Locks -> Ans -> IO Value
readAns :: Int -> Locks -> Ans -> IO Value
readAns index :: Int
index lck :: Locks
lck ans :: Ans
ans =
IO Value
readV IO Value -> IO () -> IO Value
forall a b. IO a -> IO b -> IO a
`onException` IO ()
cleanUp
where
readV :: IO Value
readV = STM Value -> IO Value
forall a. STM a -> IO a
atomically (STM Value -> IO Value) -> STM Value -> IO Value
forall a b. (a -> b) -> a -> b
$ do
Int -> Ans -> STM Value
waitRead Int
index Ans
ans
cleanUp :: IO ()
cleanUp = STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
IntMap Value
m <- Ans -> STM (IntMap Value)
forall a. TVar a -> STM a
readTVar Ans
ans
let ma :: Maybe Value
ma = Int -> IntMap Value -> Maybe Value
forall a. Int -> IntMap a -> Maybe a
M.lookup Int
index IntMap Value
m
case Maybe Value
ma of
Nothing -> Int -> Locks -> STM ()
lock Int
index Locks
lck
_ -> Ans -> IntMap Value -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar Ans
ans (Int -> IntMap Value -> IntMap Value
forall a. Int -> IntMap a -> IntMap a
M.delete Int
index IntMap Value
m)
loop :: Client -> Double -> Locks -> Ans -> InChan Update -> IO a
loop :: Client -> Double -> Locks -> Ans -> InChan Update -> IO a
loop client :: Client
client timeout :: Double
timeout lck :: Locks
lck ans :: Ans
ans chan :: InChan Update
chan = IO () -> IO a
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (IO () -> IO a) -> IO () -> IO a
forall a b. (a -> b) -> a -> b
$ do
ByteString
bs <- IO (Maybe ByteString) -> IO ByteString
forall (m :: * -> *) a. Monad m => m (Maybe a) -> m a
untilJust (IO (Maybe ByteString) -> IO ByteString)
-> IO (Maybe ByteString) -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Client -> Double -> IO (Maybe ByteString)
clientReceive Client
client Double
timeout
let m :: Maybe Value
m = ByteString -> Maybe Value
forall a. FromJSON a => ByteString -> Maybe a
decodeStrict ByteString
bs
case Maybe Value
m of
Nothing -> TDLibError -> IO ()
forall e a. Exception e => e -> IO a
throwIO (TDLibError -> IO ()) -> TDLibError -> IO ()
forall a b. (a -> b) -> a -> b
$ ByteString -> TDLibError
UnableToParseJSON ByteString
bs
Just v :: Value
v -> do
case Value -> Maybe Int
lookupExtra Value
v of
Nothing -> do
let r :: Result Update
r = Value -> Result Update
forall a. FromJSON a => Value -> Result a
fromJSON Value
v
case Result Update
r of
Error _ -> TDLibError -> IO ()
forall e a. Exception e => e -> IO a
throwIO (TDLibError -> IO ()) -> TDLibError -> IO ()
forall a b. (a -> b) -> a -> b
$ Value -> TDLibError
UnableToParseValue Value
v
Success u :: Update
u -> InChan Update -> Update -> IO ()
forall a. InChan a -> a -> IO ()
writeChan InChan Update
chan Update
u
Just i :: Int
i -> STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ Int -> Locks -> Ans -> Value -> STM ()
insertAns Int
i Locks
lck Ans
ans Value
v
runCommand :: (ToJSON a, FromJSON b, FromJSON err) => Client -> Int -> Locks -> Ans -> a -> IO (err ∪ b)
runCommand :: Client -> Int -> Locks -> Ans -> a -> IO (err ∪ b)
runCommand client :: Client
client i :: Int
i lck :: Locks
lck ans :: Ans
ans cmd :: a
cmd =
case a -> Value
forall a. ToJSON a => a -> Value
toJSON a
cmd of
Object hm :: Object
hm -> do
let o' :: Value
o' = Object -> Value
Object (Object
hm Object -> Object -> Object
forall a. Semigroup a => a -> a -> a
<> [(Text, Value)] -> Object
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList [("@extra" Text -> Int -> (Text, Value)
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= Int
i)])
Client -> ByteString -> IO ()
clientSend Client
client (ByteString -> ByteString
toStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Value -> ByteString
forall a. ToJSON a => a -> ByteString
encode Value
o')
Value
v <- Int -> Locks -> Ans -> IO Value
readAns Int
i Locks
lck Ans
ans
let m :: Result (err ∪ b)
m = Value -> Result (err ∪ b)
forall a. FromJSON a => Value -> Result a
fromJSON Value
v
case Result (err ∪ b)
m of
Error _ -> TDLibError -> IO (err ∪ b)
forall e a. Exception e => e -> IO a
throwIO (TDLibError -> IO (err ∪ b)) -> TDLibError -> IO (err ∪ b)
forall a b. (a -> b) -> a -> b
$ Value -> TDLibError
UnableToParseValue Value
v
Success r :: err ∪ b
r -> (err ∪ b) -> IO (err ∪ b)
forall (f :: * -> *) a. Applicative f => a -> f a
pure err ∪ b
r
runTDLibEventLoop :: Members '[Embed IO] r => Double -> InChan Update -> Sem (TDLib ': r) a -> Sem r a
runTDLibEventLoop :: Double -> InChan Update -> Sem (TDLib : r) a -> Sem r a
runTDLibEventLoop timeout :: Double
timeout chan :: InChan Update
chan m :: Sem (TDLib : r) a
m = do
Locks
lck <- IO Locks -> Sem r Locks
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO Locks -> Sem r Locks) -> IO Locks -> Sem r Locks
forall a b. (a -> b) -> a -> b
$ IntMap () -> IO Locks
forall a. a -> IO (TVar a)
newTVarIO IntMap ()
forall a. Monoid a => a
mempty
Ans
ans <- IO Ans -> Sem r Ans
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO Ans -> Sem r Ans) -> IO Ans -> Sem r Ans
forall a b. (a -> b) -> a -> b
$ IntMap Value -> IO Ans
forall a. a -> IO (TVar a)
newTVarIO IntMap Value
forall a. Monoid a => a
mempty
Client
c <- IO Client -> Sem r Client
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed IO Client
newClient
ThreadId
tid <- IO ThreadId -> Sem r ThreadId
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO ThreadId -> Sem r ThreadId) -> IO ThreadId -> Sem r ThreadId
forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ Client -> Double -> Locks -> Ans -> InChan Update -> IO ()
forall a. Client -> Double -> Locks -> Ans -> InChan Update -> IO a
loop Client
c Double
timeout Locks
lck Ans
ans InChan Update
chan
Counter
counter <- IO Counter -> Sem r Counter
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed IO Counter
newCounter
let runTD :: Sem (TDLib : r) a -> Sem r a
runTD = (forall x (m :: * -> *). TDLib m x -> Sem r x)
-> Sem (TDLib : r) a -> Sem r a
forall (e :: (* -> *) -> * -> *) (r :: [(* -> *) -> * -> *]) a.
FirstOrder e "interpret" =>
(forall x (m :: * -> *). e m x -> Sem r x)
-> Sem (e : r) a -> Sem r a
interpret ((forall x (m :: * -> *). TDLib m x -> Sem r x)
-> Sem (TDLib : r) a -> Sem r a)
-> (forall x (m :: * -> *). TDLib m x -> Sem r x)
-> Sem (TDLib : r) a
-> Sem r a
forall a b. (a -> b) -> a -> b
$ \case
RunCmd cmd -> do
Int
i <- IO Int -> Sem r Int
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO Int -> Sem r Int) -> IO Int -> Sem r Int
forall a b. (a -> b) -> a -> b
$ Counter -> IO Int
countUp Counter
counter
IO (err ∪ res) -> Sem r x
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO (err ∪ res) -> Sem r x) -> IO (err ∪ res) -> Sem r x
forall a b. (a -> b) -> a -> b
$ Client -> Int -> Locks -> Ans -> cmd -> IO (err ∪ res)
forall a b err.
(ToJSON a, FromJSON b, FromJSON err) =>
Client -> Int -> Locks -> Ans -> a -> IO (err ∪ b)
runCommand Client
c Int
i Locks
lck Ans
ans cmd
cmd
SetVerbosity verbosity -> do
IO () -> Sem r x
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO () -> Sem r x) -> IO () -> Sem r x
forall a b. (a -> b) -> a -> b
$ Verbosity -> IO ()
setLogVerbosityLevel Verbosity
verbosity
SetFatalErrorCallback callback -> do
IO () -> Sem r x
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO () -> Sem r x) -> IO () -> Sem r x
forall a b. (a -> b) -> a -> b
$ (ByteString -> IO ()) -> IO ()
setLogFatalErrorCallback ByteString -> IO ()
callback
SetLogPath path -> do
IO Bool -> Sem r x
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO Bool -> Sem r x) -> IO Bool -> Sem r x
forall a b. (a -> b) -> a -> b
$ ByteString -> IO Bool
setLogFilePath ByteString
path
SetLogMaxSize size -> do
IO () -> Sem r x
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO () -> Sem r x) -> IO () -> Sem r x
forall a b. (a -> b) -> a -> b
$ Int64 -> IO ()
setLogMaxFileSize Int64
size
a
r <- Sem (TDLib : r) a -> Sem r a
runTD Sem (TDLib : r) a
m
IO () -> Sem r ()
forall (m :: * -> *) (r :: [(* -> *) -> * -> *]) a.
Member (Embed m) r =>
m a -> Sem r a
embed (IO () -> Sem r ()) -> IO () -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
ThreadId -> IO ()
killThread ThreadId
tid
Client -> IO ()
destroyClient Client
c
a -> Sem r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
r