diff options
-rw-r--r-- | bittorrent.cabal | 4 | ||||
-rw-r--r-- | examples/MkTorrent.hs | 73 | ||||
-rw-r--r-- | src/Data/Torrent/Bitfield.hs | 13 | ||||
-rw-r--r-- | src/System/Torrent/Storage.hs | 14 |
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 |
273 | executable gettorrent | 273 | executable 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 @@ | |||
5 | module Main (main) where | 5 | module Main (main) where |
6 | 6 | ||
7 | import Prelude as P | 7 | import Prelude as P |
8 | import Control.Concurrent.ParallelIO | ||
8 | import Control.Exception | 9 | import Control.Exception |
9 | import Control.Lens hiding (argument) | 10 | import Control.Lens hiding (argument) |
10 | import Control.Monad | 11 | import Control.Monad |
11 | import Data.List as L | 12 | import Data.List as L |
13 | import Data.Maybe | ||
12 | import Data.Monoid | 14 | import Data.Monoid |
13 | import Data.Text as T | 15 | import Data.Text as T |
14 | import qualified Data.Text.IO as T | 16 | import qualified Data.Text.IO as T |
@@ -24,8 +26,12 @@ import Text.PrettyPrint.Class | |||
24 | 26 | ||
25 | import Paths_bittorrent (version) | 27 | import Paths_bittorrent (version) |
26 | import Data.Torrent | 28 | import Data.Torrent |
29 | import Data.Torrent.Bitfield as BF | ||
30 | import Data.Torrent.Piece | ||
31 | import Data.Torrent.Layout | ||
27 | import Data.Torrent.Magnet hiding (Magnet (Magnet)) | 32 | import Data.Torrent.Magnet hiding (Magnet (Magnet)) |
28 | import Data.Torrent.Magnet (Magnet) | 33 | import Data.Torrent.Magnet (Magnet) |
34 | import 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 | {- | 150 | data CheckOpts = CheckOpts |
145 | checkOpts :: Parser CheckOpts | 151 | { checkTorrentPath :: FilePath -- ^ validation torrent file |
146 | checkOpts = 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 | ||
153 | checkInfo :: ParserInfo CheckOpts | 155 | checkInfo :: ParserInfo CheckOpts |
154 | checkInfo = info (helper <*> checkOpts) modifier | 156 | checkInfo = 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 | |||
167 | validatePiece :: Storage -> PieceInfo -> PieceIx -> IO (Maybe PieceIx) | ||
168 | validatePiece 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 | |||
176 | validateStorage :: Storage -> PieceInfo -> IO Bitfield | ||
177 | validateStorage 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 | ||
185 | checkContent :: Storage -> PieceInfo -> IO () | ||
186 | checkContent 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 | |||
194 | checkTorrent :: CheckOpts -> IO () | ||
195 | checkTorrent 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 | {- | |
162 | createFlags :: Parser CreateFlags | 207 | createFlags :: Parser CreateFlags |
163 | createFlags = CreateFlags | 208 | createFlags = CreateFlags |
164 | <$> optional (option | 209 | <$> optional (option |
@@ -257,7 +302,7 @@ putTorrent opts @ ShowOpts {..} = do | |||
257 | 302 | ||
258 | data Command | 303 | data 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 | |||
266 | commandOpts :: Parser Command | 311 | commandOpts :: Parser Command |
267 | commandOpts = subparser $ mconcat | 312 | commandOpts = 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 | ||
350 | run :: Command -> IO () | 395 | run :: Command -> IO () |
351 | run (Amend opts) = amend opts | 396 | run (Amend opts) = amend opts |
352 | --run (Check opts) = checkTorrent opts | 397 | run (Check opts) = checkTorrent opts |
353 | --run (Create opts) = createTorrent opts | 398 | --run (Create opts) = createTorrent opts |
354 | run (Magnet opts) = magnet opts | 399 | run (Magnet opts) = magnet opts |
355 | run (Show opts) = putTorrent opts | 400 | run (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 | |||
194 | findMax = S.findMax . bfSet | 195 | findMax = S.findMax . bfSet |
195 | {-# INLINE findMax #-} | 196 | {-# INLINE findMax #-} |
196 | 197 | ||
198 | -- | Check if all pieces from first bitfield present if the second bitfield | ||
197 | isSubsetOf :: Bitfield -> Bitfield -> Bool | 199 | isSubsetOf :: Bitfield -> Bitfield -> Bool |
198 | isSubsetOf a b = bfSet a `S.isSubsetOf` bfSet b | 200 | isSubsetOf a b = bfSet a `S.isSubsetOf` bfSet b |
201 | {-# INLINE isSubsetOf #-} | ||
202 | |||
203 | -- | Resulting bitfield includes only missing pieces. | ||
204 | complement :: Bitfield -> Bitfield | ||
205 | complement 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. | ||
172 | verifyPiece :: Storage -> PieceInfo -> PieceIx -> IO Bool | ||
173 | verifyPiece 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 | |||
174 | getBitfield :: Storage -> PieceInfo -> IO Bitfield | 180 | getBitfield :: Storage -> PieceInfo -> IO Bitfield |
175 | getBitfield s @ Storage {..} pinfo @ PieceInfo {..} | 181 | getBitfield 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 |