packages feed

rtorrent-rpc (empty) → 0.2.0.0

raw patch · 16 files changed

+1524/−0 lines, 16 filesdep +attoparsecdep +basedep +blaze-buildersetup-changed

Dependencies added: attoparsec, base, blaze-builder, blaze-textual, bytestring, deepseq, haxr, mtl, network, split

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Kai Lindholm++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Network/RTorrent.hs view
@@ -0,0 +1,16 @@+{-|+Module      : RTorrent+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++A module that re-exports "Network.RTorrent.RPC" for convenience.++-}++module Network.RTorrent (+    module Network.RTorrent.RPC+) where++import Network.RTorrent.RPC
+ Network/RTorrent/Action.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE TypeOperators, TypeFamilies, RankNTypes #-}++{-|+Module      : Action+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++@Action@ is a command acting on various kinds of objects:++* torrents ('Network.RTorrent.Torrent.TorrentAction')+* files ('Network.RTorrent.File.FileAction')+* peers ('Network.RTorrent.Peer.PeerAction')+* trackers ('Network.RTorrent.Tracker.TrackerAction').++They all have the property that they can be executed on a single object or+on a group of objects.++For example, ++@+callRTorrent "localhost" 5000 $ some_action (some_id :: SomeId)+@+is a valid thing to write when @some@ is one of the previous objects.++To call an action on all torrents, you can use 'Network.RTorrent.Torrent.allTorrents', so that++@+callRTorrent "localhost" 5000 $ allTorrents getTorrentId+@+will return a list of torrent ids.++To call a action on other types of objects, you can use 'Network.RTorrent.Peer.allPeers',+'Network.RTorrent.File.allFiles', or 'Network.RTorrent.Tracker.allTrackers',+which will act on all peers, files, or trackers that are associated to a torrent.+They will also return ids for each object.+Then for example ++> allFiles getFileSizeBytes :: TorrentId -> TorrentAction [FileId :*: Int]+is an action that will return a list of ids and file sizes when run on a torrent.+These can further be used with 'Network.RTorrent.Torrent.allTorrents'.++To combine actions, you can use '<+>' and 'sequenceActions'+which correspond to ':*:' and @[]@ for commands.++In order to write new actions, 'simpleAction' can be used.++-}++module Network.RTorrent.Action (+      Action +    , simpleAction++    , sequenceActions+    , (<+>)++    , Param (..)+    , ActionB (..)++) where++import Network.RTorrent.Action.Internals
+ Network/RTorrent/Action/Internals.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE TypeOperators, TypeFamilies, RankNTypes #-}++{-|+Module      : Action.Internals+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++-}++module Network.RTorrent.Action.Internals (+      Action (..)+    , simpleAction++    , sequenceActions+    , (<+>)++    , Param (..)+    , ActionB (..)++    , AllAction (..)+    , allToMulti +) where++import Control.Applicative+import Control.Monad++import Data.Monoid+import Data.Traversable hiding (mapM)++import Network.XmlRpc.Internals+import Network.RTorrent.Command.Internals+import Network.RTorrent.Priority++-- | A type for actions that can act on different things like torrents and files.+--+-- @a@ is the return type.+data Action i a +    = Action [(String, [Param])] (forall m. (Monad m, Applicative m) => Value -> m a) i++-- | Wrapper to get monoid and applicative instances.+newtype ActionB i a = ActionB { runActionB :: i -> Action i a} ++-- | A simple action that can be used when constructing new ones.+-- +-- Watch out for using @Bool@ as @a@ since using it with this function will probably result in an error,+-- since RTorrent actually returns 0 or 1 instead of a bool.+-- One workaround is to get an @Int@ and use @Bool@'s @Enum@ instance.+simpleAction :: XmlRpcType a => +       String+    -> [Param] +    -> i+    -> Action i a+simpleAction cmd params = Action [(cmd, params)] parseSingle++instance Functor (Action i) where+    fmap f (Action cmds p fid) = Action cmds (fmap f . p) fid++instance Functor (ActionB i) where+    fmap f = ActionB . (fmap f .) . runActionB++instance Applicative (ActionB i) where+    pure a = ActionB $ Action [] (const (pure a))  ++    (ActionB a) <*> (ActionB b) = ActionB $ \tid -> let +        parse :: (Monad m, Applicative m) => (Value -> m (a -> b)) -> (Value -> m a) -> Value -> m b+        parse parseA parseB arr = do+            (valsA, valsB) <- splitAt len <$> getArray arr+            parseA (ValueArray valsA) +              <*> parseB (ValueArray valsB) +        len = length cmdsA+        Action cmdsA pA _ = a tid+        Action cmdsB pB _ = b tid+      in Action (cmdsA ++ cmdsB) (parse pA pB) tid++instance Monoid a => Monoid (ActionB i a) where+    mempty = pure mempty+    mappend = liftA2 mappend  ++instance XmlRpcType i => Command (Action i a) where+    type Ret (Action i a) = a++    commandCall (Action cmds _ tid) = +          RTMethodCall +        . ValueArray+        . concatMap (\(cmd, params) -> +                        getArray'+                      . runRTMethodCall $ mkRTMethodCall cmd +                                    (toValue tid : map toValue params))+        $ cmds++    commandValue (Action _ parse _) = parse+    levels (Action cmds _ _) = length cmds++-- | Parameters for actions.+data Param = +    PString String+  | PInt Int+  | PTorrentPriority TorrentPriority+  | PFilePriority FilePriority++instance Show Param where+    show (PString str) = show str+    show (PInt i) = show i+    show (PTorrentPriority p) = show (fromEnum p)+    show (PFilePriority p) = show (fromEnum p)++instance XmlRpcType Param where+    toValue (PString str) = toValue str+    toValue (PInt i) = toValue i+    toValue (PTorrentPriority p) = toValue p+    toValue (PFilePriority p) = toValue p++    fromValue = fail "No fromValue for Params"+    getType _ = TUnknown++-- | Sequence multiple actions, for example with @f = []@.+sequenceActions :: Traversable f => f (i -> Action i a) -> i -> Action i (f a)+sequenceActions = runActionB . traverse ActionB++infixr 6 <+>+-- | Combine two actions to get a new one.+(<+>) :: (i -> Action i a) -> (i -> Action i b) -> i -> Action i (a :*: b)+a <+> b = runActionB $ (:*:) <$> ActionB a <*> ActionB b++data AllAction i a = AllAction i String (i -> Action i a)++makeMultiCall :: [(String, [Param])] -> [String]+makeMultiCall = ("" :) +              . map (\(cmd, params) -> cmd ++ "=" ++ makeList params)+  where+    makeList :: Show a => [a] -> String+    makeList params = ('{' :) . go params $ "}"+      where+        go :: Show a => [a] -> ShowS+        go [x] = shows x +        go (x:xs) = shows x . (',' :) . go xs+        go [] = id++wrapForParse :: Monad m => Value -> m [Value]+wrapForParse = mapM ( +                 return . ValueArray +                 . map (ValueArray . (:[])) +                 <=< getArray) +               <=< getArray <=< single <=< single++allToMulti :: AllAction i a -> j -> Action j [a]+allToMulti (AllAction emptyId multicall action) = +    Action [(multicall, map PString $ makeMultiCall cmds)] +           (mapM parse <=< wrapForParse)+  where +    Action cmds parse _ = action emptyId+    +instance Command (AllAction i a) where+    type Ret (AllAction i a) = [a]+    commandCall (AllAction emptyId multicall action) = +                      mkRTMethodCall multicall+                    . map ValueString+                    . makeMultiCall +                    $ cmds+      where+        Action cmds _ _ = action emptyId++    commandValue (AllAction emptyId _ action) = +        mapM parse <=< wrapForParse+      where+        Action _ parse _ = action emptyId+
+ Network/RTorrent/Command.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE TypeOperators #-}++{-|+Module      : Command+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental+-}++module Network.RTorrent.Command (+      (:*:)(..)+    , Command (Ret) ++    -- * AnyCommand++    , AnyCommand (..)+) where++import Network.RTorrent.Command.Internals
+ Network/RTorrent/Command/Internals.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE TypeOperators, TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GADTs #-}++{-|+Module      : Command.Internals+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental+-}++module Network.RTorrent.Command.Internals (+      (:*:)(..)+    , Command (Ret, commandCall, commandValue, levels) ++    , AnyCommand (..)+    +    , RTMethodCall (..)+    , runRTMethodCall+    , mkRTMethodCall+    , parseSingle+    , getArray+    , getArray'+    , single+) where++import Control.Applicative+import Control.DeepSeq+import Control.Monad.Error+import Control.Monad.Identity++import Data.List.Split (splitPlaces)++import Network.XmlRpc.Internals++-- | A strict 2-tuple for easy combining of commands.+data (:*:) a b = (:*:) !a !b+infixr 6 :*:++instance (NFData a, NFData b) => NFData (a :*: b) where+    rnf (a :*: b) = rnf a `seq` rnf b++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 $ ValueArray (val a ++ val b)+        where+          val :: Command c => c -> [Value]+          val = getArray' . runRTMethodCall . commandCall++    commandValue (a :*: b) (ValueArray xs) = +          (:*:) <$> (commandValue a . ValueArray $ as)+                <*> (commandValue b . ValueArray $ bs)+        where+            (as, bs) = 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 => Value -> m [Value]+getArray (ValueArray ar) = return ar+getArray _ = fail "getArray in Network.RTorrent.Commands failed"++getArray' :: Value -> [Value]+getArray' (ValueArray ar) = ar+getArray' _ = error "getArray' in Network.RTorrent.Commands failed"++-- | Extract a value from a singleton array.+single :: Monad m => Value -> m Value+single (ValueArray [ar]) = return ar+single v@(ValueStruct vars) = maybe +    err+    (\(c, s) -> do+        i <- int c+        s' <- str s+        fail $ "Server returned error " ++ show i ++ +                                ": " ++ s')+    (liftA2 (,) (lookup "faultCode" vars)+                (lookup "faultString" vars))+  where+    int (ValueInt i) = return i+    int _ = err+    str (ValueString s) = return s+    str _ = err+    err :: Monad m => m a+    err = fail $ "Failed to match a singleton array, got: " ++ show v+single v = fail $ "Failed to match a singleton array, got: " ++ show v++parseValue :: (Monad m, XmlRpcType a) => Value -> m a+parseValue = fromRight . runIdentity . runErrorT . fromValue +  where+    fromRight (Right r) = return r+    fromRight (Left e) = fail $ "parseValue failed: " ++ e++-- | Parse a value wrapped in two singleton arrays.+parseSingle :: (Monad m, XmlRpcType a) => Value -> m a+parseSingle = parseValue <=< single <=< 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 Value+    deriving Show++runRTMethodCall :: RTMethodCall -> Value+runRTMethodCall (RTMethodCall v) = v++-- | Make a command that should be used when defining 'commandCall'.+mkRTMethodCall :: String -- ^ The name of the method (i.e. get_up_rate)+        -> [Value] -- ^ List of parameters+        -> RTMethodCall+mkRTMethodCall name params = RTMethodCall $ ValueArray [ValueStruct +                        [ ("methodName", ValueString name)+                        , ("params", ValueArray 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) => +        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 <=< single+    levels (AnyCommand cmd) = levels cmd++instance Command a => Command [a] where+    type Ret [a] = [Ret a]+    commandCall = RTMethodCall . ValueArray +                . concatMap ( getArray'+                            . runRTMethodCall . commandCall)+    commandValue cmds = +        zipWithM (\cmd -> commandValue cmd . ValueArray) cmds+        . splitPlaces (map levels cmds) +        <=< getArray +    levels = sum . map levels +
+ Network/RTorrent/CommandList.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE TypeFamilies, RankNTypes #-}++{-|+Module      : Commands+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++A module for defined commands.++To combine multiple commands, use ':*:',+or store them in a list, +as both of these types have 'Command' instances.++-}++module Network.RTorrent.CommandList +  ( module Network.RTorrent.File+  , module Network.RTorrent.Peer+  , module Network.RTorrent.Priority+  , module Network.RTorrent.Torrent+  , module Network.RTorrent.Tracker++  -- * Functions for global variables+  , Global +  , getUpRate +  , getDownRate+  , getDirectory+  , getPid++  , getUploadRate+  , getDownloadRate+  , setUploadRate+  , setDownloadRate+  +  -- * Loading new torrents++  , loadTorrent+  , loadTorrentRaw+  , loadStartTorrent+  , loadStartTorrentRaw++  -- * Constructing new commands++  , commandSimple+  , commandArgs+  , commandInt+  , commandString++  -- * Re-exported from "Network.RTorrent.Action"+  , (<+>)+  , sequenceActions++  -- * Re-exported from "Network.RTorrent.Command"+  , (:*:)(..)+  , AnyCommand (..)+  , Command (Ret)+  )+  where++import Network.XmlRpc.Internals++import Control.Applicative++import Data.ByteString (ByteString)++import Network.RTorrent.Action+import Network.RTorrent.Command.Internals+import Network.RTorrent.File+import Network.RTorrent.Peer+import Network.RTorrent.Priority+import Network.RTorrent.Torrent+import Network.RTorrent.Tracker++-- | Run a command with no arguments.+commandSimple :: XmlRpcType a => String -> Global a+commandSimple cmd = commandArgs cmd []++-- | Run a command with the given arguments.+commandArgs :: XmlRpcType a => String -> [Value] -> Global a+commandArgs = flip $ Global parseSingle++-- | Run a command with the @Int@ given as an argument.+commandInt :: XmlRpcType a => String -> Int -> Global a+commandInt cmd i = commandArgs cmd [ValueInt i]++-- | Run a command with the @String@ given as an argument.+commandString :: XmlRpcType a => +    String  -- ^ Command+    -> String -- ^ Argument+    -> Global a+commandString cmd s = commandArgs cmd [ValueString s]++-- | Get the current up rate, in bytes per second.+getUpRate :: Global Int+getUpRate = commandSimple "get_up_rate"++-- | Get the current down rate, in bytes per second.+getDownRate :: Global Int+getDownRate = commandSimple "get_down_rate"++-- | Get the default download directory.+getDirectory :: Global String+getDirectory = commandSimple "get_directory"++-- | Get the maximum upload rate, in bytes per second.+--+-- @0@ means no limit.+getUploadRate :: Global Int+getUploadRate = commandSimple "get_upload_rate"++-- | Get the maximum download rate, in bytes per second.+--+-- @0@ means no limit.+getDownloadRate :: Global Int+getDownloadRate = commandSimple "get_download_rate"++-- | Set the maximum upload rate, in bytes per second.+setUploadRate :: Int -> Global Int+setUploadRate = commandInt "set_upload_rate"++-- | Set the maximum download rate, in bytes per second.+setDownloadRate :: Int -> Global Int+setDownloadRate = commandInt "set_download_rate"++-- | Get the process id.+getPid :: Global Int+getPid = commandSimple "system.pid"++-- | Load a torrent file.+loadTorrent :: String -- ^ A path / URL+        -> Global Int+loadTorrent = commandString "load"++-- | Load a torrent file.+loadTorrentRaw :: ByteString -- ^ A torrent file as data+        -> Global Int+loadTorrentRaw torrentData = commandArgs "load_raw" [ValueBase64 torrentData] ++-- | Load a torrent file and start downloading it.+loadStartTorrent :: String -- ^ A path / URL+        -> Global Int+loadStartTorrent = commandString "load_start"++-- | Load a torrent file and start downloading it.+loadStartTorrentRaw :: ByteString -- ^ A torrent file as data+        -> Global Int+loadStartTorrentRaw torrentData = commandArgs "load_raw_start" [ValueBase64 torrentData] +    ++-- | Execute a command with a result type @t@.+data Global t = Global (forall m. (Monad m, Applicative m) => Value -> m t) [Value] String+instance Command (Global a) where+    type Ret (Global a) = a+    commandCall (Global _ args cmd) = mkRTMethodCall cmd args+    commandValue (Global parse _ _) = parse++instance Functor Global where+    fmap f (Global g args s) = Global (fmap f . g) args s+
+ Network/RTorrent/File.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE TypeOperators, TypeFamilies #-}++{-|+Module      : File+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++For more info on actions, see "Network.RTorrent.Action".+-}++module Network.RTorrent.File (+    FileId (..)+  , FileInfo (..)+  , FileAction +  , getFilePartial+  , getTorrentFiles+  , allFiles++  -- * Functions dealing with a single variable+  , getFilePath+  , getFileAbsolutePath+  , getFileSizeBytes+  , getFileSizeChunks+  , getFileCompletedChunks+  , getFilePriority+  , setFilePriority+) where++import Control.Applicative+import Control.DeepSeq++import Network.RTorrent.Action.Internals+import Network.RTorrent.Torrent+import Network.RTorrent.Command+import Network.RTorrent.Priority+import Network.XmlRpc.Internals++import Data.List.Split (splitOn)++data FileId = FileId !TorrentId !Int deriving Show++instance XmlRpcType FileId where+    toValue (FileId (TorrentId tid) i) = ValueString $ tid ++ ":f" ++ show i+    fromValue v = return . uncurry FileId =<< parse =<< fromValue v+      where+        parse :: Monad m => String -> m (TorrentId, Int)+        parse str = do+            [hash, i] <- return $ splitOn ":f" str+            return (TorrentId hash, read i)+    getType _ = TString++data FileInfo = FileInfo {+    filePath :: String+  , fileSizeBytes :: !Int+  , fileSizeChunks :: !Int+  , fileCompletedChunks :: !Int+  , filePriority :: !FilePriority+  , fileId :: FileId+} deriving Show++instance NFData FileId where+    rnf (FileId tid i) = rnf tid `seq` rnf i++instance NFData FileInfo where+    rnf (FileInfo fid fp fsb fsc fcc fpt) = +              rnf fp +        `seq` rnf fsb +        `seq` rnf fsc+        `seq` rnf fcc+        `seq` rnf fpt+        `seq` rnf fid++type FileAction = Action FileId++-- | Run the file action on all files that a torrent has.+allFiles :: (FileId -> FileAction a) -> TorrentId -> TorrentAction [FileId :*: a]+allFiles f = fmap addId . (getTorrentId <+> allToMulti (allF f))+  where+    addId (hash :*: files) = +        zipWith (\index -> (:*:) (FileId hash index)) [0..] files +    allF :: (FileId -> FileAction a) -> AllAction FileId a+    allF = AllAction (FileId (TorrentId "") 0) "f.multicall"++-- | Get the file name relative to the torrent base directory.+getFilePath :: FileId -> FileAction String+getFilePath = simpleAction "f.path" []++-- | Get the absolute path.+getFileAbsolutePath :: FileId -> FileAction String+getFileAbsolutePath = simpleAction "f.frozen_path" []++getFileSizeBytes :: FileId -> FileAction Int+getFileSizeBytes = simpleAction "f.size_bytes" []++getFileSizeChunks :: FileId -> FileAction Int+getFileSizeChunks = simpleAction "f.size_chunks" []++getFileCompletedChunks :: FileId -> FileAction Int+getFileCompletedChunks = simpleAction "f.completed_chunks" []++getFilePriority :: FileId -> FileAction FilePriority+getFilePriority = simpleAction "f.priority" []++setFilePriority :: FilePriority -> FileId -> FileAction Int+setFilePriority pr = simpleAction "f.priority.set" [PFilePriority pr]++-- | Get a file except for @FileId@. The @FileId@ can be got by running @allFiles@.+getFilePartial :: FileId -> FileAction (FileId -> FileInfo)+getFilePartial = runActionB $ FileInfo+           <$> b getFilePath+           <*> b getFileSizeBytes+           <*> b getFileSizeChunks+           <*> b getFileCompletedChunks+           <*> b getFilePriority+  where+    b = ActionB++getTorrentFiles :: TorrentId -> TorrentAction [FileInfo]+getTorrentFiles = fmap (map contract) . allFiles getFilePartial+  where+    contract (x :*: f) = f x+
+ Network/RTorrent/Peer.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE TypeOperators, TypeFamilies #-}++{-|+Module      : Peer+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++For more info on actions, see "Network.RTorrent.Action".+-}++module Network.RTorrent.Peer (+    PeerId (..)+  , PeerInfo (..)+  , PeerAction ++  , getPeerPartial+  , allPeers+  , getTorrentPeers++  -- * Control peers+  , banPeer+  , disconnectPeer++  -- * Functions for single variables+  , getPeerHash+  , getPeerIp++  , getPeerClientVersion+  , getPeerUpRate+  , getPeerDownRate+  , getPeerUpTotal+  , getPeerDownTotal+  , getPeerEncrypted+  , getPeerCompletedPercent+  , getPeerPort+) where++import Control.Applicative+import Control.DeepSeq++import Network.RTorrent.Action.Internals+import Network.RTorrent.Torrent+import Network.RTorrent.Command+import Network.XmlRpc.Internals++import Data.List.Split (splitOn)++data PeerId = PeerId !TorrentId !String +    deriving Show++instance XmlRpcType PeerId where+    toValue (PeerId (TorrentId tid) i) = ValueString $ tid ++ ":p" ++ i+    fromValue v = return . uncurry PeerId =<< parse =<< fromValue v+      where+        parse :: Monad m => String -> m (TorrentId, String)+        parse str = do+            [hash, s] <- return $ splitOn ":p" str+            return (TorrentId hash, s)+    getType _ = TString++instance NFData PeerId where+    rnf (PeerId tid i) = rnf tid `seq` rnf i++data PeerInfo = PeerInfo {+    peerClientVersion :: String+  , peerIp :: String+  , peerUpRate :: !Int+  , peerDownRate :: !Int+  , peerUpTotal :: !Int+  , peerDownTotal :: !Int+  , peerEncrypted :: !Bool+  , peerCompletedPercent :: !Int+  , peerPort :: !Int+  , peerId :: PeerId+} deriving Show++instance NFData PeerInfo where+    rnf (PeerInfo a0 a1 a2 a3 a4 a5 a6 a7 a8 a9) =+              rnf a0+        `seq` rnf a1 +        `seq` rnf a2+        `seq` rnf a3+        `seq` rnf a4+        `seq` rnf a5+        `seq` rnf a6+        `seq` rnf a7+        `seq` rnf a8+        `seq` rnf a9++getPeerHash :: PeerId -> PeerAction String+getPeerHash = simpleAction "p.get_id" []++getPeerIp :: PeerId -> PeerAction String+getPeerIp = simpleAction "p.get_address" []++getPeerClientVersion :: PeerId -> PeerAction String+getPeerClientVersion = simpleAction "p.get_client_version" []++getPeerUpRate :: PeerId -> PeerAction Int+getPeerUpRate = simpleAction "p.get_up_rate" []++getPeerDownRate :: PeerId -> PeerAction Int+getPeerDownRate = simpleAction "p.get_down_rate" []++getPeerUpTotal :: PeerId -> PeerAction Int+getPeerUpTotal = simpleAction "p.get_up_total" []++getPeerDownTotal :: PeerId -> PeerAction Int+getPeerDownTotal = simpleAction "p.get_down_total" []++getPeerEncrypted :: PeerId -> PeerAction Bool+getPeerEncrypted = fmap toEnum . simpleAction "p.is_encrypted" []++getPeerCompletedPercent :: PeerId -> PeerAction Int+getPeerCompletedPercent = simpleAction "p.get_completed_percent" []++getPeerPort :: PeerId -> PeerAction Int+getPeerPort = simpleAction "p.get_port" []++-- | Get a partial peer. @PeerId@ can be gotten by running @allPeers@.+getPeerPartial :: PeerId -> PeerAction (PeerId -> PeerInfo)+getPeerPartial = runActionB $ PeerInfo+         <$> b getPeerClientVersion+         <*> b getPeerIp+         <*> b getPeerUpRate+         <*> b getPeerDownRate+         <*> b getPeerUpTotal+         <*> b getPeerDownTotal+         <*> b getPeerEncrypted+         <*> b getPeerCompletedPercent+         <*> b getPeerPort+  where+    b = ActionB++disconnectPeer :: PeerId -> PeerAction Int+disconnectPeer = simpleAction "p.disconnect" []++banPeer :: PeerId -> PeerAction Int+banPeer = simpleAction "p.banned.set" [PInt 1]++type PeerAction = Action PeerId++getTorrentPeers :: TorrentId -> TorrentAction [PeerInfo]+getTorrentPeers = fmap (map contract) . allPeers getPeerPartial+  where+    contract (x :*: f) = f x++-- | Run the peer action on all peers that a torrent has.+allPeers :: (PeerId -> PeerAction a) -> TorrentId -> TorrentAction [PeerId :*: a]+allPeers p = fmap addId . (getTorrentId <+> allToMulti (allP (getPeerHash <+> p)))+  where+    addId (hash :*: peers) = +        map (\(phash :*: f) -> PeerId hash phash :*: f) peers+    allP :: (PeerId -> PeerAction a) -> AllAction PeerId a+    allP = AllAction (PeerId (TorrentId "") "") "p.multicall"
+ Network/RTorrent/Priority.hs view
@@ -0,0 +1,72 @@+{-|+Module      : Priority+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental+-}++module Network.RTorrent.Priority (+    TorrentPriority (..)+  , FilePriority (..)+) where++import Control.DeepSeq+import Network.XmlRpc.Internals++data TorrentPriority = +      TorrentPriorityOff+    | TorrentPriorityLow+    | TorrentPriorityNormal+    | TorrentPriorityHigh+  deriving (Show, Eq, Ord)++instance NFData TorrentPriority++instance Enum TorrentPriority where+    toEnum 0 = TorrentPriorityOff+    toEnum 1 = TorrentPriorityLow+    toEnum 2 = TorrentPriorityNormal+    toEnum 3 = TorrentPriorityHigh+    toEnum i = error $ "toEnum :: Int -> TorrentPriority failed, got : " ++ show i++    fromEnum TorrentPriorityOff = 0+    fromEnum TorrentPriorityLow = 1+    fromEnum TorrentPriorityNormal = 2+    fromEnum TorrentPriorityHigh = 3++instance XmlRpcType TorrentPriority where+    toValue = toValue . fromEnum+    fromValue v = return . toEnum =<< check =<< fromValue v+      where+        check i +          | 0 <= i && i <= 3 = return i+          | otherwise = fail $ "Invalid TorrentPriority, got : " ++ show i+    getType _ = TInt++data FilePriority = +      FilePriorityOff+    | FilePriorityNormal+    | FilePriorityHigh+  deriving (Show, Eq, Ord)++instance NFData FilePriority++instance Enum FilePriority where+    toEnum 0 = FilePriorityOff+    toEnum 1 = FilePriorityNormal+    toEnum 2 = FilePriorityHigh+    toEnum i = error $ "toEnum :: Int -> FilePriority failed, got : " ++ show i++    fromEnum FilePriorityOff = 0+    fromEnum FilePriorityNormal = 1+    fromEnum FilePriorityHigh = 2++instance XmlRpcType FilePriority where+    toValue = toValue . fromEnum+    fromValue v = return . toEnum =<< check =<< fromValue v+      where+        check i +          | 0 <= i && i <= 2 = return i+          | otherwise = fail $ "Invalid FilePriority, got : " ++ show i+    getType _ = TInt
+ Network/RTorrent/RPC.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE FlexibleContexts #-}++{-|+Module      : RPC+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++This package can be used for communicating with RTorrent over its XML-RPC interface.++For example, you can request torrent info and bandwidth usage:++@+result <- callRTorrent "localhost" 5000 $ +    'getTorrents' 'Network.RTorrent.Command.:*:' 'getUpRate' 'Network.RTorrent.Command.:*:' 'getDownRate'+case result of +  Right (torrentInfo :*: uploadRate :*: downloadRate) -> ...+@+where++>>> :t torrentInfo+[TorrentInfo]+>>> :t uploadRate+Int++This requires you to have set @scgi_port = localhost:5000@ in your @.rtorrent.rc@.++Note that 'Network.RTorrent.Command.:*:' is both a data constructor and a type constructor,+and therefore:++>>> :t True :*: False+Bool :*: Bool++However, using @:*:@ in types needs the @TypeOperators@ extension to work.+++As a more complete example, the following code finds all files that are over+100 megabytes, prints them along with the torrent they belong to and +sets their priorities to high.++@+{&#45;\# LANGUAGE TypeOperators \#&#45;}++import Control.Monad+import Network.RTorrent++-- This is an action, and they can be combined with ('<+>').+torrentInfo :: 'TorrentId'+                -> 'TorrentAction' (String :*: [FileId :*: String :*: Int])+torrentInfo = 'getTorrentName' +               '<+>' 'allFiles' ('getFilePath' '<+>' 'getFileSizeBytes')++    -- 'allFiles' takes a file action ('FileId' -> 'FileAction' a)+    -- and returns a torrent action: TorrentId -> 'TorrentAction' [FileId :*: a].+    -- Note that it automatically adds 'FileId's to all elements.++main :: IO ()+main = do+    Right torrents <- callRTorrent "localhost" 5000 $+                        'allTorrents' torrentInfo+    let largeFiles = +                filter (\\(_ :*: _ :*: _ :*: size) -> size > 10^8)+                . concatMap (\\(tName :*: fileList) -> +                                map ((:*:) tName) fileList) +                             -- Move the torrent name into the list+                $ torrents++    putStrLn "Large files:"+    forM_ largeFiles $ \\(torrent :*: _ :*: fPath :*: _) ->+        putStrLn $ "\\t" ++ torrent ++ ": " ++ fPath++    -- There is instance ('Network.RTorrent.Command.Command' cmdA, 'Network.RTorrent.Command.Command' cmdB) => 'Network.RTorrent.Command.Command' (cmdA :*: cmdB)+    -- The return value for the command cmdA is 'Network.RTorrent.Command.Ret' cmdA, which is an associated type+    -- in the Command type class.+    -- The return value for the command cmdA :*: cmdB is Ret cmdA :*: Ret cmdB.+                     +    let cmd :: String :*: FileId :*: String :*: Int +                -> FileAction FilePriority :*: FileAction Int+        cmd (_ :*: fid :*: _ :*: _) = +            'getFilePriority' fid :*: 'setFilePriority' 'FilePriorityHigh' fid++            -- Get the old priority and set the new one to high.+            -- setFilePriority returns a not-so-useful Int.++    -- There is also an instance Command a => Command [a],+    -- and the return value for [a] is [Ret a].+    +    Right ret <- callRTorrent "localhost" 5000 $ map cmd largeFiles++    putStrLn "Old priorities:"+    forM_ ret $ \\(oldPriority :*: _) -> do+        putStrLn $ "\\t" ++ show oldPriority+@+-}++module Network.RTorrent.RPC (+      module Network.RTorrent.CommandList+    , callRTorrent +    ) where++import Control.Monad.Error (ErrorT(..), throwError, strMsg)+import Data.Monoid+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LB+import Network+import Network.XmlRpc.Internals++import qualified Network.RTorrent.Command.Internals as C+import Network.RTorrent.CommandList+import Network.RTorrent.SCGI++callRTorrentRaw :: HostName -> Int -> C.RTMethodCall -> ErrorT String IO Value+callRTorrentRaw host port calls = do+    let request = Body [] . mconcat . LB.toChunks $ renderCall call +    Body _ content <- ErrorT $ query host port request+    let cs = map (toEnum . fromEnum) $ BS.unpack content+    response <- parseResponse cs+    case response of+        Return ret -> case ret of+            ValueArray arr -> return $ ValueArray arr+            val -> throwError . strMsg $ "Got value of type " ++ show (getType val)+        Fault code err -> throwError . strMsg $ show code ++ ": " ++ err+  where+    call = MethodCall "system.multicall" [C.runRTMethodCall calls]++-- | Call RTorrent with a command.+-- Only one connection is opened even when combining commands+-- for example by using ':*:' or lists.+callRTorrent :: Command a =>+    HostName+    -> Int+    -> a +    -> IO (Either String (Ret a))+callRTorrent host port command = +    runErrorT $ +        C.commandValue command =<< callRTorrentRaw host port (C.commandCall command)
+ Network/RTorrent/SCGI.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE OverloadedStrings #-}++{-# OPTIONS_HADDOCK hide #-}++{-|+Module      : SCGI+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++An internal module for establishing a connection with RTorrent.+-}++module Network.RTorrent.SCGI (Headers, Body(..), query) where++import Control.Applicative+import Data.Either (partitionEithers)+import Data.Monoid++import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Char8+import Blaze.Text+import Data.Attoparsec.ByteString.Char8 (Parser)+import qualified Data.Attoparsec.ByteString.Char8 as A+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS++import Network++type Headers = [(ByteString, ByteString)]++data Body = Body {+      headers :: Headers+    , body :: ByteString+} deriving Show++makeRequest :: Body -> ByteString+makeRequest (Body hd bd) = res +  where+    res = toByteString . mconcat $ [ +        integral len+      , fromChar ':'+      , fromByteString hdbs+      , fromChar ','+      , fromByteString bd+      ]+    fromBS0 bs = fromWrite $ writeByteString bs <> writeWord8 0+    hdbs = toByteString . mconcat $ +           (fromBS0 "CONTENT_LENGTH" <> integral (BS.length bd) <> fromWord8 0): +            map (\(a, b) -> fromBS0 a <> fromBS0 b) hd+    len = BS.length hdbs++parseResponse :: Parser Body+parseResponse = parseBody+  where+    lineParser :: Parser ByteString+    lineParser = +        mappend <$> A.takeTill (== '\r') <*> (("\r\n" *> pure "") <|> lineParser)+    headerParser = (,) <$> A.takeTill (==  ':') <*> (": " *> lineParser) ++    contentParser = "\r\n" *> A.takeByteString+    parseBody = do+        header <- headerParser+        (moreHeaders, [content]) <- +            partitionEithers <$> A.many' (A.eitherP headerParser contentParser)+        return $ Body (header : moreHeaders) content++query :: HostName -> Int -> Body -> IO (Either String Body)+query host port queryBody = do+    h <- connectTo host (PortNumber (toEnum port))+    BS.hPut h (makeRequest queryBody) +    A.parseOnly parseResponse <$> BS.hGetContents h+
+ Network/RTorrent/Torrent.hs view
@@ -0,0 +1,180 @@+{-|+Module      : Torrent+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++For more info on actions, see "Network.RTorrent.Action".+-}++module Network.RTorrent.Torrent+  ( TorrentInfo (..)+  , TorrentId (..)+  , TorrentAction++  -- * Functions for handling torrents+  , start+  , close+  , erase+  , checkHash+  , getTorrent+  , getTorrents++  , allTorrents ++  -- * Functions for single variables+  , setTorrentPriority+  , getTorrentPriority+  , getTorrentId +  , getTorrentOpen+  , getTorrentUpRate+  , getTorrentDownRate+  , getTorrentSizeBytes+  , getTorrentLeftBytes+  , getTorrentName+  , getTorrentPath+  , getTorrentDir+  , setTorrentDir+  , getTorrentRatio+  , getTorrentFileCount+  )+  where++import Control.Applicative+import Control.DeepSeq+import Network.XmlRpc.Internals++import Network.RTorrent.Action.Internals+import Network.RTorrent.Priority++-- | A newtype wrapper for torrent identifiers.+newtype TorrentId = TorrentId String +    deriving Show++type TorrentAction = Action TorrentId++instance NFData TorrentId where+    rnf (TorrentId str) = rnf str++instance XmlRpcType TorrentId where+    toValue (TorrentId s) = ValueString s+    fromValue v = return . TorrentId =<< fromValue v+    getType _ = TString++data TorrentInfo = TorrentInfo {+      torrentId :: TorrentId+    , torrentName :: String+    , torrentOpen :: !Bool+    , torrentDownRate :: !Int+    , torrentUpRate :: !Int+    , torrentSize :: !Int+    , torrentBytesLeft :: !Int+    , torrentPath :: String+    , torrentDir :: String+    , torrentTorrentPriority :: !TorrentPriority+    } deriving Show++instance NFData TorrentInfo where+    rnf (TorrentInfo i a0 a1 a2 a3 a4 a5 a6 a7 a8) = +        rnf i  `seq`+        rnf a0 `seq`+        rnf a1 `seq`+        rnf a2 `seq`+        rnf a3 `seq`+        rnf a4 `seq`+        rnf a5 `seq`+        rnf a6 `seq`+        rnf a7 `seq`+        rnf a8++-- | Get a TorrentInfo for a torrent.+getTorrent :: TorrentId -> TorrentAction TorrentInfo+getTorrent = runActionB $ TorrentInfo+         <$> b getTorrentId+         <*> b getTorrentName+         <*> b getTorrentOpen+         <*> b getTorrentDownRate+         <*> b getTorrentUpRate+         <*> b getTorrentSizeBytes+         <*> b getTorrentLeftBytes+         <*> b getTorrentPath+         <*> b getTorrentDir+         <*> b getTorrentPriority+  where+    b = ActionB+    +-- | Start downloading a torrent.+start :: TorrentId -> TorrentAction Int+start = simpleAction "d.start" []++-- | Close a torrent. +close :: TorrentId -> TorrentAction Int+close = simpleAction "d.close" []++-- | Erase a torrent. +erase :: TorrentId -> TorrentAction Int+erase = simpleAction "d.erase" []++-- | Initiate a hash check for a torrent.+checkHash :: TorrentId -> TorrentAction Int+checkHash = simpleAction "d.check_hash" []++-- | Set the download priority of a torrent.+setTorrentPriority :: TorrentPriority -> TorrentId -> TorrentAction Int+setTorrentPriority pr = simpleAction "d.priority.set" [PTorrentPriority pr]++getTorrentId :: TorrentId -> TorrentAction TorrentId+getTorrentId = simpleAction "d.hash" []++getTorrentName :: TorrentId -> TorrentAction String+getTorrentName = simpleAction "d.get_name" []++-- | Get the absolute path to the torrent's directory or file.+getTorrentPath :: TorrentId -> TorrentAction String+getTorrentPath = simpleAction "d.get_base_path" []++-- | Get the absolute path to the directory in which the torrent's directory or+-- file resides.+getTorrentDir :: TorrentId -> TorrentAction String+getTorrentDir = simpleAction "d.get_directory" []++setTorrentDir :: String -> TorrentId -> TorrentAction Int+setTorrentDir dir = simpleAction "d.set_directory" [PString dir]++getTorrentOpen :: TorrentId -> TorrentAction Bool+getTorrentOpen = fmap toEnum . simpleAction "d.is_open" []++getTorrentUpRate :: TorrentId -> TorrentAction Int+getTorrentUpRate = simpleAction "d.get_up_rate" []++getTorrentDownRate :: TorrentId -> TorrentAction Int+getTorrentDownRate = simpleAction "d.get_down_rate" []++getTorrentSizeBytes :: TorrentId -> TorrentAction Int+getTorrentSizeBytes = simpleAction "d.get_size_bytes" []++getTorrentLeftBytes :: TorrentId -> TorrentAction Int+getTorrentLeftBytes = simpleAction "d.get_left_bytes" []++getTorrentPriority :: TorrentId -> TorrentAction TorrentPriority+getTorrentPriority = simpleAction "d.priority" []++-- | Get the ratio (which is multiplied by a thousand)+getTorrentRatio :: TorrentId -> TorrentAction Int+getTorrentRatio = simpleAction "d.get_ratio" []++getTorrentFileCount :: TorrentId -> TorrentAction Int+getTorrentFileCount = simpleAction "d.get_size_files" []++-- | Execute a command on all torrents.+-- For example the command+--+-- > allTorrents (setTorrentPriority TorrentPriorityNormal)+-- will set the priority of all torrents to normal.+allTorrents :: (TorrentId -> TorrentAction a) -> AllAction TorrentId a+allTorrents = AllAction (TorrentId "") "d.multicall"++-- | A command for getting torrent info for all torrents.+getTorrents :: AllAction TorrentId TorrentInfo+getTorrents = allTorrents getTorrent
+ Network/RTorrent/Tracker.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE TypeOperators, TypeFamilies #-}++{-|+Module      : Tracker+Copyright   : (c) Kai Lindholm, 2014+License     : MIT+Maintainer  : megantti@gmail.com+Stability   : experimental++For more info on actions, see "Network.RTorrent.Action".+-}++module Network.RTorrent.Tracker (+    TrackerId (..)+  , TrackerType (..)+  , TrackerInfo (..)+  , TrackerAction +  , getTrackerPartial+  , getTorrentTrackers+  , allTrackers++  -- * Functions dealing with a single variable+  , getTrackerUrl+  , getTrackerEnabled+  , setTrackerEnabled+  , getTrackerType+  , getTrackerOpen+) where++import Control.Applicative+import Control.DeepSeq++import Network.RTorrent.Action.Internals+import Network.RTorrent.Torrent+import Network.RTorrent.Command+import Network.XmlRpc.Internals++import Data.List.Split (splitOn)++data TrackerId = TrackerId !TorrentId !Int deriving Show++instance XmlRpcType TrackerId where+    toValue (TrackerId (TorrentId tid) i) = ValueString $ tid ++ ":t" ++ show i+    fromValue v = return . uncurry TrackerId =<< parse =<< fromValue v+      where+        parse :: Monad m => String -> m (TorrentId, Int)+        parse str = do+            [hash, i] <- return $ splitOn ":t" str+            return (TorrentId hash, read i)+    getType _ = TString++data TrackerType = +      TrackerHTTP+    | TrackerUDP+    | TrackerDHT+  deriving (Show, Eq)++instance Enum TrackerType where+    toEnum 1 = TrackerHTTP+    toEnum 2 = TrackerUDP+    toEnum 3 = TrackerDHT+    toEnum i = error $ "toEnum :: Int -> TrackerType failed, got : " ++ show i++    fromEnum TrackerHTTP = 1+    fromEnum TrackerUDP = 2+    fromEnum TrackerDHT = 3++instance XmlRpcType TrackerType where+    toValue = toValue . fromEnum+    fromValue v = return . toEnum =<< check =<< fromValue v+      where+        check i +          | 1 <= i && i <= 3 = return i+          | otherwise = fail $ "Invalid TrackerType, got : " ++ show i+    getType _ = TInt++instance NFData TrackerType++data TrackerInfo = TrackerInfo {+    trackerUrl :: String+  , trackerType :: !TrackerType+  , trackerEnabled :: !Bool+  , trackerOpen :: !Bool+  , trackerId :: TrackerId+} deriving Show++instance NFData TrackerId where+    rnf (TrackerId tid i) = rnf tid `seq` rnf i++instance NFData TrackerInfo where+    rnf (TrackerInfo a0 a1 a2 a3 a4) = +              rnf a0 +        `seq` rnf a1+        `seq` rnf a2+        `seq` rnf a3+        `seq` rnf a4++type TrackerAction = Action TrackerId++-- | Run the tracker action on all trackers that a torrent has.+allTrackers :: (TrackerId -> TrackerAction a) -> TorrentId -> TorrentAction [TrackerId :*: a]+allTrackers t = fmap addId . (getTorrentId <+> allToMulti (allT t))+  where+    addId (hash :*: trackers) = +        zipWith (\index -> (:*:) (TrackerId hash index)) [0..] trackers +    allT :: (TrackerId -> TrackerAction a) -> AllAction TrackerId a+    allT = AllAction (TrackerId (TorrentId "") 0) "t.multicall"++getTrackerUrl :: TrackerId -> TrackerAction String+getTrackerUrl = simpleAction "t.get_url" []++getTrackerEnabled :: TrackerId -> TrackerAction Bool+getTrackerEnabled = fmap toEnum . simpleAction "t.is_enabled" []++setTrackerEnabled :: Bool -> TrackerId -> TrackerAction Int+setTrackerEnabled i = simpleAction "t.is_enabled" [PInt (fromEnum i)]++getTrackerType :: TrackerId -> TrackerAction TrackerType+getTrackerType = simpleAction "t.get_type" []++getTrackerOpen :: TrackerId -> TrackerAction Bool+getTrackerOpen = fmap toEnum . simpleAction "t.is_open" []++-- | Get a tracker except for @TrackerId@. The @TrackerId@ can be got by running @allTrackers@.+getTrackerPartial :: TrackerId -> TrackerAction (TrackerId -> TrackerInfo)+getTrackerPartial = runActionB $ TrackerInfo+           <$> b getTrackerUrl+           <*> b getTrackerType+           <*> b getTrackerEnabled+           <*> b getTrackerOpen+  where+    b = ActionB++getTorrentTrackers :: TorrentId -> TorrentAction [TrackerInfo]+getTorrentTrackers = fmap (map contract) . allTrackers getTrackerPartial+  where+    contract (x :*: f) = f x+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ rtorrent-rpc.cabal view
@@ -0,0 +1,30 @@+name:                rtorrent-rpc+version:             0.2.0.0+synopsis:            A library for communicating with RTorrent over its XML-RPC interface.+-- description:         +license:             MIT+license-file:        LICENSE+author:              Kai Lindholm+maintainer:          megantti@gmail.com+homepage:            https://github.com/megantti/rtorrent-rpc+-- copyright:           +category:            Network+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++library+  exposed-modules:     Network.RTorrent, Network.RTorrent.RPC, Network.RTorrent.CommandList, +                       Network.RTorrent.Command, Network.RTorrent.Torrent, Network.RTorrent.File,+                       Network.RTorrent.Action, Network.RTorrent.Priority, Network.RTorrent.Peer, +                       Network.RTorrent.Tracker, Network.RTorrent.Command.Internals,+                       Network.RTorrent.Action.Internals+  other-modules:       Network.RTorrent.SCGI+  other-extensions:    TypeFamilies, OverloadedStrings, TypeOperators, ScopedTypeVariables, +                       GADTs, FlexibleContexts, RankNTypes+  build-depends:       base >=4.7 && <4.8, mtl >=2.1 && < 2.3, bytestring >=0.10 && <0.11, +                       network >=2.6, haxr >=3000.10 && <3000.11, blaze-builder >=0.3 && <0.4, +                       blaze-textual >=0.2 && <0.3, attoparsec >=0.12 && <0.13, deepseq >= 1.3.0.0, +                       split >=0.2.0.0+  -- hs-source-dirs:      +  default-language:    Haskell2010