diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Network/RTorrent.hs b/Network/RTorrent.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent.hs
@@ -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
diff --git a/Network/RTorrent/Action.hs b/Network/RTorrent/Action.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Action.hs
@@ -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
diff --git a/Network/RTorrent/Action/Internals.hs b/Network/RTorrent/Action/Internals.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Action/Internals.hs
@@ -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
+
diff --git a/Network/RTorrent/Command.hs b/Network/RTorrent/Command.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Command.hs
@@ -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
diff --git a/Network/RTorrent/Command/Internals.hs b/Network/RTorrent/Command/Internals.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Command/Internals.hs
@@ -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 
+
diff --git a/Network/RTorrent/CommandList.hs b/Network/RTorrent/CommandList.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/CommandList.hs
@@ -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
+
diff --git a/Network/RTorrent/File.hs b/Network/RTorrent/File.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/File.hs
@@ -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
+
diff --git a/Network/RTorrent/Peer.hs b/Network/RTorrent/Peer.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Peer.hs
@@ -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"
diff --git a/Network/RTorrent/Priority.hs b/Network/RTorrent/Priority.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Priority.hs
@@ -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
diff --git a/Network/RTorrent/RPC.hs b/Network/RTorrent/RPC.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/RPC.hs
@@ -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)
diff --git a/Network/RTorrent/SCGI.hs b/Network/RTorrent/SCGI.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/SCGI.hs
@@ -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
+
diff --git a/Network/RTorrent/Torrent.hs b/Network/RTorrent/Torrent.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Torrent.hs
@@ -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
diff --git a/Network/RTorrent/Tracker.hs b/Network/RTorrent/Tracker.hs
new file mode 100644
--- /dev/null
+++ b/Network/RTorrent/Tracker.hs
@@ -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
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/rtorrent-rpc.cabal b/rtorrent-rpc.cabal
new file mode 100644
--- /dev/null
+++ b/rtorrent-rpc.cabal
@@ -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
