module Main (main) where import Control.Concurrent import Control.Concurrent.CML import Control.Monad import qualified Data.ByteString as B import Data.List import System.Environment import System.Random import System.Console.GetOpt import System.Log.Logger import System.Log.Handler.Simple import System.IO as SIO import qualified Protocol.BCode as BCode import qualified Process.Console as Console import qualified Process.FS as FSP import qualified Process.PeerMgr as PeerMgr import qualified Process.PieceMgr as PieceMgr (start, createPieceDb) import qualified Process.ChokeMgr as ChokeMgr (start) import qualified Process.Status as Status import qualified Process.Tracker as Tracker import qualified Process.Listen as Listen import FS import Supervisor import Torrent import Version import qualified Test main :: IO () main = do args <- getArgs if "--tests" `elem` args then Test.runTests else progOpts args >>= run -- COMMAND LINE PARSING data Flag = Version | Debug | LogFile String deriving (Eq, Show) options :: [OptDescr Flag] options = [ Option ['V','?'] ["version"] (NoArg Version) "Show version number" , Option ['D'] ["debug"] (NoArg Debug) "Spew extra debug information" , Option [] ["logfile"] (ReqArg LogFile "FILE") "Choose a filepath on which to log" ] progOpts :: [String] -> IO ([Flag], [String]) progOpts args = do case getOpt Permute options args of (o,n,[] ) -> return (o, n) (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options)) where header = "Usage: HaskellTorrent [OPTION...] file" run :: ([Flag], [String]) -> IO () run (flags, files) = do if Version `elem` flags then progHeader else case files of [] -> putStrLn "No torrentfile input" [name] -> progHeader >> download flags name _ -> putStrLn "More than one torrent file given" progHeader :: IO () progHeader = putStrLn $ "This is Haskell-torrent version " ++ version ++ "\n" ++ " For help type 'help'\n" setupLogging :: [Flag] -> IO () setupLogging flags = do rootL <- getRootLogger fLog <- case logFlag flags of Nothing -> streamHandler SIO.stdout DEBUG Just (LogFile fp) -> fileHandler fp DEBUG when (Debug `elem` flags) (updateGlobalLogger rootLoggerName (setHandlers [fLog] . (setLevel DEBUG))) where logFlag = find (\e -> case e of LogFile _ -> True _ -> False) download :: [Flag] -> String -> IO () download flags name = do torrent <- B.readFile name let bcoded = BCode.decode torrent case bcoded of Left pe -> print pe Right bc -> do setupLogging flags debugM "Main" (show bc) (handles, haveMap, pieceMap) <- openAndCheckFile bc -- setup channels trackerC <- channel statusC <- channel waitC <- channel pieceMgrC <- channel supC <- channel fspC <- channel statInC <- channel pmC <- channel chokeC <- channel chokeInfoC <- channel debugM "Main" "Created channels" -- setup StdGen and Peer data gen <- getStdGen ti <- mkTorrentInfo bc let pid = mkPeerId gen left = bytesLeft haveMap pieceMap clientState = determineState haveMap -- Create main supervisor process tid <- allForOne "MainSup" [ Worker $ Console.start waitC statusC , Worker $ FSP.start handles pieceMap fspC , Worker $ PeerMgr.start pmC pid (infoHash ti) pieceMap pieceMgrC fspC chokeC statInC (pieceCount ti) , Worker $ PieceMgr.start pieceMgrC fspC chokeInfoC statInC (PieceMgr.createPieceDb haveMap pieceMap) , Worker $ Status.start left clientState statusC statInC trackerC , Worker $ Tracker.start ti pid defaultPort statusC statInC trackerC pmC , Worker $ ChokeMgr.start chokeC chokeInfoC 100 -- 100 is upload rate in KB (case clientState of Seeding -> True Leeching -> False) , Worker $ Listen.start defaultPort pmC ] supC sync $ transmit trackerC Status.Start sync $ receive waitC (const True) infoM "Main" "Closing down, giving processes 10 seconds to cool off" sync $ transmit supC (PleaseDie tid) threadDelay $ 10*1000000 infoM "Main" "Done..." return ()