26 theshadow 1.1 from threading import Event
27 from time import time
28
29 defaults = [
30 ('announce_list', '',
31 'a list of announce URLs - explained below'),
32 ('piece_size_pow2', 0,
33 "which power of 2 to set the piece size to (0 = automatic)"),
34 ('comment', '',
35 "optional human-readable comment to put in .torrent"),
36 ('target', '',
37 "optional target file for the torrent")
38 ]
39
40 default_piece_len_exp = 18
41
42 ignore = ['core', 'CVS']
43
44 def print_announcelist_details():
45 print (' announce_list = optional list of redundant/backup tracker URLs, in the format:')
46 print (' url[,url...][|url[,url...]...]')
47 theshadow 1.1 print (' where URLs separated by commas are all tried first')
48 print (' before the next group of URLs separated by the pipe is checked.')
49 print (" If none is given, it is assumed you don't want one in the metafile.")
50 print (' If announce_list is given, clients which support it')
51 print (' will ignore the <announce> value.')
52 print (' Examples:')
53 print (' http://tracker1.com|http://tracker2.com|http://tracker3.com')
54 print (' (tries trackers 1-3 in order)')
55 print (' http://tracker1.com,http://tracker2.com,http://tracker3.com')
56 print (' (tries trackers 1-3 in a randomly selected order)')
57 print (' http://tracker1.com|http://backup1.com,http://backup2.com')
58 print (' (tries tracker 1 first, then tries between the 2 backups randomly)')
59
60 def dummy(v):
61 pass
62
63 def make_meta_file(file, url, params = {}, flag = Event(),
64 progress = dummy, progress_percent = 1):
65 if params.has_key('piece_size_pow2'):
66 piece_len_exp = params['piece_size_pow2']
67 else:
68 theshadow 1.1 piece_len_exp = default_piece_len_exp
69 if params.has_key('target') and params['target'] != '':
70 f = params['target']
71 else:
72 a, b = split(file)
73 if b == '':
74 f = a + '.torrent'
75 else:
76 f = join(a, b + '.torrent')
77
78 if piece_len_exp == 0: # automatic
79 size = calcsize(file)
80 if size > 8L*1024*1024*1024: # > 8 gig =
81 piece_len_exp = 21 # 2 meg pieces
82 elif size > 2*1024*1024*1024: # > 2 gig =
83 piece_len_exp = 20 # 1 meg pieces
84 elif size > 512*1024*1024: # > 512M =
85 piece_len_exp = 19 # 512K pieces
86 elif size > 64*1024*1024: # > 64M =
87 piece_len_exp = 18 # 256K pieces
88 elif size > 16*1024*1024: # > 16M =
89 theshadow 1.1 piece_len_exp = 17 # 128K pieces
90 elif size > 4*1024*1024: # > 4M =
91 piece_len_exp = 16 # 64K pieces
92 else: # < 4M =
93 piece_len_exp = 15 # 32K pieces
94 piece_length = 2 ** piece_len_exp
95
96 info = makeinfo(file, piece_length, flag, progress, progress_percent)
97 if flag.isSet():
98 return
99 check_info(info)
100 h = open(f, 'wb')
101 data = {'info': info, 'announce': strip(url), 'creation date': long(time())}
102 if params.has_key('comment') and params['comment'] != '':
103 data['comment'] = params['comment']
104 if params.has_key('real_announce_list'): # shortcut for progs calling in from outside
105 data['announce-list'] = params['real_announce_list']
106 elif params.has_key('announce_list') and params['announce_list'] != '':
107 list = []
108 for tier in params['announce_list'].split('|'):
109 sublist = []
110 theshadow 1.1 for tracker in tier.split(','):
111 sublist += [tracker]
112 list += [sublist]
113 data['announce-list'] = list
114
115 h.write(bencode(data))
116 h.close()
117
118 def calcsize(file):
119 if not isdir(file):
120 return getsize(file)
121 total = 0L
122 for s in subfiles(abspath(file)):
123 total += getsize(s[1])
124 return total
125
126 def makeinfo(file, piece_length, flag, progress, progress_percent=1):
127 file = abspath(file)
128 if isdir(file):
129 subs = subfiles(file)
130 subs.sort()
131 theshadow 1.1 pieces = []
132 sh = sha()
133 done = 0L
134 fs = []
135 totalsize = 0.0
136 totalhashed = 0L
137 for p, f in subs:
138 totalsize += getsize(f)
139
140 for p, f in subs:
141 pos = 0L
142 size = getsize(f)
143 fs.append({'length': size, 'path': p})
144 h = open(f, 'rb')
145 while pos < size:
146 a = min(size - pos, piece_length - done)
147 sh.update(h.read(a))
148 if flag.isSet():
149 return
150 done += a
151 pos += a
152 theshadow 1.1 totalhashed += a
153
154 if done == piece_length:
155 pieces.append(sh.digest())
156 done = 0
157 sh = sha()
158 if progress_percent:
159 progress(totalhashed / totalsize)
160 else:
161 progress(a)
162 h.close()
163 if done > 0:
164 pieces.append(sh.digest())
165 return {'pieces': ''.join(pieces),
166 'piece length': piece_length, 'files': fs,
167 'name': split(file)[1]}
168 else:
169 size = getsize(file)
170 pieces = []
171 p = 0L
172 h = open(file, 'rb')
173 theshadow 1.1 while p < size:
174 x = h.read(min(piece_length, size - p))
175 if flag.isSet():
176 return
177 pieces.append(sha(x).digest())
178 p += piece_length
179 if p > size:
180 p = size
181 if progress_percent:
182 progress(float(p) / size)
183 else:
184 progress(min(piece_length, size - p))
185 h.close()
186 return {'pieces': ''.join(pieces),
187 'piece length': piece_length, 'length': size,
188 'name': split(file)[1]}
189
190 def subfiles(d):
191 r = []
192 stack = [([], d)]
193 while len(stack) > 0:
194 theshadow 1.1 p, n = stack.pop()
195 if isdir(n):
196 for s in listdir(n):
197 if s not in ignore and s[:1] != '.':
198 stack.append((copy(p) + [s], join(n, s)))
199 else:
200 r.append((p, n))
201 return r
202
203 def prog(amount):
204 print '%.1f%% complete\r' % (amount * 100),
205
206 if __name__ == '__main__':
207 if len(argv) < 3:
208 a,b = split(argv[0])
209 print 'Usage: ' + b + ' <trackerurl> <file> [file...] [params...]'
210 print
211 print formatDefinitions(defaults, 80)
212 print_announcelist_details()
213 print ('')
214 exit(2)
215 theshadow 1.1
216 try:
217 config, args = parseargs(argv[1:], defaults, 2, None)
218 for file in args[1:]:
219 make_meta_file(file, args[0], config, progress = prog)
220 except ValueError, e:
221 print 'error: ' + str(e)
222 print 'run with no args for parameter explanations'
|