rtorrent-rpc-0.3.0.0: Network/RTorrent/Command/Internals.hs
{-# LANGUAGE TypeOperators, TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-|
Module : Command.Internals
Copyright : (c) Kai Lindholm, 2014, 2025
License : MIT
Maintainer : megantti@gmail.com
Stability : experimental
-}
module Network.RTorrent.Command.Internals (
(:*:)(..)
, Command (Ret, commandCall, commandValue, levels)
, AnyCommand (..)
, RTMethodCall (..)
, mkRTMethodCall
, parseSingle
, parseValue
, getArray
, single
) where
import Control.Applicative
import Control.Monad.Identity
import Control.Monad ((<=<), zipWithM)
import qualified Data.Map as M
import qualified Data.Vector as V
import qualified Data.Text as T
import Data.Vector.Split (splitPlaces)
import Network.RTorrent.Value
-- | A strict 2-tuple for easy combining of commands.
data (:*:) a b = (:*:) !a !b
infixr 6 :*:
instance (Show a, Show b) => Show (a :*: b) where
show (a :*: b) = show a ++ " :*: " ++ show b
instance (Command a, Command b) => Command (a :*: b) where
type Ret (a :*: b) = Ret a :*: Ret b
commandCall (a :*: b) = RTMethodCall (val a <> val b)
where
val :: Command c => c -> V.Vector (T.Text, V.Vector Value)
val = runRTMethodCall . commandCall
commandValue (a :*: b) (ValueArray xs) =
(:*:) <$> (commandValue a . ValueArray $ as)
<*> (commandValue b . ValueArray $ bs)
where
(as, bs) = V.splitAt (levels a) xs
commandValue _ _ = fail "commandValue in Command (a :*: b) instance failed"
levels (a :*: b) = levels a + levels b
-- Helpers for values
getArray :: (Monad m, MonadFail m) => Value -> m (V.Vector Value)
getArray (ValueArray ar) = return ar
getArray _ = fail "getArray in Network.RTorrent.Commands failed"
-- | Extract a value from a singleton array.
single :: (Monad m, MonadFail m) => Value -> m Value
single (ValueArray ar) = if V.null ar
then fail "Array has no values"
else return $ V.head ar
single v = fail $ "Failed to match a singleton array, got: " ++ show v
-- | Try to parse a 'Value' as any 'RpcType a'.
parseValue :: (Monad m, MonadFail m, RpcType a) => Value -> m a
parseValue = handleError (\e -> fail $ "parseValue failed: " ++ e) . fromValue
-- | Parse a 'Value' wrapped in a singleton array.
parseSingle :: (Monad m, MonadFail m, RpcType a) => Value -> m a
parseSingle = parseValue <=< single
-- | A newtype wrapper for method calls.
--
-- You shouldn't directly use the constructor
-- if you don't know what you are doing.
newtype RTMethodCall = RTMethodCall {
runRTMethodCall :: V.Vector (T.Text, V.Vector Value)
}
deriving Show
-- | Make a command that should be used when defining 'commandCall'.
mkRTMethodCall :: T.Text -- ^ The name of the method (i.e. get_up_rate)
-> V.Vector Value -- ^ List of parameters
-> RTMethodCall
mkRTMethodCall name params = RTMethodCall . V.singleton $ (name, params)
-- | A typeclass for commands that can be send to RTorrent.
class Command a where
-- | Return type of the command.
type Ret a
-- | Construct a request.
commandCall :: a -> RTMethodCall
-- | Parse the resulting value.
commandValue :: (Applicative m, Monad m, MonadFail m) =>
a -> Value -> m (Ret a)
levels :: a -> Int
levels _ = 1
-- | Existential wrapper for any command.
--
-- @Command@s wrapped in @AnyCommand@ won't parse their results.
--
-- @AnyCommand@ can be used when you want to call multiple commands
-- but don't care about their return values.
data AnyCommand where
AnyCommand :: Command a => a -> AnyCommand
instance Command AnyCommand where
type Ret AnyCommand = Value
commandCall (AnyCommand cmd) = commandCall cmd
commandValue _ = single
levels (AnyCommand cmd) = levels cmd
instance Command a => Command (V.Vector a) where
type Ret (V.Vector a) = V.Vector (Ret a)
commandCall = RTMethodCall . V.concatMap (runRTMethodCall . commandCall)
commandValue cmds =
V.zipWithM (\cmd -> commandValue cmd . ValueArray) cmds
. V.fromList
. splitPlaces (map levels (V.toList cmds))
<=< getArray
levels = sum . V.map levels
instance Command a => Command [a] where
type Ret [a] = [Ret a]
commandCall = RTMethodCall
. V.concatMap (runRTMethodCall . commandCall)
. V.fromList
commandValue cmds =
zipWithM (\cmd -> commandValue cmd . ValueArray) cmds
. splitPlaces (map levels cmds)
<=< getArray
levels = sum . map levels