<commit_before>{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}
module Network.BitTorrent.Client
       ( -- * Options
         Options (..)

         -- * Client session
       , Client

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

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

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

         -- * Handle
       , openTorrent
       , openMagnet
       , closeHandle

       , start
       , pause
       , stop
       ) where

import Control.Exception
import Control.Concurrent
import Control.Monad.Logger
import Control.Monad.Trans.Resource

import Data.Default
import Data.HashMap.Strict as HM
import Data.Maybe
import Data.Text
import Network

import           Network.BitTorrent.Client.Types
import           Network.BitTorrent.Client.Handle
import           Network.BitTorrent.Core
import           Network.BitTorrent.DHT
import           Network.BitTorrent.Tracker  as Tracker  hiding (Options)
import           Network.BitTorrent.Exchange as Exchange hiding (Options)
import qualified Network.BitTorrent.Exchange as Exchange (Options(..))


data Options = Options
  { optFingerprint :: Fingerprint
  , optName        :: Text
  , optPort        :: PortNumber
  , optExtensions  :: [Extension]
  , optNodeAddr    :: NodeAddr IPv4
  , optBootNode    :: Maybe (NodeAddr IPv4)
  }

instance Default Options where
  def = Options
    { optFingerprint = def
    , optName        = "hs-bittorrent"
    , optPort        = 6882
    , optExtensions  = []
    , optNodeAddr    = "0.0.0.0:6882"
    , optBootNode    = Nothing
    }

exchangeOptions :: PeerId -> Options -> Exchange.Options
exchangeOptions pid Options {..} = Exchange.Options
  { optPeerAddr = PeerAddr (Just pid) (peerHost def) optPort
  , optBacklog  = optBacklog def
  }

--connHandler :: HashMap InfoHash Handle -> Handler
connHandler tmap = undefined

newClient :: Options -> LogFun -> IO Client
newClient opts @ Options {..} logger = do
  pid  <- genPeerId
  tmap <- newMVar HM.empty
  tmgr <- Tracker.newManager def (PeerInfo pid Nothing optPort)
  emgr <- Exchange.newManager (exchangeOptions pid opts) connHandler
  node <- runResourceT $ do
    node <- startNode handlers def optNodeAddr logger
    runDHT node $ bootstrap (maybeToList optBootNode)
    return node
  return Client
    { clientPeerId       = pid
    , clientListenerPort = optPort
    , allowedExtensions  = toCaps optExtensions
    , trackerManager     = tmgr
    , exchangeManager    = emgr
    , clientNode         = node
    , clientTorrents     = tmap
    , clientLogger       = logger
    }

closeClient :: Client -> IO ()
closeClient Client {..} = do
  Exchange.closeManager exchangeManager
  Tracker.closeManager  trackerManager
  return ()
--  closeNode clientNode

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

-- | Run bittorrent client with default options and log to @stderr@.
--
--   For testing purposes only.
--
simpleClient :: BitTorrent () -> IO ()
simpleClient m = do
  runStderrLoggingT $ LoggingT $ \ logger -> do
    withClient def logger (`runBitTorrent` m)<commit_msg>Add TorrentSource class<commit_after>{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE TypeSynonymInstances       #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}
module Network.BitTorrent.Client
       ( -- * Options
         Options (..)

         -- * Client session
       , Client

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

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

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

         -- * Handle
       , TorrentSource (..)
       , closeHandle

       , start
       , pause
       , stop
       ) where

import Control.Exception
import Control.Concurrent
import Control.Monad.Logger
import Control.Monad.Trans
import Control.Monad.Trans.Resource

import Data.Default
import Data.HashMap.Strict as HM
import Data.Maybe
import Data.Text
import Network

import           Data.Torrent
import           Data.Torrent.InfoHash
import           Data.Torrent.Magnet
import           Network.BitTorrent.Client.Types
import           Network.BitTorrent.Client.Handle
import           Network.BitTorrent.Core
import           Network.BitTorrent.DHT
import           Network.BitTorrent.Tracker  as Tracker  hiding (Options)
import           Network.BitTorrent.Exchange as Exchange hiding (Options)
import qualified Network.BitTorrent.Exchange as Exchange (Options(..))


data Options = Options
  { optFingerprint :: Fingerprint
  , optName        :: Text
  , optPort        :: PortNumber
  , optExtensions  :: [Extension]
  , optNodeAddr    :: NodeAddr IPv4
  , optBootNode    :: Maybe (NodeAddr IPv4)
  }

instance Default Options where
  def = Options
    { optFingerprint = def
    , optName        = "hs-bittorrent"
    , optPort        = 6882
    , optExtensions  = []
    , optNodeAddr    = "0.0.0.0:6882"
    , optBootNode    = Nothing
    }

exchangeOptions :: PeerId -> Options -> Exchange.Options
exchangeOptions pid Options {..} = Exchange.Options
  { optPeerAddr = PeerAddr (Just pid) (peerHost def) optPort
  , optBacklog  = optBacklog def
  }

--connHandler :: HashMap InfoHash Handle -> Handler
connHandler tmap = undefined

newClient :: Options -> LogFun -> IO Client
newClient opts @ Options {..} logger = do
  pid  <- genPeerId
  tmap <- newMVar HM.empty
  tmgr <- Tracker.newManager def (PeerInfo pid Nothing optPort)
  emgr <- Exchange.newManager (exchangeOptions pid opts) connHandler
  node <- runResourceT $ do
    node <- startNode handlers def optNodeAddr logger
    runDHT node $ bootstrap (maybeToList optBootNode)
    return node
  return Client
    { clientPeerId       = pid
    , clientListenerPort = optPort
    , allowedExtensions  = toCaps optExtensions
    , trackerManager     = tmgr
    , exchangeManager    = emgr
    , clientNode         = node
    , clientTorrents     = tmap
    , clientLogger       = logger
    }

closeClient :: Client -> IO ()
closeClient Client {..} = do
  Exchange.closeManager exchangeManager
  Tracker.closeManager  trackerManager
  return ()
--  closeNode clientNode

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

-- | Run bittorrent client with default options and log to @stderr@.
--
--   For testing purposes only.
--
simpleClient :: BitTorrent () -> IO ()
simpleClient m = do
  runStderrLoggingT $ LoggingT $ \ logger -> do
    withClient def logger (`runBitTorrent` m)

{-----------------------------------------------------------------------
--  Torrent identifiers
-----------------------------------------------------------------------}

class TorrentSource s where
  openHandle :: FilePath -> s -> BitTorrent Handle

instance TorrentSource InfoHash where
  openHandle path ih = openMagnet path (nullMagnet ih)
  {-# INLINE openHandle #-}

instance TorrentSource Magnet where
  openHandle = openMagnet
  {-# INLINE openHandle #-}

instance TorrentSource InfoDict where
  openHandle path dict = openTorrent path (nullTorrent dict)
  {-# INLINE openHandle #-}

instance TorrentSource Torrent where
  openHandle = openTorrent
  {-# INLINE openHandle #-}

instance TorrentSource FilePath where
  openHandle contentDir torrentPath = do
    t <- liftIO $ fromFile torrentPath
    openTorrent contentDir t
  {-# INLINE openHandle #-}
{'fraction_non_alphanumeric': 0.10221378874130298, 'fraction_numerical': 0.0035420619860847564, 'mean_word_length': 2.3771892353695003, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1342879', 'n_tokens_mistral': 2274, 'n_tokens_neox': 2193, 'n_words': 616}