| @@ -1,5 +1,6 @@ | |||||
| from . import bencode | from . import bencode | ||||
| from functools import reduce | |||||
| from hashlib import sha1 | from hashlib import sha1 | ||||
| import importlib.resources | import importlib.resources | ||||
| import itertools | import itertools | ||||
| @@ -19,9 +20,22 @@ class Storage: | |||||
| self._buildindex() | self._buildindex() | ||||
| def _filepaths(self): | |||||
| for curfile in self._files: | |||||
| fname = pathlib.Path( | |||||
| *(x.decode(self._encoding) for x in | |||||
| curfile['path'])) | |||||
| curfilepath = self._rootpath / fname | |||||
| yield curfile, fname, curfilepath | |||||
| def allfiles(self): | |||||
| for x, y, curfilepath in self._filepaths(): | |||||
| yield curfilepath | |||||
| def _buildindex(self): | def _buildindex(self): | ||||
| self._index = [] | self._index = [] | ||||
| files = iter(self._files) | |||||
| files = self._filepaths() | |||||
| left = 0 | left = 0 | ||||
| curfile = None | curfile = None | ||||
| @@ -29,11 +43,7 @@ class Storage: | |||||
| if curfile is None or curfileoff == curfile['length']: | if curfile is None or curfileoff == curfile['length']: | ||||
| # next file | # next file | ||||
| try: | try: | ||||
| curfile = next(files) | |||||
| fname = pathlib.Path( | |||||
| *(x.decode(self._encoding) for x in | |||||
| curfile['path'])) | |||||
| curfilepath = self._rootpath / fname | |||||
| curfile, fname, curfilepath = next(files) | |||||
| except StopIteration: | except StopIteration: | ||||
| break | break | ||||
| curfileoff = 0 | curfileoff = 0 | ||||
| @@ -51,6 +61,10 @@ class Storage: | |||||
| curfileoff += sz | curfileoff += sz | ||||
| left -= sz | left -= sz | ||||
| def filesforpiece(self, idx): | |||||
| for x in self._index[idx]: | |||||
| yield x['file'] | |||||
| def apply_piece(self, idx, fun): | def apply_piece(self, idx, fun): | ||||
| for i in self._index[idx]: | for i in self._index[idx]: | ||||
| with open(i['file'], 'rb') as fp: | with open(i['file'], 'rb') as fp: | ||||
| @@ -72,14 +86,28 @@ def validate(torrent, basedir): | |||||
| stor = Storage(torrentdir, info['files'], info['piece length'], encoding) | stor = Storage(torrentdir, info['files'], info['piece length'], encoding) | ||||
| pieces = info['pieces'] | pieces = info['pieces'] | ||||
| piecescnt = len(pieces) // 20 | |||||
| valid = [ None ] * piecescnt | |||||
| for num, i in enumerate(pieces[x:x+20] for x in range(0, len(pieces), | for num, i in enumerate(pieces[x:x+20] for x in range(0, len(pieces), | ||||
| 20)): | 20)): | ||||
| hash = sha1() | hash = sha1() | ||||
| stor.apply_piece(num, hash.update) | stor.apply_piece(num, hash.update) | ||||
| if hash.digest() != i: | |||||
| raise ValueError | |||||
| if hash.digest() == i: | |||||
| valid[num] = True | |||||
| else: | |||||
| valid[num] = False | |||||
| # if any piece of a file is bad, it's bad | |||||
| allfiles = set(stor.allfiles()) | |||||
| badpieces = [ x for x, v in enumerate(valid) if not v ] | |||||
| badfiles = reduce(set.__or__, (set(stor.filesforpiece(x)) for x in | |||||
| badpieces), set()) | |||||
| return allfiles - badfiles, badfiles | |||||
| class _TestCases(unittest.TestCase): | class _TestCases(unittest.TestCase): | ||||
| dirname = 'somedir' | dirname = 'somedir' | ||||
| @@ -155,13 +183,20 @@ class _TestCases(unittest.TestCase): | |||||
| missingfiles = self.origfiledata.copy() | missingfiles = self.origfiledata.copy() | ||||
| missingfiles['filea.txt'] = b'' | |||||
| missingfiles['filec.txt'] = b'\x00\x00\x00\x00a\n' | |||||
| missingfiles['filee.txt'] = b'no' | |||||
| badfiles = { | |||||
| 'filea.txt': b'', | |||||
| 'filec.txt': b'\x00\x00\x00\x00a\n', | |||||
| 'filee.txt': b'no', | |||||
| } | |||||
| missingfiles.update(badfiles) | |||||
| sd = self.basetempdir / self.dirname | sd = self.basetempdir / self.dirname | ||||
| sd.mkdir() | sd.mkdir() | ||||
| self.make_files(sd, missingfiles) | self.make_files(sd, missingfiles) | ||||
| self.assertRaises(ValueError, validate, self.torrent, self.basetempdir) | |||||
| val, inval = validate(self.torrent, self.basetempdir) | |||||
| self.assertEqual(set(val), { sd / x for x in missingfiles.keys() if x not in badfiles }) | |||||
| self.assertEqual(set(inval), { sd / x for x in badfiles.keys() }) | |||||