module Reddit.Routes.Run
( runRoute ) where
import Reddit.Types.Error
import Reddit.Types.Reddit
import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Concurrent.STM
import Control.Monad (liftM, when)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Except
import Control.Monad.Trans.State
import Data.Aeson (FromJSON(..))
import Data.ByteString.Lazy (ByteString)
import Data.Time.Clock
import Network.API.Builder.Routes (Route)
import Network.HTTP.Conduit
import Network.HTTP.Types
import Prelude
import qualified Network.API.Builder as API
runRoute :: (MonadIO m, FromJSON a) => Route -> RedditT m a
runRoute route = do
RateLimits limiting _ <- RedditT $ API.liftState get >>= liftIO . readTVarIO
if limiting
then runRouteWithLimiting route
else RedditT $ API.unwrapJSON `liftM` API.runRoute route
runRouteWithLimiting :: (MonadIO m, FromJSON a) => Route -> RedditT m a
runRouteWithLimiting route = do
RateLimits _ rli <- RedditT $ API.liftState get >>= liftIO . readTVarIO
case rli of
Just r -> do
when (needsReset r) $ waitForReset $ resetTime r
requestAndUpdate
Nothing -> requestAndUpdate
where
requestAndUpdate = do
resp <- nest $ RedditT $ API.routeResponse route
case resp of
Left (API.APIError (RateLimitError resetIn _)) -> do
updateFromZero resetIn
runRouteWithLimiting route
Left x -> RedditT $ ExceptT $ return $ Left x
Right x -> do
case API.unwrapJSON <$> API.receive x of
Left (API.APIError (RateLimitError resetIn _)) -> do
updateFromZero resetIn
runRouteWithLimiting route
Left y -> RedditT $ ExceptT $ return $ Left y
Right y -> do
updateRateLimitInfo $ responseHeaders x
return y
updateRateLimitInfo :: MonadIO m => ResponseHeaders -> RedditT m ()
updateRateLimitInfo hs = do
time <- liftIO getCurrentTime
case headersToRateLimitInfo hs time of
Just rli ->
RedditT $ do
r <- API.liftState get
liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just rli)
Nothing -> return ()
updateFromZero :: MonadIO m => Integer -> RedditT m ()
updateFromZero resetIn = do
time <- liftIO getCurrentTime
let resetAt = addUTCTime (fromInteger resetIn) time
RedditT $ do
r <- API.liftState get
liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just $ RateLimitInfo 0 0 resetAt)
waitForReset :: MonadIO m => UTCTime -> RedditT m ()
waitForReset dt = do
time <- liftIO getCurrentTime
let wait = floor $ diffUTCTime dt time
liftIO $ threadDelay $ (wait + 5) * 1000000
needsReset :: RateLimitInfo -> Bool
needsReset = (<= 0) . remaining