forked from staceysern/bt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
metainfo.py
110 lines (86 loc) · 3.2 KB
/
metainfo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""
The Metainfo reads and parses a torrent file and makes the information
available via properties. Upon initialization, Metainfo raises an exception if
the specified file doesn't exist (IOError) or is not a valid bencoded
BitTorrent metainfo file (ValueError).
The SHA1 hash is currently computed on the info key which is bencoded after
previously having been bdecoded. This is probably not the right way to do it
because if the order of the keys is different from the original the hash will
be different.
"""
import bencode
import hashlib
class Metainfo(object):
def __init__(self, filename):
metafile = open(filename, 'rb')
try:
self._metainfo = bencode.bdecode(metafile.read())
except bencode.BTFailure:
raise ValueError("Invalid BitTorrent metainfo file format")
if 'announce' not in self._metainfo or 'info' not in self._metainfo:
raise ValueError("Invalid BitTorrent metainfo file format")
info = self._metainfo['info']
if ('piece length' not in info
or 'pieces' not in info
or 'name' not in info):
raise ValueError("Invalid BitTorrent metainfo file format")
try:
if 'length' in info:
# Single file mode
self._directory = ''
self._files = [([info['name']], info['length'])]
self._length = info['length']
else:
# Multi file mode
self._directory = info['name']
self._files = [(d['path'], d['length'])
for d in self._metainfo['info']['files']]
self._length = sum([length for (_, length) in self._files])
except:
raise ValueError("Invalid BitTorrent metainfo file format")
self._hash = hashlib.sha1(bencode.bencode(info)).digest()
self._num_pieces = len(self._metainfo['info']['pieces'])/20
@property
def announce(self):
return self._metainfo['announce']
@property
def announce_list(self):
return self._metainfo.get('announce-list', None)
@property
def creation_date(self):
return self._metainfo.get('creation date', None)
@property
def comment(self):
return self._metainfo.get('comment', "")
@property
def created_by(self):
return self._metainfo.get('created by', "")
@property
def encoding(self):
return self._metainfo.get('encoding', "")
@property
def total_length(self):
return self._length
@property
def piece_length(self):
return self._metainfo['info']['piece length']
@property
def num_pieces(self):
return self._num_pieces
def piece_hash(self, index):
if index <= self._num_pieces:
return self._metainfo['info']['pieces'][index*20:index*20+20]
else:
raise IndexError("{} out of range".format(index))
@property
def private(self):
return self._metainfo['info'].get('private', None)
@property
def directory(self):
return self._directory
@property
def files(self):
return self._files
@property
def info_hash(self):
return self._hash