diff options
-rw-r--r-- | bittorrent.cabal | 14 | ||||
-rw-r--r-- | examples/Client.hs | 17 | ||||
-rw-r--r-- | examples/Main.hs | 18 | ||||
-rw-r--r-- | src/Network/BitTorrent/Client.hs | 33 | ||||
-rw-r--r-- | src/Network/BitTorrent/Client/Swarm.hs | 49 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Wai.hs | 2 | ||||
-rw-r--r-- | tests/Network/BitTorrent/Tracker/RPCSpec.hs | 29 |
7 files changed, 138 insertions, 24 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal index e420beba..69a1207b 100644 --- a/bittorrent.cabal +++ b/bittorrent.cabal | |||
@@ -51,6 +51,7 @@ library | |||
51 | Data.Torrent.Tree | 51 | Data.Torrent.Tree |
52 | -- Network.BitTorrent | 52 | -- Network.BitTorrent |
53 | Network.BitTorrent.Client | 53 | Network.BitTorrent.Client |
54 | Network.BitTorrent.Client.Swarm | ||
54 | Network.BitTorrent.Core | 55 | Network.BitTorrent.Core |
55 | Network.BitTorrent.Core.Fingerprint | 56 | Network.BitTorrent.Core.Fingerprint |
56 | Network.BitTorrent.Core.PeerId | 57 | Network.BitTorrent.Core.PeerId |
@@ -95,7 +96,7 @@ library | |||
95 | -- Concurrency | 96 | -- Concurrency |
96 | -- , SafeSemaphore | 97 | -- , SafeSemaphore |
97 | -- , BoundedChan >= 1.0.1.0 | 98 | -- , BoundedChan >= 1.0.1.0 |
98 | -- , stm >= 2.4 | 99 | , stm >= 2.4 |
99 | 100 | ||
100 | -- Streaming | 101 | -- Streaming |
101 | , conduit >= 1.0 | 102 | , conduit >= 1.0 |
@@ -110,7 +111,7 @@ library | |||
110 | , intset >= 0.1 | 111 | , intset >= 0.1 |
111 | , split >= 0.2 | 112 | , split >= 0.2 |
112 | , text >= 0.11.0 | 113 | , text >= 0.11.0 |
113 | -- , unordered-containers | 114 | , unordered-containers |
114 | , vector >= 0.10 | 115 | , vector >= 0.10 |
115 | 116 | ||
116 | -- Hashing | 117 | -- Hashing |
@@ -211,3 +212,12 @@ test-suite spec | |||
211 | -- | 212 | -- |
212 | -- , bittorrent | 213 | -- , bittorrent |
213 | -- ghc-options: -O2 -Wall -fno-warn-orphans | 214 | -- ghc-options: -O2 -Wall -fno-warn-orphans |
215 | |||
216 | executable client-example | ||
217 | default-language: Haskell2010 | ||
218 | hs-source-dirs: examples | ||
219 | main-is: Client.hs | ||
220 | build-depends: base == 4.* | ||
221 | , bittorrent | ||
222 | , pretty-class | ||
223 | , data-default \ No newline at end of file | ||
diff --git a/examples/Client.hs b/examples/Client.hs new file mode 100644 index 00000000..efafba72 --- /dev/null +++ b/examples/Client.hs | |||
@@ -0,0 +1,17 @@ | |||
1 | module Main (main) where | ||
2 | |||
3 | import Control.Concurrent | ||
4 | import Data.Default | ||
5 | import Data.Torrent | ||
6 | import Network.BitTorrent.Client | ||
7 | import System.Environment | ||
8 | import Text.PrettyPrint.Class | ||
9 | |||
10 | |||
11 | main :: IO () | ||
12 | main = do | ||
13 | [path] <- getArgs | ||
14 | torrent <- fromFile path | ||
15 | client <- newClient def | ||
16 | addTorrent torrent client | ||
17 | return () \ No newline at end of file | ||
diff --git a/examples/Main.hs b/examples/Main.hs deleted file mode 100644 index ef12cc70..00000000 --- a/examples/Main.hs +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | module Main (main) where | ||
2 | |||
3 | import Control.Concurrent | ||
4 | import Network.BitTorrent | ||
5 | import Network.BitTorrent.Sessions | ||
6 | import System.Environment | ||
7 | |||
8 | main :: IO () | ||
9 | main = do | ||
10 | [path] <- getArgs | ||
11 | torrent <- fromFile path | ||
12 | print (contentLayout "./" (tInfo torrent)) | ||
13 | let loc = TorrentLoc path "/tmp" | ||
14 | |||
15 | withDefaultClient (head defaultPorts) 3000 $ \ client -> do | ||
16 | openSwarmSession client loc | ||
17 | threadDelay 1000000000000 | ||
18 | return () | ||
diff --git a/src/Network/BitTorrent/Client.hs b/src/Network/BitTorrent/Client.hs index fc2b904a..a6151857 100644 --- a/src/Network/BitTorrent/Client.hs +++ b/src/Network/BitTorrent/Client.hs | |||
@@ -1,16 +1,21 @@ | |||
1 | module Network.BitTorrent.Client | 1 | module Network.BitTorrent.Client |
2 | ( Options (..) | 2 | ( Options (..) |
3 | , Client (..) | 3 | , Client |
4 | , newClient | ||
5 | , addTorrent | ||
4 | ) where | 6 | ) where |
5 | 7 | ||
8 | import Control.Concurrent.STM | ||
6 | import Data.Default | 9 | import Data.Default |
7 | import Data.Function | 10 | import Data.Function |
11 | import Data.HashMap.Strict as HM | ||
8 | import Data.Ord | 12 | import Data.Ord |
9 | import Data.Text | 13 | import Data.Text |
10 | import Network | 14 | import Network |
11 | 15 | ||
12 | import Data.Torrent | 16 | import Data.Torrent |
13 | import Data.Torrent.InfoHash | 17 | import Data.Torrent.InfoHash |
18 | import Network.BitTorrent.Client.Swarm | ||
14 | import Network.BitTorrent.Core | 19 | import Network.BitTorrent.Core |
15 | import Network.BitTorrent.Exchange.Message | 20 | import Network.BitTorrent.Exchange.Message |
16 | 21 | ||
@@ -19,6 +24,7 @@ data Options = Options | |||
19 | { fingerprint :: Fingerprint | 24 | { fingerprint :: Fingerprint |
20 | , name :: Text | 25 | , name :: Text |
21 | , port :: PortNumber | 26 | , port :: PortNumber |
27 | , extensions :: [Extension] | ||
22 | } | 28 | } |
23 | 29 | ||
24 | instance Default Options where | 30 | instance Default Options where |
@@ -26,11 +32,14 @@ instance Default Options where | |||
26 | { fingerprint = def | 32 | { fingerprint = def |
27 | , name = "hs-bittorrent" | 33 | , name = "hs-bittorrent" |
28 | , port = 6882 | 34 | , port = 6882 |
35 | , extensions = [] | ||
29 | } | 36 | } |
30 | 37 | ||
31 | data Client = Client | 38 | data Client = Client |
32 | { clientPeerId :: !PeerId | 39 | { clientPeerId :: !PeerId |
33 | , allowedExtensions :: !Caps | 40 | , clientListenerPort :: !PortNumber |
41 | , allowedExtensions :: !Caps | ||
42 | , torrents :: TVar (HashMap InfoHash Swarm) | ||
34 | } | 43 | } |
35 | 44 | ||
36 | instance Eq Client where | 45 | instance Eq Client where |
@@ -38,3 +47,21 @@ instance Eq Client where | |||
38 | 47 | ||
39 | instance Ord Client where | 48 | instance Ord Client where |
40 | compare = comparing clientPeerId | 49 | compare = comparing clientPeerId |
50 | |||
51 | newClient :: Options -> IO Client | ||
52 | newClient Options {..} = do | ||
53 | pid <- genPeerId | ||
54 | ts <- newTVarIO HM.empty | ||
55 | return Client | ||
56 | { clientPeerId = pid | ||
57 | , clientListenerPort = port | ||
58 | , allowedExtensions = toCaps extensions | ||
59 | , torrents = ts | ||
60 | } | ||
61 | |||
62 | addTorrent :: Torrent -> Client -> IO () | ||
63 | addTorrent t Client {..} = do | ||
64 | leecher <- newLeecher clientPeerId clientListenerPort t | ||
65 | let ih = idInfoHash (tInfoDict t) | ||
66 | atomically $ modifyTVar' torrents (HM.insert ih leecher) | ||
67 | askPeers leecher >>= print \ No newline at end of file | ||
diff --git a/src/Network/BitTorrent/Client/Swarm.hs b/src/Network/BitTorrent/Client/Swarm.hs new file mode 100644 index 00000000..a9dca048 --- /dev/null +++ b/src/Network/BitTorrent/Client/Swarm.hs | |||
@@ -0,0 +1,49 @@ | |||
1 | module Network.BitTorrent.Client.Swarm | ||
2 | ( Swarm | ||
3 | , newLeecher | ||
4 | , askPeers | ||
5 | ) where | ||
6 | |||
7 | import Data.Default | ||
8 | import Network | ||
9 | |||
10 | import Data.Torrent | ||
11 | import Data.Torrent.InfoHash | ||
12 | import Network.BitTorrent.Core | ||
13 | import Network.BitTorrent.Tracker.Message | ||
14 | import Network.BitTorrent.Tracker.RPC as RPC | ||
15 | |||
16 | |||
17 | data Swarm = Swarm | ||
18 | { swarmTopic :: InfoHash | ||
19 | , thisPeerId :: PeerId | ||
20 | , listenerPort :: PortNumber | ||
21 | , trackerConn :: Tracker | ||
22 | -- , infoDict :: | ||
23 | } | ||
24 | |||
25 | newLeecher :: PeerId -> PortNumber -> Torrent -> IO Swarm | ||
26 | newLeecher pid port Torrent {..} = do | ||
27 | tracker <- connect tAnnounce | ||
28 | return Swarm | ||
29 | { swarmTopic = idInfoHash tInfoDict | ||
30 | , thisPeerId = pid | ||
31 | , listenerPort = port | ||
32 | , trackerConn = tracker | ||
33 | } | ||
34 | |||
35 | getAnnounceQuery :: Swarm -> AnnounceQuery | ||
36 | getAnnounceQuery Swarm {..} = AnnounceQuery | ||
37 | { reqInfoHash = swarmTopic | ||
38 | , reqPeerId = thisPeerId | ||
39 | , reqPort = listenerPort | ||
40 | , reqProgress = def | ||
41 | , reqIP = Nothing | ||
42 | , reqNumWant = Nothing | ||
43 | , reqEvent = Nothing | ||
44 | } | ||
45 | |||
46 | askPeers :: Swarm -> IO [PeerAddr] | ||
47 | askPeers s @ Swarm {..} = do | ||
48 | AnnounceInfo {..} <- RPC.announce (getAnnounceQuery s) trackerConn | ||
49 | return (getPeerList respPeers) | ||
diff --git a/src/Network/BitTorrent/Tracker/Wai.hs b/src/Network/BitTorrent/Tracker/Wai.hs index c43c7a3a..69510fa0 100644 --- a/src/Network/BitTorrent/Tracker/Wai.hs +++ b/src/Network/BitTorrent/Tracker/Wai.hs | |||
@@ -79,7 +79,7 @@ getAnnounceR = undefined | |||
79 | getScrapeR :: TrackerSettings -> ScrapeQuery -> ResourceT IO ScrapeInfo | 79 | getScrapeR :: TrackerSettings -> ScrapeQuery -> ResourceT IO ScrapeInfo |
80 | getScrapeR = undefined | 80 | getScrapeR = undefined |
81 | 81 | ||
82 | -- content-type: "text/plain" ? | 82 | -- content-type: "text/plain"! |
83 | tracker :: TrackerSettings -> Application | 83 | tracker :: TrackerSettings -> Application |
84 | tracker settings Request {..} | 84 | tracker settings Request {..} |
85 | | requestMethod /= methodGet | 85 | | requestMethod /= methodGet |
diff --git a/tests/Network/BitTorrent/Tracker/RPCSpec.hs b/tests/Network/BitTorrent/Tracker/RPCSpec.hs new file mode 100644 index 00000000..6bc72e86 --- /dev/null +++ b/tests/Network/BitTorrent/Tracker/RPCSpec.hs | |||
@@ -0,0 +1,29 @@ | |||
1 | module Network.BitTorrent.Tracker.RPCSpec (spec) where | ||
2 | import Control.Monad | ||
3 | import Data.Default | ||
4 | import Data.List as L | ||
5 | import Network.URI | ||
6 | import Test.Hspec | ||
7 | |||
8 | import Network.BitTorrent.Tracker.MessageSpec hiding (spec) | ||
9 | import Network.BitTorrent.Tracker.RPC.HTTPSpec as HTTP hiding (spec) | ||
10 | import Network.BitTorrent.Tracker.RPC.UDPSpec as UDP hiding (spec) | ||
11 | import Network.BitTorrent.Tracker.RPC as RPC | ||
12 | |||
13 | uris :: [URI] | ||
14 | uris = UDP.trackerURIs ++ HTTP.trackerURIs | ||
15 | |||
16 | spec :: Spec | ||
17 | spec = do | ||
18 | forM_ uris $ \ uri -> | ||
19 | context (show uri) $ do | ||
20 | describe "announce" $ do | ||
21 | it "have valid response" $ do | ||
22 | q <- arbitrarySample | ||
23 | info <- connect uri >>= announce q | ||
24 | validateInfo q info | ||
25 | |||
26 | describe "scrape" $ do | ||
27 | it "have valid response" $ do | ||
28 | xs <- connect uri >>= scrape [def] | ||
29 | L.length xs `shouldSatisfy` (>= 1) | ||