| 1 | #!/usr/bin/python -O |
1 | #!/usr/bin/python -O |
| 2 | # $Id: bt_daemon.py,v 1.5 2004/06/16 05:15:31 carpaski Exp $ |
2 | # $Id: bt_daemon.py,v 1.6 2004/06/16 08:46:48 carpaski Exp $ |
| 3 | # |
3 | # |
| 4 | # Greatly modified by carpaski@gentoo.org |
4 | # Greatly modified by carpaski@gentoo.org |
| 5 | # btdownloadheadless written by Bram Cohen |
5 | # btdownloadheadless written by Bram Cohen |
| 6 | # see LICENSE.txt for license information |
6 | # see LICENSE.txt for license information |
| 7 | |
7 | |
| 8 | import os,sys,string |
8 | import os,sys,string,signal |
| 9 | os.nice(5) |
9 | os.nice(5) |
| 10 | |
10 | |
| 11 | sys.path = ["/usr/lib/portage/pym"] + sys.path + ["/usr/bin"] |
11 | sys.path = ["/usr/lib/portage/pym"] + sys.path + ["/usr/bin"] |
| 12 | from portage import getconfig |
12 | from portage import getconfig |
| 13 | |
13 | |
| 14 | import BitTorrent |
14 | import BitTorrent |
| 15 | from BitTorrent.download import download |
15 | from BitTorrent.download import download |
| 16 | |
16 | |
| 17 | from threading import Event,Thread |
17 | from threading import Event,Thread |
| 18 | import types,traceback,re,random |
18 | import types,re,random |
| 19 | |
19 | |
| 20 | from os import listdir, getcwd, stat |
20 | from os import listdir, getcwd, stat |
| 21 | from stat import ST_SIZE,ST_MTIME |
21 | from stat import ST_SIZE,ST_MTIME |
| 22 | from os.path import abspath,isdir,isfile,normpath |
22 | from os.path import abspath,isdir,isfile,normpath |
| 23 | from sys import argv, stdout, path |
23 | from sys import argv, stdout, path |
| … | |
… | |
| 131 | return newarray |
131 | return newarray |
| 132 | |
132 | |
| 133 | def list_files(dirlist): |
133 | def list_files(dirlist): |
| 134 | file_list = [] |
134 | file_list = [] |
| 135 | for mydir in dirlist: |
135 | for mydir in dirlist: |
|
|
136 | mys = mydir.split("/") |
|
|
137 | if mys[-1][0] == '.': |
|
|
138 | continue |
| 136 | if isdir(mydir): |
139 | if isdir(mydir): |
| 137 | try: |
140 | try: |
| 138 | file_list += list_files(prefix_array(listdir(mydir),mydir+"/")) |
141 | file_list += list_files(prefix_array(listdir(mydir),mydir+"/")) |
| 139 | except Exception,e: |
142 | except Exception,e: |
| 140 | print e |
143 | print e |
| … | |
… | |
| 165 | |
168 | |
| 166 | # Useless variables |
169 | # Useless variables |
| 167 | cols = 80 |
170 | cols = 80 |
| 168 | |
171 | |
| 169 | # This is 2^x ... put the x below -- Chunk size for sha1 -- 18 == 256k |
172 | # This is 2^x ... put the x below -- Chunk size for sha1 -- 18 == 256k |
| 170 | piece_size_pow2 = 18 |
173 | piece_size_pow2 = 18 |
| 171 | max_starting_threads = 1 |
174 | max_starting_threads = 1 |
| 172 | loop_mod = 4320 # Rotating value (for logs) |
175 | loop_mod = 4320 # Rotating value (for logs) |
| 173 | rotate_logs = 0 # Make one log or roate into loop_mod logs |
176 | rotate_logs = 0 # Make one log or roate into loop_mod logs |
| 174 | cycle_sleep = 20 |
177 | cycle_sleep = 20 |
| 175 | spawn_thread_delay = 0 |
178 | spawn_thread_delay = 0 |
| 176 | debug = 0 |
179 | debug = 0 |
| 177 | minimum_age = 30 |
180 | minimum_age = 120 |
| 178 | port_min = 6881 |
181 | port_min = 6881 |
| 179 | port_max = 7280 |
182 | port_max = 7280 |
| 180 | check_hashes = 0 |
183 | check_hashes = 0 |
| 181 | shuffle_files = 1 |
184 | shuffle_files = 0 |
| 182 | master_seed = 0 |
185 | master_seed = 0 |
| 183 | min_uploads = 20 |
186 | min_uploads = 20 |
| 184 | max_uploads = 20 |
187 | max_uploads = 20 |
| 185 | download_slice_size = 65536 |
188 | download_slice_size = 65536 |
| 186 | max_slice_length = 131072 |
189 | max_slice_length = 131072 |
| 187 | request_backlog = 20 |
190 | request_backlog = 20 |
| 188 | timeout = 90 |
191 | timeout = 90 |
| 189 | timeout_check_interval = 20 |
192 | timeout_check_interval = 20 |
| 190 | stats = 1 |
193 | stats = 1 |
|
|
194 | min_file_size = 5000000 |
|
|
195 | max_fetches = 3 |
| 191 | |
196 | |
| 192 | # No trailing / |
197 | # No trailing / |
| 193 | file_base = "/usr/portage/distfiles" |
198 | file_base = "/usr/portage/distfiles" |
| 194 | torrent_base = "/tmp/torrents" |
199 | torrent_base = "/tmp/torrents" |
| 195 | |
200 | |
| … | |
… | |
| 217 | threads = {} |
222 | threads = {} |
| 218 | dirs = [torrent_base[:],file_base[:]] |
223 | dirs = [torrent_base[:],file_base[:]] |
| 219 | re_file = re.compile("("+torrent_base[:]+"|"+file_base[:]+")(/.+?)(\.torrent)?$") |
224 | re_file = re.compile("("+torrent_base[:]+"|"+file_base[:]+")(/.+?)(\.torrent)?$") |
| 220 | absmax_upRate = 0.0 |
225 | absmax_upRate = 0.0 |
| 221 | absmax_upFile = '' |
226 | absmax_upFile = '' |
|
|
227 | |
|
|
228 | noticed_newfiles = [] |
| 222 | |
229 | |
| 223 | loop_val = 0 |
230 | loop_val = 0 |
| 224 | while(True): |
231 | while(True): |
| 225 | mydict = getconfig("bt_daemon.conf") |
232 | mydict = getconfig("bt_daemon.conf") |
| 226 | if not mydict: |
233 | if not mydict: |
| … | |
… | |
| 231 | |
238 | |
| 232 | if mydict.has_key("piece_size_pow2"): |
239 | if mydict.has_key("piece_size_pow2"): |
| 233 | piece_size_pow2 = int(mydict["piece_size_pow2"]) |
240 | piece_size_pow2 = int(mydict["piece_size_pow2"]) |
| 234 | if mydict.has_key("max_starting_threads"): |
241 | if mydict.has_key("max_starting_threads"): |
| 235 | max_starting_threads = int(mydict["max_starting_threads"]) |
242 | max_starting_threads = int(mydict["max_starting_threads"]) |
|
|
243 | if mydict.has_key("max_fetches"): |
|
|
244 | max_fetches = int(mydict["max_fetches"]) |
| 236 | if mydict.has_key("loop_mod"): |
245 | if mydict.has_key("loop_mod"): |
| 237 | loop_mod = int(mydict["loop_mod"]) |
246 | loop_mod = int(mydict["loop_mod"]) |
| 238 | if mydict.has_key("rotate_logs"): |
247 | if mydict.has_key("rotate_logs"): |
| 239 | rotate_logs = int(mydict["rotate_logs"]) |
248 | rotate_logs = int(mydict["rotate_logs"]) |
| 240 | if mydict.has_key("cycle_sleep"): |
249 | if mydict.has_key("cycle_sleep"): |
| 241 | cycle_sleep = int(mydict["cycle_sleep"]) |
250 | cycle_sleep = int(mydict["cycle_sleep"]) |
| 242 | if mydict.has_key("spawn_thread_delay"): |
251 | if mydict.has_key("spawn_thread_delay"): |
| 243 | spawn_thread_delay = int(mydict["spawn_thread_delay"]) |
252 | spawn_thread_delay = int(mydict["spawn_thread_delay"]) |
|
|
253 | if mydict.has_key("shuffle_files"): |
|
|
254 | shuffle_files = int(mydict["shuffle_files"]) |
| 244 | if mydict.has_key("debug"): |
255 | if mydict.has_key("debug"): |
| 245 | debug = int(mydict["debug"]) |
256 | debug = int(mydict["debug"]) |
| 246 | |
257 | |
| 247 | if mydict.has_key("min_uploads"): |
258 | if mydict.has_key("min_uploads"): |
| 248 | min_uploads = int(mydict["min_uploads"]) |
259 | min_uploads = int(mydict["min_uploads"]) |
| … | |
… | |
| 278 | loop_val = (loop_val + 1) % loop_mod |
289 | loop_val = (loop_val + 1) % loop_mod |
| 279 | files = list_files(dirs) |
290 | files = list_files(dirs) |
| 280 | files.sort() |
291 | files.sort() |
| 281 | new_files = [] |
292 | new_files = [] |
| 282 | torrents = [] |
293 | torrents = [] |
|
|
294 | changing = [] |
| 283 | |
295 | |
| 284 | if shuffle_files: |
296 | if shuffle_files: |
| 285 | random.shuffle(files) |
297 | random.shuffle(files) |
| 286 | |
298 | |
| 287 | for f in files: |
299 | for f in files: |
| … | |
… | |
| 295 | if match.group(2) not in torrents: |
307 | if match.group(2) not in torrents: |
| 296 | mystat = stat(f) |
308 | mystat = stat(f) |
| 297 | # only add if it has a size and is not actively modified. |
309 | # only add if it has a size and is not actively modified. |
| 298 | if mystat[ST_SIZE]>0 and (time()-mystat[ST_MTIME])>minimum_age: |
310 | if mystat[ST_SIZE]>0 and (time()-mystat[ST_MTIME])>minimum_age: |
| 299 | torrents.append(match.group(2)) |
311 | torrents.append(match.group(2)) |
|
|
312 | else: |
|
|
313 | changing.append(match.group(2)) |
| 300 | while(match.group(2) in new_files): |
314 | while(match.group(2) in new_files): |
| 301 | del new_files[new_files.index(match.group(2))] |
315 | del new_files[new_files.index(match.group(2))] |
| 302 | elif torrent_base != file_base and debug: |
316 | elif torrent_base != file_base and debug: |
| 303 | print "Not a torrent file:",f |
317 | print "Not a torrent file:",f |
| 304 | if match.group(1) == file_base: |
318 | if match.group(1) == file_base: |
| 305 | if match.group(3) and torrent_base != file_base and debug: |
319 | if match.group(3) and torrent_base != file_base and debug: |
| 306 | print "Torrent in file directory:",f |
320 | print "Torrent in file directory:",f |
| 307 | elif match.group(2) not in torrents: |
321 | elif match.group(2) not in torrents: |
| 308 | mystat = stat(f) |
322 | mystat = stat(f) |
|
|
323 | if mystat[ST_SIZE] < min_file_size: |
|
|
324 | pass # Too small ignore it. |
| 309 | if mystat[ST_SIZE]>0 and (time()-mystat[ST_MTIME])>minimum_age: |
325 | elif (time()-mystat[ST_MTIME]) > minimum_age: |
| 310 | new_files.append(match.group(2)) |
326 | new_files.append(match.group(2)) |
| 311 | if debug: |
327 | if debug: |
|
|
328 | if new_files[-1] not in torrents: |
|
|
329 | if new_files[-1] not in changing: |
|
|
330 | if f not in noticed_newfiles: |
| 312 | print "New file:",f |
331 | print "New file:",f |
|
|
332 | noticed_newfiles.append(f) |
|
|
333 | else: |
|
|
334 | print "Changing file:",f |
| 313 | else: |
335 | else: |
| 314 | if debug: |
336 | if debug: |
| 315 | print "Actively modified: ",f |
337 | print "Actively modified: ",f |
| 316 | except Exception,e: |
338 | except Exception,e: |
| 317 | traceback.print_last() |
|
|
| 318 | print "--- IGNORING FILE:",t |
339 | print "--- IGNORING FILE: %s '%s'" % (f,e) |
| 319 | print |
340 | print |
| 320 | |
341 | |
| 321 | for t in threads.keys(): |
342 | for t in threads.keys(): |
| 322 | # Remove threads that we don't have a bittorrent for. |
343 | # Remove threads that we don't have a bittorrent for. |
| 323 | if t not in torrents or not os.path.exists(file_base+t): |
344 | if t not in torrents or not os.path.exists(file_base+t): |
| … | |
… | |
| 386 | filename = "bt_daemon-%d.stats" % (os.getpid()) |
407 | filename = "bt_daemon-%d.stats" % (os.getpid()) |
| 387 | makenew = 0 |
408 | makenew = 0 |
| 388 | writemsg("",filename,overwrite=makenew) |
409 | writemsg("",filename,overwrite=makenew) |
| 389 | writemsg("Stats: %s" % (ctime()) ,filename) |
410 | writemsg("Stats: %s" % (ctime()) ,filename) |
| 390 | writemsg("Seeding: %d" % (len(completed)) ,filename) |
411 | writemsg("Seeding: %d" % (len(completed)) ,filename) |
| 391 | writemsg("Fetching: %d (stalled: %d) (hashing: %d)" % (len(fetching),len(stalled),len(hashing)),filename) |
412 | writemsg("Fetching: %d/%d (stalled: %d) (hashing: %d)" % (len(fetching),max_fetches,len(stalled),len(hashing)),filename) |
| 392 | writemsg("Total up: %.3f MB" % (totalup) ,filename) |
413 | writemsg("Total up: %.3f MB" % (totalup) ,filename) |
| 393 | writemsg("UpRate: %.2f k/s" % (rateup) ,filename) |
414 | writemsg("UpRate: %.2f k/s" % (rateup) ,filename) |
| 394 | writemsg("UpMaxNow: %.2f k/s '%s'" % (max_up_rate,max_up_file) ,filename) |
415 | writemsg("UpMaxNow: %.2f k/s '%s'" % (max_up_rate,max_up_file) ,filename) |
| 395 | writemsg("UpAbsMax: %.2f k/s '%s'" % (absmax_upRate,absmax_upFile) ,filename) |
416 | writemsg("UpAbsMax: %.2f k/s '%s'" % (absmax_upRate,absmax_upFile) ,filename) |
| 396 | writemsg("DownRate: %.2f k/s" % (ratedown) ,filename) |
417 | writemsg("DownRate: %.2f k/s" % (ratedown) ,filename) |
| … | |
… | |
| 408 | stats_down.sort() |
429 | stats_down.sort() |
| 409 | writemsg(string.join(stats_down, "\n"), filename+".stats_down", overwrite=1, echo=0) |
430 | writemsg(string.join(stats_down, "\n"), filename+".stats_down", overwrite=1, echo=0) |
| 410 | |
431 | |
| 411 | for t in torrents: |
432 | for t in torrents: |
| 412 | # Start threads up if we have load free for them. Startup is harsh. |
433 | # Start threads up if we have load free for them. Startup is harsh. |
|
|
434 | if len(fetching) >= max_fetches: |
|
|
435 | continue |
| 413 | if t not in threads.keys() and (max_starting_threads - len(hashing) > 0): |
436 | if t not in threads.keys() and (max_starting_threads - len(hashing) > 0): |
| 414 | hashing.append(t) |
437 | hashing.append(t) |
|
|
438 | while t in noticed_newfiles: |
|
|
439 | del noticed_newfiles[noticed_newfiles.index(t)] |
| 415 | print "Starting torrent [%s]: %s" % (ctime(),t) |
440 | print "Starting torrent [%s]: %s" % (ctime(),t) |
| 416 | h = HeadlessDisplayer() |
441 | h = HeadlessDisplayer() |
| 417 | try: |
442 | try: |
| 418 | if not os.path.isdir(file_base+"/"+os.path.dirname(t)): |
443 | if not os.path.isdir(file_base+"/"+os.path.dirname(t)): |
| 419 | os.makedirs(file_base+"/"+os.path.dirname(t)) |
444 | os.makedirs(file_base+"/"+os.path.dirname(t)) |
| … | |
… | |
| 451 | print "Failed to start thread: %s" % (t) |
476 | print "Failed to start thread: %s" % (t) |
| 452 | print "Reason: %s" % (str(e)) |
477 | print "Reason: %s" % (str(e)) |
| 453 | |
478 | |
| 454 | if master_seed: |
479 | if master_seed: |
| 455 | for f in new_files: |
480 | for f in new_files: |
|
|
481 | if f in torrents: |
|
|
482 | print "in torrents:",f |
|
|
483 | continue |
| 456 | # Create the torrents for files that don't have them. |
484 | # Create the torrents for files that don't have them. |
|
|
485 | mystat = os.stat(file_base+"/"+f) |
|
|
486 | if mystat[ST_SIZE] < min_file_size: |
|
|
487 | print "Too small:",f |
|
|
488 | continue |
|
|
489 | # only add if it has a size and is not actively modified. |
|
|
490 | if (time() - mystat[ST_MTIME]) < minimum_age: |
|
|
491 | print "Too new:",f |
|
|
492 | continue |
| 457 | try: |
493 | try: |
| 458 | print "Creating torrent [%s]: %s" % (ctime(),f) |
494 | print "Creating torrent [%s]: %s" % (ctime(),f) |
| 459 | if not os.path.isdir(torrent_base+"/"+os.path.dirname(f)): |
495 | if not os.path.isdir(torrent_base+"/"+os.path.dirname(f)): |
| 460 | os.makedirs(torrent_base+"/"+os.path.dirname(f)) |
496 | os.makedirs(torrent_base+"/"+os.path.dirname(f)) |
| 461 | make_meta_file(file_base+"/"+f,tracker,piece_size_pow2,progress=progress,comment=comment,target=torrent_base+"/"+f+".torrent") |
497 | make_meta_file(file_base+"/"+f,tracker,piece_size_pow2,progress=progress,comment=comment,target=torrent_base+"/"+f+".torrent") |
| 462 | except Exception, e: |
498 | except Exception, e: |
| 463 | print "Failed to create torrent: "+str(e) |
499 | print "Failed to create torrent: "+str(e) |
| 464 | |
500 | |
| 465 | sleep(cycle_sleep) |
501 | sleep(cycle_sleep) |
| 466 | except Exception, e: |
502 | except Exception, e: |
| 467 | import signal,traceback |
503 | sys.stderr.write("\n\nException caught. Terminating...\n%s\n\n" % e) |
| 468 | print e |
|
|
| 469 | traceback.print_stack() |
|
|
| 470 | os.kill(0,signal.SIGTERM) |
504 | os.kill(0,signal.SIGTERM) |