MetaData Sharing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

149 lines
3.3 KiB

  1. from . import bencode
  2. from hashlib import sha1
  3. import importlib.resources
  4. import itertools
  5. import os
  6. import pathlib
  7. import shutil
  8. import sys
  9. import tempfile
  10. import unittest
  11. class Storage:
  12. def __init__(self, rootpath, files, piecelen):
  13. self._rootpath = pathlib.Path(rootpath)
  14. self._files = files
  15. self._piecelen = piecelen
  16. self._buildindex()
  17. def _buildindex(self):
  18. self._index = []
  19. files = iter(self._files)
  20. left = 0
  21. curfile = None
  22. while True:
  23. if curfile is None or curfileoff == curfile['length']:
  24. # next file
  25. try:
  26. curfile = next(files)
  27. fname = pathlib.Path(
  28. *(x.decode('us-ascii') for x in
  29. curfile['path']))
  30. curfilepath = self._rootpath / fname
  31. except StopIteration:
  32. break
  33. curfileoff = 0
  34. if left == 0:
  35. current = []
  36. self._index.append(current)
  37. left = self._piecelen
  38. sz = min(curfile['length'] - curfileoff, left)
  39. current.append(dict(file=curfilepath, fname=fname,
  40. offset=curfileoff, size=sz))
  41. curfileoff += sz
  42. left -= sz
  43. def apply_piece(self, idx, fun):
  44. for i in self._index[idx]:
  45. with open(i['file'], 'rb') as fp:
  46. fp.seek(i['offset'])
  47. fun(fp.read(i['size']))
  48. def validate(torrent, basedir):
  49. info = torrent['info']
  50. basedir = pathlib.Path(basedir)
  51. print(repr(torrent))
  52. torrentdir = basedir / info['name'].decode('us-ascii')
  53. stor = Storage(torrentdir, info['files'], info['piece length'])
  54. pieces = info['pieces']
  55. for num, i in enumerate(pieces[x:x+20] for x in range(0, len(pieces),
  56. 20)):
  57. hash = sha1()
  58. stor.apply_piece(num, hash.update)
  59. if hash.digest() != i:
  60. raise ValueError
  61. class _TestCases(unittest.TestCase):
  62. dirname = 'somedir'
  63. origfiledata = {
  64. 'filea.txt': b'foo\n',
  65. 'fileb.txt': b'bar\n',
  66. 'filec.txt': b'bleha\n',
  67. 'filed.txt': b'somehow\n',
  68. 'filee.txt': b'nowab\n',
  69. 'filef/filef.txt': b'\n',
  70. }
  71. def setUp(self):
  72. d = pathlib.Path(tempfile.mkdtemp()).resolve()
  73. tor = importlib.resources.files(__name__)
  74. tor = tor / 'fixtures' / 'somedir.torrent'
  75. with tor.open('rb') as fp:
  76. self.torrent = bencode.bdecode(fp.read())
  77. self.basetempdir = d
  78. self.oldcwd = os.getcwd()
  79. os.chdir(d)
  80. def tearDown(self):
  81. shutil.rmtree(self.basetempdir)
  82. os.chdir(self.oldcwd)
  83. @staticmethod
  84. def make_files(dname, fdict):
  85. dname = pathlib.Path(dname)
  86. for k, v in fdict.items():
  87. k = dname / pathlib.PurePosixPath(k)
  88. k.parent.mkdir(parents=True, exist_ok=True)
  89. with open(k, 'wb') as fp:
  90. fp.write(v)
  91. def test_completeverif(self):
  92. sd = self.basetempdir / self.dirname
  93. sd.mkdir()
  94. self.make_files(sd, self.origfiledata)
  95. validate(self.torrent, self.basetempdir)
  96. def test_verification(self):
  97. # Testing for "missing" files
  98. # piece size 2 (aka 4 bytes)
  99. # empty file of 4 bytes 'foo\n'
  100. # complete file of 4 bytes 'bar\n'
  101. # partial missing file, 6 bytes, last two correct 'bleha\n'
  102. # complete file of 8 bytes (multiple pieces) 'somehow\n'
  103. # partial missing file, starting w/ 2 bytes, length 6 'nowab\n'
  104. # complete file (length 1) '\n'
  105. missingfiles = self.origfiledata.copy()
  106. missingfiles['filea.txt'] = b''
  107. missingfiles['filec.txt'] = b'\x00\x00\x00\x00a\n'
  108. missingfiles['filee.txt'] = b'no'
  109. sd = self.basetempdir / self.dirname
  110. sd.mkdir()
  111. self.make_files(sd, missingfiles)
  112. self.assertRaises(ValueError, validate, self.torrent, self.basetempdir)