summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Client.hs
blob: fd830239c108102a7eefa8fb30c19520d1dbe652 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Network.BitTorrent.Client
       ( -- * Options
         Options (..)

         -- * Client session
       , Client

         -- ** Session data
       , clientPeerId
       , clientListenerPort
       , allowedExtensions

         -- ** Session initialization
       , newClient
       , closeClient
       , withClient

         -- * BitTorrent monad
       , BitTorrent
       , runBitTorrent
       , MonadBitTorrent (..)
       , getClient

         -- * Operations
       , addTorrent
       ) where

import Control.Exception
import Control.Concurrent.STM
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.Trans.Resource
import Data.Default
import Data.Function
import Data.HashMap.Strict as HM
import Data.Maybe
import Data.Ord
import Data.Text
import Network
import System.Log.FastLogger

import Data.Torrent
import Data.Torrent.InfoHash
import Network.BitTorrent.Client.Swarm
import Network.BitTorrent.Core
import Network.BitTorrent.DHT
import Network.BitTorrent.Exchange.Message


data Options = Options
  { fingerprint :: Fingerprint
  , name        :: Text
  , port        :: PortNumber
  , extensions  :: [Extension]
  , nodeAddr    :: NodeAddr IPv4
  , bootNode    :: Maybe (NodeAddr IPv4)
  }

instance Default Options where
  def = Options
    { fingerprint = def
    , name        = "hs-bittorrent"
    , port        = 6882
    , extensions  = []
    , nodeAddr    = "0.0.0.0:6882"
    , bootNode    = Nothing
    }

data Client = Client
  { clientPeerId       :: !PeerId
  , clientListenerPort :: !PortNumber
  , allowedExtensions  :: !Caps
  , clientNode         :: !(Node IPv4)
  , clientTorrents     :: !(TVar (HashMap InfoHash Swarm))
  , clientLogger       :: !LogFun
-- , trackerClient     :: !(Manager)
  }

instance Eq Client where
  (==) = (==) `on` clientPeerId

instance Ord Client where
  compare = comparing clientPeerId

newClient :: Options -> LogFun -> IO Client
newClient Options {..} logger = do
  pid  <- genPeerId
  ts   <- newTVarIO HM.empty
  node <- runResourceT $ do
    node <- startNode handlers def nodeAddr logger
    runDHT node $ bootstrap (maybeToList bootNode)
    return node

  return Client
    { clientPeerId       = pid
    , clientListenerPort = port
    , allowedExtensions  = toCaps extensions
    , clientTorrents     = ts
    , clientNode         = node
    , clientLogger       = logger
    }

closeClient :: Client -> IO ()
closeClient Client {..} = do
  return ()
--  closeNode clientNode

withClient :: Options -> LogFun -> (Client -> IO a) -> IO a
withClient opts log action = bracket (newClient opts log) closeClient action

{-----------------------------------------------------------------------
--  BitTorrent monad
-----------------------------------------------------------------------}

class MonadBitTorrent m where
  liftBT :: BitTorrent a -> m a

newtype BitTorrent a = BitTorrent
  { unBitTorrent :: ReaderT Client (ResourceT IO) a
  } deriving (Monad, MonadIO)

instance MonadBitTorrent BitTorrent where
  liftBT = id

instance MonadDHT BitTorrent where
  liftDHT action = BitTorrent $ do
    node <- asks clientNode
    liftIO $ runResourceT $ runDHT node action

instance MonadLogger BitTorrent where
  monadLoggerLog loc src lvl msg = BitTorrent $ do
    logger <- asks clientLogger
    liftIO $ logger loc src lvl (toLogStr msg)

runBitTorrent :: Client -> BitTorrent a -> IO a
runBitTorrent client action = runResourceT $
  runReaderT (unBitTorrent action) client
{-# INLINE runBitTorrent #-}

getClient :: BitTorrent Client
getClient = BitTorrent ask

{-----------------------------------------------------------------------
--  Operations
-----------------------------------------------------------------------}
-- All operations should be non blocking!

addTorrent :: Torrent -> BitTorrent ()
addTorrent t = do
  Client {..} <- getClient
  liftIO $ do
    leecher <- newLeecher clientPeerId clientListenerPort t
    let ih = idInfoHash (tInfoDict t)
    atomically $ modifyTVar' clientTorrents (HM.insert ih leecher)
    askPeers leecher >>= print