summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bittorrent.cabal4
-rw-r--r--examples/MkTorrent.hs73
-rw-r--r--src/Data/Torrent/Bitfield.hs13
-rw-r--r--src/System/Torrent/Storage.hs14
4 files changed, 84 insertions, 20 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal
index d721eacd..073329de 100644
--- a/bittorrent.cabal
+++ b/bittorrent.cabal
@@ -261,13 +261,13 @@ executable mktorrent
261 , pretty-class 261 , pretty-class
262 262
263 , lens 263 , lens
264 264 , parallel-io
265 , network 265 , network
266 , bittorrent 266 , bittorrent
267 267
268 , optparse-applicative 268 , optparse-applicative
269 , hslogger 269 , hslogger
270 ghc-options: -Wall -threaded 270 ghc-options: -Wall -O2 -threaded
271 271
272-- Utility to fetch 272-- Utility to fetch
273executable gettorrent 273executable gettorrent
diff --git a/examples/MkTorrent.hs b/examples/MkTorrent.hs
index 7bf8b513..49856f9b 100644
--- a/examples/MkTorrent.hs
+++ b/examples/MkTorrent.hs
@@ -5,10 +5,12 @@
5module Main (main) where 5module Main (main) where
6 6
7import Prelude as P 7import Prelude as P
8import Control.Concurrent.ParallelIO
8import Control.Exception 9import Control.Exception
9import Control.Lens hiding (argument) 10import Control.Lens hiding (argument)
10import Control.Monad 11import Control.Monad
11import Data.List as L 12import Data.List as L
13import Data.Maybe
12import Data.Monoid 14import Data.Monoid
13import Data.Text as T 15import Data.Text as T
14import qualified Data.Text.IO as T 16import qualified Data.Text.IO as T
@@ -24,8 +26,12 @@ import Text.PrettyPrint.Class
24 26
25import Paths_bittorrent (version) 27import Paths_bittorrent (version)
26import Data.Torrent 28import Data.Torrent
29import Data.Torrent.Bitfield as BF
30import Data.Torrent.Piece
31import Data.Torrent.Layout
27import Data.Torrent.Magnet hiding (Magnet (Magnet)) 32import Data.Torrent.Magnet hiding (Magnet (Magnet))
28import Data.Torrent.Magnet (Magnet) 33import Data.Torrent.Magnet (Magnet)
34import System.Torrent.Storage
29 35
30 36
31{----------------------------------------------------------------------- 37{-----------------------------------------------------------------------
@@ -138,27 +144,66 @@ amend (AmendOpts tpath) = do
138 toFile tpath $ a t 144 toFile tpath $ a t
139 145
140{----------------------------------------------------------------------- 146{-----------------------------------------------------------------------
141-- Check command 147-- Check command -- validate content files using torrent file
142-----------------------------------------------------------------------} 148-----------------------------------------------------------------------}
143 149
144{- 150data CheckOpts = CheckOpts
145checkOpts :: Parser CheckOpts 151 { checkTorrentPath :: FilePath -- ^ validation torrent file
146checkOpts = CheckOpts 152 , checkContentPath :: FilePath -- ^ root dir for content files
147 <$> torrentFile 153 } deriving Show
148 <*> argument Just
149 ( metavar "PATH"
150 <> value "."
151 <> help "Content directory or a single file" )
152 154
153checkInfo :: ParserInfo CheckOpts 155checkInfo :: ParserInfo CheckOpts
154checkInfo = info (helper <*> checkOpts) modifier 156checkInfo = info (helper <*> parser) modifier
155 where 157 where
156 modifier = progDesc "Validate integrity of torrent data" 158 modifier = progDesc "Validate integrity of torrent data"
159 parser = CheckOpts
160 <$> torrentFile
161 <*> argument Just
162 ( metavar "PATH"
163 <> value "."
164 <> help "Content directory or a single file"
165 )
166
167validatePiece :: Storage -> PieceInfo -> PieceIx -> IO (Maybe PieceIx)
168validatePiece s pinfo pix = do
169 valid <- verifyPiece s pinfo pix
170 if valid
171 then do infoM "check" $ "valid piece " ++ show pix
172 return (Just pix)
173 else do infoM "check" $ "invalid piece " ++ show pix
174 return Nothing
175
176validateStorage :: Storage -> PieceInfo -> IO Bitfield
177validateStorage s pinfo = do
178 infoM "check" "start storage validation"
179 let total = totalPieces s
180 pixs <- parallel $ L.map (validatePiece s pinfo) [0 .. total - 1]
181 infoM "check" "storage validation finished"
182 return $ fromList total $ catMaybes pixs
183
184-- TODO use local thread pool
185checkContent :: Storage -> PieceInfo -> IO ()
186checkContent s pinfo = do
187 invalids <- BF.complement <$> validateStorage s pinfo
188 if BF.null invalids
189 then noticeM "check" "all files are complete and valid"
190 else do
191 emergencyM "check" $ "there are some invalid pieces" ++ show invalids
192 exitFailure
193
194checkTorrent :: CheckOpts -> IO ()
195checkTorrent CheckOpts {..} = do
196 InfoDict {..} <- tInfoDict <$> fromFile checkTorrentPath
197 let layout = flatLayout checkContentPath idLayoutInfo
198 withStorage ReadOnly (piPieceLength idPieceInfo) layout $ \ s -> do
199 infoM "check" "files mapped"
200 checkContent s idPieceInfo
201 infoM "check" "unmapping files"
157 202
158{----------------------------------------------------------------------- 203{-----------------------------------------------------------------------
159-- Create command 204-- Create command
160-----------------------------------------------------------------------} 205-----------------------------------------------------------------------}
161 206{-
162createFlags :: Parser CreateFlags 207createFlags :: Parser CreateFlags
163createFlags = CreateFlags 208createFlags = CreateFlags
164 <$> optional (option 209 <$> optional (option
@@ -257,7 +302,7 @@ putTorrent opts @ ShowOpts {..} = do
257 302
258data Command 303data Command
259 = Amend AmendOpts 304 = Amend AmendOpts
260-- | Check CheckOpts 305 | Check CheckOpts
261-- | Create CreateOpts 306-- | Create CreateOpts
262 | Magnet MagnetOpts 307 | Magnet MagnetOpts
263 | Show ShowOpts 308 | Show ShowOpts
@@ -266,7 +311,7 @@ data Command
266commandOpts :: Parser Command 311commandOpts :: Parser Command
267commandOpts = subparser $ mconcat 312commandOpts = subparser $ mconcat
268 [ command "amend" (Amend <$> amendInfo) 313 [ command "amend" (Amend <$> amendInfo)
269-- , command "check" (Check <$> checkInfo) 314 , command "check" (Check <$> checkInfo)
270-- , command "create" (Create <$> createInfo) 315-- , command "create" (Create <$> createInfo)
271 , command "magnet" (Magnet <$> magnetInfo) 316 , command "magnet" (Magnet <$> magnetInfo)
272 , command "show" (Show <$> showInfo) 317 , command "show" (Show <$> showInfo)
@@ -349,7 +394,7 @@ parserInfo = info parser modifier
349 394
350run :: Command -> IO () 395run :: Command -> IO ()
351run (Amend opts) = amend opts 396run (Amend opts) = amend opts
352--run (Check opts) = checkTorrent opts 397run (Check opts) = checkTorrent opts
353--run (Create opts) = createTorrent opts 398--run (Create opts) = createTorrent opts
354run (Magnet opts) = magnet opts 399run (Magnet opts) = magnet opts
355run (Show opts) = putTorrent opts 400run (Show opts) = putTorrent opts
diff --git a/src/Data/Torrent/Bitfield.hs b/src/Data/Torrent/Bitfield.hs
index 8cdae69f..b65f058b 100644
--- a/src/Data/Torrent/Bitfield.hs
+++ b/src/Data/Torrent/Bitfield.hs
@@ -57,6 +57,7 @@ module Data.Torrent.Bitfield
57 , isSubsetOf 57 , isSubsetOf
58 58
59 -- ** Availability 59 -- ** Availability
60 , complement
60 , Frequency 61 , Frequency
61 , frequencies 62 , frequencies
62 , rarest 63 , rarest
@@ -194,8 +195,20 @@ findMax :: Bitfield -> PieceIx
194findMax = S.findMax . bfSet 195findMax = S.findMax . bfSet
195{-# INLINE findMax #-} 196{-# INLINE findMax #-}
196 197
198-- | Check if all pieces from first bitfield present if the second bitfield
197isSubsetOf :: Bitfield -> Bitfield -> Bool 199isSubsetOf :: Bitfield -> Bitfield -> Bool
198isSubsetOf a b = bfSet a `S.isSubsetOf` bfSet b 200isSubsetOf a b = bfSet a `S.isSubsetOf` bfSet b
201{-# INLINE isSubsetOf #-}
202
203-- | Resulting bitfield includes only missing pieces.
204complement :: Bitfield -> Bitfield
205complement Bitfield {..} = Bitfield
206 { bfSet = uni `S.difference` bfSet
207 , bfSize = bfSize
208 }
209 where
210 Bitfield _ uni = haveAll bfSize
211{-# INLINE complement #-}
199 212
200{----------------------------------------------------------------------- 213{-----------------------------------------------------------------------
201-- Availability 214-- Availability
diff --git a/src/System/Torrent/Storage.hs b/src/System/Torrent/Storage.hs
index a8b0bdc6..b5092b2e 100644
--- a/src/System/Torrent/Storage.hs
+++ b/src/System/Torrent/Storage.hs
@@ -28,6 +28,8 @@ module System.Torrent.Storage
28 , withStorage 28 , withStorage
29 29
30 -- * Query 30 -- * Query
31 , totalPieces
32 , verifyPiece
31 , genPieceInfo 33 , genPieceInfo
32 , getBitfield 34 , getBitfield
33 35
@@ -166,6 +168,10 @@ genPieceInfo s = do
166 hashes <- sourceStorage s $= C.map hashPiece $$ C.sinkLbs 168 hashes <- sourceStorage s $= C.map hashPiece $$ C.sinkLbs
167 return $ PieceInfo (pieceLen s) (HashList (BL.toStrict hashes)) 169 return $ PieceInfo (pieceLen s) (HashList (BL.toStrict hashes))
168 170
171-- | Verify specific piece using infodict hash list.
172verifyPiece :: Storage -> PieceInfo -> PieceIx -> IO Bool
173verifyPiece s pinfo pix = checkPieceLazy pinfo <$> readPiece pix s
174
169-- | Verify storage. 175-- | Verify storage.
170-- 176--
171-- Throws 'InvalidSize' if piece info size do not match with storage 177-- Throws 'InvalidSize' if piece info size do not match with storage
@@ -174,11 +180,11 @@ genPieceInfo s = do
174getBitfield :: Storage -> PieceInfo -> IO Bitfield 180getBitfield :: Storage -> PieceInfo -> IO Bitfield
175getBitfield s @ Storage {..} pinfo @ PieceInfo {..} 181getBitfield s @ Storage {..} pinfo @ PieceInfo {..}
176 | pieceLen /= piPieceLength = throwIO (InvalidSize piPieceLength) 182 | pieceLen /= piPieceLength = throwIO (InvalidSize piPieceLength)
177 | otherwise = M.foldM verifyPiece (BF.haveNone total) [0..total - 1] 183 | otherwise = M.foldM checkPiece (BF.haveNone total) [0..total - 1]
178 where 184 where
179 total = totalPieces s 185 total = totalPieces s
180 186
181 verifyPiece :: Bitfield -> PieceIx -> IO Bitfield 187 checkPiece :: Bitfield -> PieceIx -> IO Bitfield
182 verifyPiece bf pix = do 188 checkPiece bf pix = do
183 valid <- checkPieceLazy pinfo <$> readPiece pix s 189 valid <- verifyPiece s pinfo pix
184 return $ if valid then BF.insert pix bf else bf 190 return $ if valid then BF.insert pix bf else bf