def test(self): c = Context('Root') c.start() o0 = self.SampleObj() Dispatcher.add(obj=o0, parent_obj=None, context=c) o1 = self.SampleObj() Dispatcher.add(obj=o1, parent_obj=None, context=c) Dispatcher.add_listener(src_obj=o1, dst_obj=o0) Dispatcher.send(event=Event('1'), src_obj=o0, dst_obj=o1) assert str(o1.lastHandler) == 'on1' Dispatcher.queue(event=Event('2'), src_obj=o0, dst_obj=o1) #assert str(o1.lastHandler) == 'on2' Dispatcher.send(event=Event('3'), src_obj=o0, dst_obj=o1) #assert str(o1.lastHandler) == 'on3' Dispatcher.notify(event=Event('4'), src_obj=o1) #assert str(o1.lastHandler) == 'on4' Dispatcher.notify(event=Event('4'), src_obj=o1) #assert str(o1.lastHandler) == 'on4' c.stop()
class Client(object): INITIAL_TRACKERS=['udp://tracker.openbittorrent.com:80', 'udp://tracker.istole.it:80', 'udp://open.demonii.com:80', 'udp://tracker.coppersurfer.tk:80', 'udp://tracker.leechers-paradise.org:6969', 'udp://exodus.desync.com:6969', 'udp://tracker.publicbt.com:80' "http://exodus.desync.com:6969/announce", "udp://tracker.publicbt.com:80/announce", "udp://tracker.openbittorrent.com:80/announce", "http://tracker.torrentbay.to:6969/announce", "http://fr33dom.h33t.com:3310/announce", "http://tracker.pow7.com/announce", "udp://tracker.ccc.de:80/announce", "http://tracker.bittorrent.am:80/announce", "http://denis.stalker.h3q.com:6969/announce", "udp://tracker.prq.to:80/announce", "udp://tracker.istole.it:80/announce", "udp://open.demonii.com:1337", "http://9.rarbg.com:2710/announce", "http://announce.torrentsmd.com:6969/announce", "http://bt.careland.com.cn:6969/announce", "http://explodie.org:6969/announce", "http://mgtracker.org:2710/announce", "http://tracker.best-torrents.net:6969/announce", "http://tracker.tfile.me/announce", "http://tracker.torrenty.org:6969/announce", "http://tracker1.wasabii.com.tw:6969/announce", "udp://9.rarbg.com:2710/announce", "udp://9.rarbg.me:2710/announce", "udp://coppersurfer.tk:6969/announce", "udp://tracker.btzoo.eu:80/announce", "http://www.spanishtracker.com:2710/announce", "http://www.todotorrents.com:2710/announce", ] VIDEO_EXTS={'.avi':'video/x-msvideo','.mp4':'video/mp4','.mkv':'video/x-matroska', '.m4v':'video/mp4','.mov':'video/quicktime', '.mpg':'video/mpeg','.ogv':'video/ogg', '.ogg':'video/ogg', '.webm':'video/webm', '.ts': 'video/mp2t', '.3gp':'video/3gpp'} def __init__(self, url = None, port=None, ip=None, auto_shutdown=True, wait_time=35, timeout=15, auto_delete=False, temp_path=None, is_playing_fnc=None, print_status = False): #server self.port = port if port else random.randint(8000,8099) self.ip = ip if ip else "127.0.0.1" self.server = Server((self.ip,self.port), Handler, client=self) #Options self.temp_path = temp_path if temp_path else os.path.join(os.path.dirname(__file__),"tmp") self.is_playing_fnc = is_playing_fnc self.timeout = timeout self.auto_delete = auto_delete self.wait_time= wait_time self.auto_shutdown = auto_shutdown self.buffer_size = 10 self.state_file="state" self.torrent_paramss={'save_path':self.temp_path,'storage_mode':lt.storage_mode_t.storage_mode_sparse} #State self.has_meta = False self.meta = None self.start_time = None self.last_connect = 0 self.connected = False self.closed = False self.file = None self.files = None self._th=None #Sesion self._cache=Cache(self.temp_path) self._ses=lt.session() self._ses.listen_on(0,0) #Cargamos el archivo de estado (si existe) if os.path.exists(os.path.join(self.temp_path,self.state_file)): try: with open(os.path.join(self.temp_path,self.state_file), "rb") as f: state=pickle.load(f) self._ses.load_state(state) except: pass self._start_services() #Monitor & Dispatcher self._monitor= Monitor(self) if print_status: self._monitor.add_listener(self.print_status) self._monitor.add_listener(self._check_meta) self._monitor.add_listener(self.save_state) self._monitor.add_listener(self.priorize_start_file) self._monitor.add_listener(self.announce_torrent) if self.auto_shutdown: self._monitor.add_listener(self._auto_shutdown) self._dispatcher=Dispatcher(self) self._dispatcher.add_listener(self._update_ready_pieces) #Iniciamos la URL if url: self.start_url(url) def get_play_list(self): #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) #Comprobamos que haya archivos de video if self.files: if len(self.files) > 1: return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls" else: return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].path) def get_files(self): #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) files = [] #Comprobamos que haya archivos de video if self.files: #Creamos el dict con los archivos for file in self.files: n = file.path u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n) s = file.size files.append({"name":n,"url":u,"size":s}) return files def announce_torrent(self): self._th.force_reannounce() self._th.force_dht_announce() def _auto_shutdown(self, *args, **kwargs): if self.file and self.file.cursor: self.last_connect = time.time() self.connected = True if self.is_playing_fnc and self.is_playing_fnc(): self.last_connect = time.time() self.connected = True if self.auto_shutdown: #shutdown por haber cerrado el reproductor if self.connected and self.is_playing_fnc and not self.is_playing_fnc(): if time.time() - self.last_connect - 1 > self.timeout: self.stop() #shutdown por no realizar ninguna conexion if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected: if time.time() - self.start_time - 1 > self.wait_time: self.stop() #shutdown tras la ultima conexion if (not self.file or not self.file.cursor) and self.timeout and self.connected and not self.is_playing_fnc: if time.time() - self.last_connect - 1 > self.timeout: self.stop() def save_state(self): state=self._ses.save_state() with open(os.path.join(self.temp_path,self.state_file), 'wb') as f: pickle.dump(state,f) def _update_ready_pieces(self, alert_type, alert): if alert_type == 'read_piece_alert' and self.file: self.file.update_piece(alert.piece, alert.buffer) def _check_meta(self): if self.status.state>=3 and self.status.state <= 5 and not self.has_meta: #Guardamos los metadatos self.meta = self._th.get_torrent_info() #Obtenemos la lista de archivos del meta fs=self.meta.files() files=fs if isinstance(fs, list) else [fs.at(i) for i in xrange(fs.num_files())] #Guardamos la lista de archivos self.files=self._choose_files(files) #Marcamos el primer archivo como activo self.set_file(self.files[0]) #Damos por iniciada la descarga self.start_time = time.time() #Guardamos el .torrent en el cahce self._cache.file_complete(self._th.get_torrent_info()) self.has_meta = True def _choose_files(self, files, search=None): #Obtenemos los archivos que la extension este en la lista videos=filter(lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files) if not videos: raise Exception('No video files in torrent') for v in videos: videos[videos.index(v)].index = files.index(v) return videos def set_file(self, f): #Seleccionamos el archivo que vamos a servir fmap=self.meta.map_file(f.index, 0,1) self.file=File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self) self.prioritize_file() def priorize_start_file(self): #Si tenemos un archivo seleccionado, pero no hay conexion, priorizamos el inicio del archivo if self.file and not self.file.cursor: for x in range(self.file.first_piece,self.file.last_piece): if not self._th.have_piece(x): for y in range(x,x+self.buffer_size): if y == x+self.buffer_size-1 and not self._th.have_piece(self.file.last_piece): self.prioritize_piece(self.file.last_piece,y-x) else: self.prioritize_piece(y,y-x) break def prioritize_piece(self, pc, idx): piece_duration=1000 min_deadline=2000 dl=idx*piece_duration+min_deadline self._th.set_piece_deadline(pc, dl,lt.deadline_flags.alert_when_available) if idx==0: # it enough when first piece if prioritize, no need to repeat it for following pieces # we do not need to download pieces that are lower then current index, # but last x pieces are special because players often look at end of file # for mp4 metadata - so in this case, we do not want to stop download of previous pieces tail_pieces=9 if (self.file.last_piece - pc) > tail_pieces: for i in xrange(self.file.first_piece,pc): self._th.piece_priority(i,0) self._th.reset_piece_deadline(i) # If we are skipping back we'd like to re-enable all pieces after this one # e.g. give them at least priority 1 for i in xrange(pc+1, self.file.last_piece+1): self._th.piece_priority(i,1) def prioritize_file(self): priorities=[1 if i>= self.file.first_piece and i<= self.file.last_piece else 0 \ for i in xrange(self.meta.num_pieces())] self._th.prioritize_pieces(priorities) def download_torrent(self,url): from core import scrapertools data = scrapertools.downloadpage(url) return data def start_url(self, uri): if self._th: raise Exception('Torrent is already started') if uri.startswith('http://') or uri.startswith('https://'): torrent_data = self.download_torrent(uri) info = lt.torrent_info(lt.bdecode(torrent_data)) tp = {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data elif uri.startswith('magnet:'): tp={'url':uri} resume_data=self._cache.get_resume(info_hash=Cache.hash_from_magnet(uri)) if resume_data: tp['resume_data']=resume_data elif os.path.isfile(uri): if os.access(uri,os.R_OK): info = lt.torrent_info(uri) tp= {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data else: raise ValueError('Invalid torrent path %s' % uri) else: raise ValueError("Invalid torrent %s" %uri) tp.update(self.torrent_paramss) self._th = self._ses.add_torrent(tp) for tr in self.INITIAL_TRACKERS: self._th.add_tracker({'url':tr}) self._th.set_sequential_download(True) self._th.force_reannounce() self._th.force_dht_announce() self._monitor.start() self._dispatcher.do_start(self._th, self._ses) self.server.run() def stop(self): self._dispatcher.stop() self._dispatcher.join() self._monitor.stop() self.server.stop() self._dispatcher.stop() if self._ses: self._ses.pause() if self._th: self.save_resume() self.save_state() self._stop_services() self._ses.remove_torrent(self._th, self.auto_delete) del self._ses self.closed = True def _start_services(self): self._ses.add_dht_router("router.bittorrent.com",6881) self._ses.add_dht_router("router.utorrent.com",6881) self._ses.add_dht_router("router.bitcomet.com",554) self._ses.add_dht_router("router.utorrent.com",6881) self._ses.add_dht_router("dht.transmissionbt.com",6881) self._ses.start_dht() self._ses.start_lsd() self._ses.start_upnp() self._ses.start_natpmp() def _stop_services(self): self._ses.stop_natpmp() self._ses.stop_upnp() self._ses.stop_lsd() self._ses.stop_dht() def save_resume(self): if self._th.need_save_resume_data() and self._th.is_valid() and self.meta: r = ResumeData(self) start=time.time() while (time.time() - start) <= 5 : if r.data or r.failed: break time.sleep(0.1) if r.data: self._cache.save_resume(self.unique_file_id,lt.bencode(r.data)) @property def status(self): if self._th: s = self._th.status() s._download_rate = s.download_rate / 1000 if self.file: pieces=s.pieces[self.file.first_piece:self.file.last_piece] progress= float(sum(pieces))/len(pieces) else: progress=0 s.progress_file=progress * 100 s.file_size=self.file.size / 1048576.0 if self.file else 0 if self.file and self.file.cursor: percent = len(self.file.cursor.cache) percent = percent * 100 / self.buffer_size s.buffer = int(percent) elif self.file: bp = [True if (x == self.buffer_size -1 and self._th.have_piece(self.file.last_piece)) or (x < self.buffer_size -1 and self._th.have_piece(x)) else False for x in range(self.buffer_size ) ] percent = len([a for a in bp if a == True]) percent = percent * 100 / self.buffer_size s.buffer = int(percent) else: s.buffer = 0 if self.auto_shutdown: if self.connected: if self.timeout: s.timeout = int(self.timeout - (time.time() - self.last_connect -1)) if self.file and self.file.cursor: s.timeout = self.timeout if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: if self.start_time and self.wait_time: s.timeout = int(self.wait_time - (time.time() - self.start_time -1)) if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: s.timeout = "Off" STATE_STR = ['En cola', 'Comprobando', 'Descargando metadata', \ 'Descargando', 'Finalizado', 'Seeding', 'Allocating', 'Comprobando fastresume'] s.str_state = STATE_STR[s.state] if self._ses.dht_state() is not None: s.dht_state = "On" s.dht_nodes=self._ses.status().dht_nodes else: s.dht_state = "Off" s.dht_nodes = 0 s.trackers= len(self._th.trackers()) s.dht_peers = 0 s.trk_peers = 0 s.pex_peers = 0 s.lsd_peers = 0 for peer in self._th.get_peer_info(): if peer.source & 1: s.trk_peers +=1 if peer.source & 2: s.dht_peers +=1 if peer.source & 4: s.pex_peers +=1 if peer.source & 8: s.lsd_peers +=1 return s def print_status(self): s = self.status archivo = "N/D" percent = s.buffer if percent < 75: color= "33" if percent < 40: color= "31" if percent > 75: color= "32" buffer = "\033[%sm%s%s\033[39m" % ( color ,u"\u25A0" * (percent/10), u"\u25A1" *(10-percent/10)) print '\r\033[39;m%.2f%% de %.1fMB %s | %.1f kB/s | #%s %s | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trackers: %d' % \ (s.progress_file , s.file_size, s.str_state, s._download_rate ,archivo, buffer, s.timeout , s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers,s.dht_peers, s.pex_peers, s.lsd_peers, s.dht_state, s.dht_nodes, s.trackers),
class Client(object): INITIAL_TRACKERS = [ 'udp://tracker.openbittorrent.com:80', 'udp://tracker.istole.it:80', 'udp://open.demonii.com:80', 'udp://tracker.coppersurfer.tk:80', 'udp://tracker.leechers-paradise.org:6969', 'udp://exodus.desync.com:6969', 'udp://tracker.publicbt.com:80', 'http://tracker.torrentbay.to:6969/announce', 'http://tracker.pow7.com/announce', 'udp://tracker.ccc.de:80/announce', 'udp://open.demonii.com:1337', 'http://9.rarbg.com:2710/announce', 'http://bt.careland.com.cn:6969/announce', 'http://explodie.org:6969/announce', 'http://mgtracker.org:2710/announce', 'http://tracker.best-torrents.net:6969/announce', 'http://tracker.tfile.me/announce', 'http://tracker1.wasabii.com.tw:6969/announce', 'udp://9.rarbg.com:2710/announce', 'udp://9.rarbg.me:2710/announce', 'udp://coppersurfer.tk:6969/announce', 'http://www.spanishtracker.com:2710/announce', 'http://www.todotorrents.com:2710/announce' ] ### Added some trackers from MCT VIDEO_EXTS = { '.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg', '.ogv': 'video/ogg', '.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp', '.rar': 'video/unrar' } def __init__(self, url=None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True, temp_path=None, is_playing_fnc=None, print_status=False): # server if port: self.port = port else: self.port = random.randint(8000, 8099) if ip: self.ip = ip else: self.ip = "127.0.0.1" self.server = Server((self.ip, self.port), Handler, client=self) # Options if temp_path: self.temp_path = temp_path else: self.temp_path = DOWNLOAD_PATH self.is_playing_fnc = is_playing_fnc self.timeout = timeout self.auto_delete = auto_delete self.wait_time = wait_time self.auto_shutdown = auto_shutdown self.buffer_size = BUFFER self.first_pieces_priorize = BUFFER self.last_pieces_priorize = 5 self.state_file = "state" try: self.torrent_paramss = { 'save_path': self.temp_path, 'storage_mode': lt.storage_mode_t.storage_mode_allocate } except Exception as e: try: do = xbmcgui.Dialog() e = e1 or e2 do.ok( config.get_localized_string(30035) + 'BT Libtorrent', config.get_localized_string(30036), config.get_localized_string(60015), str(e)) except: pass return # State self.has_meta = False self.meta = None self.start_time = None self.last_connect = 0 self.connected = False self.closed = False self.file = None self.files = None self._th = None self.seleccion = 0 self.index = 0 # Sesion self._cache = Cache(self.temp_path) self._ses = lt.session() #self._ses.listen_on(0, 0) ### ALFA: it blocks repro of some .torrents # Cargamos el archivo de estado (si existe) """ ### ALFA: it blocks repro of some .torrents if os.path.exists(os.path.join(self.temp_path, self.state_file)): try: f = open(os.path.join(self.temp_path, self.state_file), "rb") state = pickle.load(f) self._ses.load_state(state) f.close() except: pass """ self._start_services() # Monitor & Dispatcher self._monitor = Monitor(self) if print_status: self._monitor.add_listener(self.print_status) self._monitor.add_listener(self._check_meta) self._monitor.add_listener(self.save_state) self._monitor.add_listener(self.priorize_start_file) self._monitor.add_listener(self.announce_torrent) if self.auto_shutdown: self._monitor.add_listener(self._auto_shutdown) self._dispatcher = Dispatcher(self) self._dispatcher.add_listener(self._update_ready_pieces) # Iniciamos la URL if url: self.start_url(url) def set_speed_limits(self, download=0, upload=0): """ Función encargada de poner límites a la velocidad de descarga o subida """ if isinstance(download, int) and download > 0: self._th.set_download_limit(download * 1024) if isinstance(upload, int) and download > 0: self._th.set_upload_limit(upload * 1024) def get_play_list(self): """ Función encargada de generar el playlist """ # Esperamos a lo metadatos while not self.has_meta: time.sleep(1) # Comprobamos que haya archivos de video if self.files: if len(self.files) > 1: return "http://" + self.ip + ":" + str( self.port) + "/playlist.pls" else: return "http://" + self.ip + ":" + str( self.port) + "/" + urllib.quote(self.files[0].path) def get_files(self): """ Función encargada de genera el listado de archivos """ # Esperamos a lo metadatos while not self.has_meta: time.sleep(1) files = [] # Comprobamos que haya archivos de video if self.files: # Creamos el dict con los archivos for file in self.files: n = file.path u = "http://" + self.ip + ":" + str( self.port) + "/" + urllib.quote(n) s = file.size files.append({"name": n, "url": u, "size": s}) return files def _find_files(self, files, search=None): """ Función encargada de buscar los archivos reproducibles del torrent """ self.total_size = 0 # Obtenemos los archivos que la extension este en la lista videos = filter( lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files) if not videos: raise Exception('No video files in torrent') for v in videos: self.total_size += v.size ### ALFA videos[videos.index(v)].index = files.index(v) return videos def set_file(self, f): """ Función encargada de seleccionar el archivo que vamos a servir y por tanto, priorizar su descarga """ # Seleccionamos el archivo que vamos a servir fmap = self.meta.map_file(f.index, 0, 1) self.file = File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self) if self.seleccion < 0: ### ALFA self.file.first_piece = 0 ### ALFA self.file.last_piece = self.meta.num_pieces() ### ALFA self.file.size = self.total_size ### ALFA self.prioritize_file() def prioritize_piece(self, pc, idx): """ Función encargada de priorizar una determinada pieza """ piece_duration = 1000 min_deadline = 2000 dl = idx * piece_duration + min_deadline """ ### ALFA try: self._th.set_piece_deadline(pc, dl, lt.deadline_flags.alert_when_available) except: pass """ if idx == 0: tail_pieces = 9 # Piezas anteriores a la primera se desactivan if (self.file.last_piece - pc) > tail_pieces: for i in xrange(self.file.first_piece, pc): self._th.piece_priority(i, 0) self._th.reset_piece_deadline(i) # Piezas siguientes a la primera se activan for i in xrange(pc + 1, self.file.last_piece + 1): #self._th.piece_priority(i, 0) self._th.piece_priority(i, 1) def prioritize_file(self): """ Función encargada de priorizar las piezas correspondientes al archivo seleccionado en la funcion set_file() """ priorities = [] for i in xrange(self.meta.num_pieces()): if i >= self.file.first_piece and i <= self.file.last_piece: priorities.append(1) else: if self.index < 0: priorities.append(1) ### ALFA else: priorities.append(0) ### ALFA self._th.prioritize_pieces(priorities) x = 0 for i, _set in enumerate(self._th.piece_priorities()): if _set > 0: x += 1 #logger.info("***** Nº Pieza: %s: %s" % (i, str(_set))) logger.info("***** Piezas %s : Activas: %s" % (str(i + 1), str(x))) logger.info("***** first_piece %s : last_piece: %s" % (str(self.file.first_piece), str(self.file.last_piece))) def download_torrent(self, url): """ Función encargada de descargar un archivo .torrent """ from core import httptools data = httptools.downloadpage(url).data return data def start_url(self, uri): """ Función encargada iniciar la descarga del torrent desde la url, permite: - Url apuntando a un .torrent - Url magnet - Archivo .torrent local """ if self._th: raise Exception('Torrent is already started') if uri.startswith('http://') or uri.startswith('https://'): torrent_data = self.download_torrent(uri) info = lt.torrent_info(lt.bdecode(torrent_data)) tp = {'ti': info} resume_data = self._cache.get_resume( info_hash=str(info.info_hash())) if resume_data: tp['resume_data'] = resume_data elif uri.startswith('magnet:'): tp = {'url': uri} resume_data = self._cache.get_resume( info_hash=Cache.hash_from_magnet(uri)) if resume_data: tp['resume_data'] = resume_data elif os.path.isfile(uri): if os.access(uri, os.R_OK): info = lt.torrent_info(uri) tp = {'ti': info} resume_data = self._cache.get_resume( info_hash=str(info.info_hash())) if resume_data: tp['resume_data'] = resume_data else: raise ValueError('Invalid torrent path %s' % uri) else: raise ValueError("Invalid torrent %s" % uri) tp.update(self.torrent_paramss) self._th = self._ses.add_torrent(tp) for tr in self.INITIAL_TRACKERS: self._th.add_tracker({'url': tr}) self._th.set_sequential_download(True) self._th.force_reannounce() self._th.force_dht_announce() self._monitor.start() self._dispatcher.do_start(self._th, self._ses) self.server.run() def stop(self): """ Función encargada de de detener el torrent y salir """ self._dispatcher.stop() self._dispatcher.join() self._monitor.stop() self.server.stop() self._dispatcher.stop() if self._ses: self._ses.pause() if self._th: self.save_resume() self.save_state() self._stop_services() self._ses.remove_torrent(self._th, self.auto_delete) del self._ses self.closed = True def pause(self): """ Función encargada de de pausar el torrent """ self._ses.pause() def _start_services(self): """ Función encargada de iniciar los servicios de libtorrent: dht, lsd, upnp, natpnp """ self._ses.add_dht_router("router.bittorrent.com", 6881) self._ses.add_dht_router("router.bitcomet.com", 554) self._ses.add_dht_router("router.utorrent.com", 6881) self._ses.add_dht_router("dht.transmissionbt.com", 6881) ### from MCT self._ses.start_dht() self._ses.start_lsd() self._ses.start_upnp() self._ses.start_natpmp() def _stop_services(self): """ Función encargada de detener los servicios de libtorrent: dht, lsd, upnp, natpnp """ self._ses.stop_natpmp() self._ses.stop_upnp() self._ses.stop_lsd() self._ses.stop_dht() def save_resume(self): """ Función encargada guardar los metadatos para continuar una descarga mas rapidamente """ if self._th.need_save_resume_data() and self._th.is_valid( ) and self.meta: r = ResumeData(self) start = time.time() while (time.time() - start) <= 5: if r.data or r.failed: break time.sleep(0.1) if r.data: self._cache.save_resume(self.unique_file_id, lt.bencode(r.data)) @property def status(self): """ Función encargada de devolver el estado del torrent """ if self._th: s = self._th.status() # Download Rate s._download_rate = s.download_rate / 1024 # Progreso del archivo if self.file: pieces = s.pieces[self.file.first_piece: self.file.last_piece] ### ALFA progress = float(sum(pieces)) / len(pieces) s.pieces_len = len(pieces) ### ALFA s.pieces_sum = sum(pieces) ### ALFA #logger.info('***** Estado piezas: %s' % pieces) else: progress = 0 s.pieces_len = 0 ### ALFA s.pieces_sum = 0 ### ALFA s.progress_file = progress * 100 # Tamaño del archivo s.file_name = '' ### ALFA s.seleccion = '' ### ALFA if self.file: s.seleccion = self.seleccion ### ALFA s.file_name = self.file.path ### ALFA s.file_size = self.file.size / 1048576.0 else: s.file_size = 0 # Estado del buffer if self.file and self.file.cursor: # Con una conexion activa: Disponible vs Posicion del reproductor percent = len(self.file.cursor.cache) percent = percent * 100 / self.buffer_size s.buffer = int(percent) elif self.file: # Sin una conexion activa: Pre-buffer antes de iniciar # El Pre-buffer consta de dos partes_ # 1. Buffer al inicio del archivo para que el reproductor empieze sin cortes # 2. Buffer al final del archivo (en algunos archivos el reproductor mira el final del archivo antes de comenzar) bp = [] # El tamaño del buffer de inicio es el tamaño del buffer menos el tamaño del buffer del final first_pieces_priorize = self.buffer_size - self.last_pieces_priorize # Comprobamos qué partes del buffer del inicio estan disponibles for x in range(first_pieces_priorize): if self._th.have_piece(self.file.first_piece + x): bp.append(True) else: bp.append(False) # Comprobamos qué partes del buffer del final estan disponibles for x in range(self.last_pieces_priorize): if self._th.have_piece(self.file.last_piece - x): bp.append(True) else: bp.append(False) s.buffer = int(sum(bp) * 100 / self.buffer_size) else: # Si no hay ningun archivo seleccionado: No hay buffer s.buffer = 0 # Tiempo restante para cerrar en caso de tener el timeout activo if self.auto_shutdown: if self.connected: if self.timeout: s.timeout = int(self.timeout - (time.time() - self.last_connect - 1)) if self.file and self.file.cursor: s.timeout = self.timeout if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: if self.start_time and self.wait_time: s.timeout = int(self.wait_time - (time.time() - self.start_time - 1)) if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: s.timeout = "Off" # Estado de la descarga STATE_STR = ['Queued', 'Checking', 'Downloading Metadata', \ 'Downloading', 'Finalized', 'Seeding', 'Allocating', 'Checking Fastresume'] s.str_state = STATE_STR[s.state] # Estado DHT if self._ses.dht_state() is not None: s.dht_state = "On" s.dht_nodes = self._ses.status().dht_nodes else: s.dht_state = "Off" s.dht_nodes = 0 # Cantidad de Trackers s.trackers = len(self._th.trackers()) # Origen de los peers s.dht_peers = 0 s.trk_peers = 0 s.pex_peers = 0 s.lsd_peers = 0 for peer in self._th.get_peer_info(): if peer.source & 1: s.trk_peers += 1 if peer.source & 2: s.dht_peers += 1 if peer.source & 4: s.pex_peers += 1 if peer.source & 8: s.lsd_peers += 1 return s """ Servicios: - Estas funciones se ejecutan de forma automatica cada x tiempo en otro Thread. - Estas funciones son ejecutadas mientras el torrent esta activo algunas pueden desactivarse segun la configuracion como por ejemplo la escritura en el log """ def _auto_shutdown(self, *args, **kwargs): """ Servicio encargado de autoapagar el servidor """ if self.file and self.file.cursor: self.last_connect = time.time() self.connected = True if self.is_playing_fnc and self.is_playing_fnc(): self.last_connect = time.time() self.connected = True if self.auto_shutdown: # shudown por haber cerrado el reproductor if self.connected and self.is_playing_fnc and not self.is_playing_fnc( ): if time.time() - self.last_connect - 1 > self.timeout: self.stop() # shutdown por no realizar ninguna conexion if ( not self.file or not self.file.cursor ) and self.start_time and self.wait_time and not self.connected: if time.time() - self.start_time - 1 > self.wait_time: self.stop() # shutdown tras la ultima conexion if ( not self.file or not self.file.cursor ) and self.timeout and self.connected and not self.is_playing_fnc: if time.time() - self.last_connect - 1 > self.timeout: self.stop() def announce_torrent(self): """ Servicio encargado de anunciar el torrent """ self._th.force_reannounce() self._th.force_dht_announce() def save_state(self): """ Servicio encargado de guardar el estado """ state = self._ses.save_state() f = open(os.path.join(self.temp_path, self.state_file), 'wb') pickle.dump(state, f) f.close() def _update_ready_pieces(self, alert_type, alert): """ Servicio encargado de informar que hay una pieza disponible """ if alert_type == 'read_piece_alert' and self.file: self.file.update_piece(alert.piece, alert.buffer) def _check_meta(self): """ Servicio encargado de comprobar si los metadatos se han descargado """ if self.status.state >= 3 and self.status.state <= 5 and not self.has_meta: # Guardamos los metadatos self.meta = self._th.get_torrent_info() # Obtenemos la lista de archivos del meta fs = self.meta.files() if isinstance(fs, list): files = fs else: files = [fs.at(i) for i in xrange(fs.num_files())] # Guardamos la lista de archivos self.files = self._find_files(files) # Si hay varios vídeos (no RAR), se selecciona el vídeo o "todos" lista = [] seleccion = 0 for file in self.files: if '.rar' in str(file.path): seleccion = -9 lista += [os.path.split(str(file.path))[1]] if len(lista) > 1 and seleccion >= 0: d = xbmcgui.Dialog() seleccion = d.select( msg_header + config.get_localized_string(30034), lista) if seleccion < 0: index = 0 self.index = seleccion else: index = seleccion self.index = self.files[index].index self.seleccion = seleccion # Marcamos el primer archivo como activo self.set_file(self.files[index]) # Damos por iniciada la descarga self.start_time = time.time() # Guardamos el .torrent en el cache self._cache.file_complete(self._th.get_torrent_info()) self.has_meta = True def priorize_start_file(self): ''' Servicio encargado de priorizar el principio y final de archivo cuando no hay conexion ''' if self.file and not self.file.cursor: num_start_pieces = self.buffer_size - self.last_pieces_priorize # Cantidad de piezas a priorizar al inicio num_end_pieces = self.last_pieces_priorize # Cantidad de piezas a priorizar al final pieces_count = 0 # Priorizamos las ultimas piezas for y in range(self.file.last_piece - num_end_pieces, self.file.last_piece + 1): if not self._th.have_piece(y): self.prioritize_piece(y, pieces_count) pieces_count += 1 # Priorizamos las primeras piezas for y in range(self.file.first_piece, self.file.last_piece + 1): if not self._th.have_piece(y): if pieces_count == self.buffer_size: break self.prioritize_piece(y, pieces_count) pieces_count += 1 def print_status(self): ''' Servicio encargado de mostrar en el log el estado de la descarga ''' s = self.status ### ALFA if self.seleccion >= 0: archivo = self.seleccion + 1 else: archivo = self.seleccion logger.info( '%.2f%% de %.1fMB %s | %.1f kB/s | #%s %d%% | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d | Pieces: %d (%d)' % \ (s.progress_file, s.file_size, s.str_state, s._download_rate, archivo, s.buffer, s.timeout, s.num_seeds, \ s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers, s.dht_state, s.dht_nodes, s.trackers, s.pieces_sum, s.pieces_len)) ### ALFA
class Client(object): INITIAL_TRACKERS=['udp://tracker.openbittorrent.com:80', 'udp://tracker.istole.it:80', 'udp://open.demonii.com:80', 'udp://tracker.coppersurfer.tk:80', 'udp://tracker.leechers-paradise.org:6969', 'udp://exodus.desync.com:6969', 'udp://tracker.publicbt.com:80'] VIDEO_EXTS={'.avi':'video/x-msvideo','.mp4':'video/mp4','.mkv':'video/x-matroska', '.m4v':'video/mp4','.mov':'video/quicktime', '.mpg':'video/mpeg','.ogv':'video/ogg', '.ogg':'video/ogg', '.webm':'video/webm', '.ts': 'video/mp2t', '.3gp':'video/3gpp'} def __init__(self, url = None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True, temp_path=None, is_playing_fnc=None, print_status = False): #server if port: self.port = port else: self.port = random.randint(8000,8099) if ip: self.ip = ip else: self.ip = "127.0.0.1" self.server = Server((self.ip,self.port), Handler, client=self) #Options if temp_path: self.temp_path = temp_path else: self.temp_path = os.path.join(os.path.dirname(__file__),"tmp") self.is_playing_fnc = is_playing_fnc self.timeout = timeout self.auto_delete = auto_delete self.wait_time= wait_time self.auto_shutdown = auto_shutdown self.buffer_size = 15 self.last_pieces_priorize = 5 self.state_file="state" self.torrent_paramss={'save_path':self.temp_path,'storage_mode':lt.storage_mode_t.storage_mode_sparse} #State self.has_meta = False self.meta = None self.start_time = None self.last_connect = 0 self.connected = False self.closed = False self.file = None self.files = None self._th=None #Sesion self._cache=Cache(self.temp_path) self._ses=lt.session() self._ses.listen_on(0,0) #Cargamos el archivo de estado (si esxiste) if os.path.exists(os.path.join(self.temp_path,self.state_file)): try: f = open(os.path.join(self.temp_path,self.state_file), "rb") state=pickle.load(f) self._ses.load_state(state) f.close() except: pass self._start_services() #Monitor & Dispatcher self._monitor= Monitor(self) if print_status: self._monitor.add_listener(self.print_status) self._monitor.add_listener(self._check_meta) self._monitor.add_listener(self.save_state) self._monitor.add_listener(self.priorize_start_file) self._monitor.add_listener(self.announce_torrent) if self.auto_shutdown: self._monitor.add_listener(self._auto_shutdown) self._dispatcher=Dispatcher(self) self._dispatcher.add_listener(self._update_ready_pieces) #Iniciamos la URL if url: self.start_url(url) def get_play_list(self): """ Función encargada de generar el playlist """ #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) #Comprobamos que haya archivos de video if self.files: if len(self.files) > 1: return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls" else: return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].path) def get_files(self): """ Función encargada de genera el listado de archivos """ #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) files = [] #Comprobamos que haya archivos de video if self.files: #Creamos el dict con los archivos for file in self.files: n = file.path u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n) s = file.size files.append({"name":n,"url":u,"size":s}) return files def _find_files(self, files, search=None): """ Función encargada de buscar los archivos reproducibles del torrent """ #Obtenemos los archivos que la extension este en la lista videos=filter(lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files) if not videos: raise Exception('No video files in torrent') for v in videos: videos[videos.index(v)].index = files.index(v) return videos def set_file(self, f): """ Función encargada de seleccionar el archivo que vamos a servir y por tanto, priorizar su descarga """ #Seleccionamos el archivo que vamos a servir fmap=self.meta.map_file(f.index, 0,1) self.file=File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self) self.prioritize_file() def prioritize_piece(self, pc, idx): """ Función encargada de priorizar una determinada pieza """ piece_duration=1000 min_deadline=2000 dl=idx*piece_duration+min_deadline self._th.set_piece_deadline(pc, dl,lt.deadline_flags.alert_when_available) if idx==0: tail_pieces=9 #Piezas anteriores a la primera se desactivan if (self.file.last_piece - pc) > tail_pieces: for i in xrange(self.file.first_piece,pc): self._th.piece_priority(i,0) self._th.reset_piece_deadline(i) #Piezas siguientes a la primera se activan for i in xrange(pc+1, self.file.last_piece+1): self._th.piece_priority(i,1) def prioritize_file(self): """ Función encargada de priorizar las piezas correspondientes al archivo seleccionado en la funcion set_file() """ priorities = [] for i in xrange(self.meta.num_pieces()): if i>= self.file.first_piece and i<= self.file.last_piece: priorities.append(1) else: priorities.append(0) self._th.prioritize_pieces(priorities) def download_torrent(self,url): """ Función encargada de descargar un archivo .torrent """ from core import scrapertools data = scrapertools.downloadpage(url) return data def start_url(self, uri): """ Función encargada iniciar la descarga del torrent desde la url, permite: - Url apuntando a un .torrent - Url magnet - Archivo .torrent local """ if self._th: raise Exception('Torrent is already started') if uri.startswith('http://') or uri.startswith('https://'): torrent_data = self.download_torrent(uri) info = lt.torrent_info(lt.bdecode(torrent_data)) tp = {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data elif uri.startswith('magnet:'): tp={'url':uri} resume_data=self._cache.get_resume(info_hash=Cache.hash_from_magnet(uri)) if resume_data: tp['resume_data']=resume_data elif os.path.isfile(uri): if os.access(uri,os.R_OK): info = lt.torrent_info(uri) tp= {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data else: raise ValueError('Invalid torrent path %s' % uri) else: raise ValueError("Invalid torrent %s" %uri) tp.update(self.torrent_paramss) self._th = self._ses.add_torrent(tp) for tr in self.INITIAL_TRACKERS: self._th.add_tracker({'url':tr}) self._th.set_sequential_download(True) self._th.force_reannounce() self._th.force_dht_announce() self._monitor.start() self._dispatcher.do_start(self._th, self._ses) self.server.run() def stop(self): """ Función encargada de de detener el torrent y salir """ self._dispatcher.stop() self._dispatcher.join() self._monitor.stop() self.server.stop() self._dispatcher.stop() if self._ses: self._ses.pause() if self._th: self.save_resume() self.save_state() self._stop_services() self._ses.remove_torrent(self._th, self.auto_delete) del self._ses self.closed = True def _start_services(self): """ Función encargada de iniciar los servicios de libtorrent: dht, lsd, upnp, natpnp """ self._ses.add_dht_router("router.bittorrent.com",6881) self._ses.add_dht_router("router.bitcomet.com",554) self._ses.add_dht_router("router.utorrent.com",6881) self._ses.start_dht() self._ses.start_lsd() self._ses.start_upnp() self._ses.start_natpmp() def _stop_services(self): """ Función encargada de detener los servicios de libtorrent: dht, lsd, upnp, natpnp """ self._ses.stop_natpmp() self._ses.stop_upnp() self._ses.stop_lsd() self._ses.stop_dht() def save_resume(self): """ Función encargada guardar los metadatos para continuar una descarga mas rapidamente """ if self._th.need_save_resume_data() and self._th.is_valid() and self.meta: r = ResumeData(self) start=time.time() while (time.time() - start) <= 5 : if r.data or r.failed: break time.sleep(0.1) if r.data: self._cache.save_resume(self.unique_file_id,lt.bencode(r.data)) @property def status(self): """ Función encargada de devolver el estado del torrent """ if self._th: s = self._th.status() #Download Rate s._download_rate = s.download_rate / 1000 #Progreso del archivo if self.file: pieces=s.pieces[self.file.first_piece:self.file.last_piece] progress= float(sum(pieces))/len(pieces) else: progress=0 s.progress_file=progress * 100 #Tamaño del archivo if self.file: s.file_size=self.file.size / 1048576.0 else: s.file_size=0 #Estado del buffer if self.file and self.file.cursor: #Con una conexion activa: Disponible vs Posicion del reproductor percent = len(self.file.cursor.cache) percent = percent * 100 / self.buffer_size s.buffer = int(percent) elif self.file: #Sin una conexion activa: Pre-buffer antes de iniciar #El Pre-buffer consta de dos partes_ # 1. Buffer al inicio del archivo para que el reproductor empieze sin cortes # 2. Buffer al final del archivo (en algunos archivos el reproductor mira el final del archivo antes de comenzar) bp = [] #El tamaño del buffer de inicio es el tamaño del buffer menos el tamaño del buffer del final first_pieces_priorize = self.buffer_size - self.last_pieces_priorize #Comprobamos que partes del buffer del inicio estan disponibles for x in range(first_pieces_priorize): if self._th.have_piece(self.file.first_piece + x): bp.append(True) else: bp.append(False) #Comprobamos que partes del buffer del final estan disponibles for x in range(self.last_pieces_priorize): if self._th.have_piece(self.file.last_piece - x): bp.append(True) else: bp.append(False) s.buffer = int(sum(bp) * 100 / self.buffer_size) else: #Si no hay ningun archivo seleccionado: No hay buffer s.buffer = 0 #Tiempo restante para cerrar en caso de tener el timeout activo if self.auto_shutdown: if self.connected: if self.timeout: s.timeout = int(self.timeout - (time.time() - self.last_connect -1)) if self.file and self.file.cursor: s.timeout = self.timeout if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: if self.start_time and self.wait_time: s.timeout = int(self.wait_time - (time.time() - self.start_time -1)) if s.timeout < 0: s.timeout = "Cerrando" else: s.timeout = "---" else: s.timeout = "Off" #Estado de la descarga STATE_STR = ['Queue', 'Verify', 'Download metadata', \ 'Download', 'Finalizing', 'Seeding', 'Allocating', 'Verify fastresume'] s.str_state = STATE_STR[s.state] #Estado DHT if self._ses.dht_state() is not None: s.dht_state = "On" s.dht_nodes=self._ses.status().dht_nodes else: s.dht_state = "Off" s.dht_nodes = 0 #Cantidad de Trackers s.trackers= len(self._th.trackers()) #Origen de los peers s.dht_peers = 0 s.trk_peers = 0 s.pex_peers = 0 s.lsd_peers = 0 for peer in self._th.get_peer_info(): if peer.source & 1: s.trk_peers +=1 if peer.source & 2: s.dht_peers +=1 if peer.source & 4: s.pex_peers +=1 if peer.source & 8: s.lsd_peers +=1 return s """ Servicios: - Estas funciones se ejecutan de forma automatica cada x tiempo en otro Thread. - Estas funciones son ejecutadas mientras el torrent esta activo algunas pueden desactivarse segun la configuracion como por ejemplo la escritura en el log """ def _auto_shutdown(self, *args, **kwargs): """ Servicio encargado de autoapagar el servidor """ if self.file and self.file.cursor: self.last_connect = time.time() self.connected = True if self.is_playing_fnc and self.is_playing_fnc(): self.last_connect = time.time() self.connected = True if self.auto_shutdown: #shudown por haber cerrado el reproductor if self.connected and self.is_playing_fnc and not self.is_playing_fnc(): if time.time() - self.last_connect - 1 > self.timeout: self.stop() #shutdown por no realizar ninguna conexion if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected: if time.time() - self.start_time - 1 > self.wait_time: self.stop() #shutdown tras la ultima conexion if (not self.file or not self.file.cursor) and self.timeout and self.connected and not self.is_playing_fnc: if time.time() - self.last_connect - 1 > self.timeout: self.stop() def announce_torrent(self): """ Servicio encargado de anunciar el torrent """ self._th.force_reannounce() self._th.force_dht_announce() def save_state(self): """ Servicio encargado de guardar el estado """ state=self._ses.save_state() f = open(os.path.join(self.temp_path,self.state_file), 'wb') pickle.dump(state,f) f.close() def _update_ready_pieces(self, alert_type, alert): """ Servicio encargado de informar que hay una pieza disponible """ if alert_type == 'read_piece_alert' and self.file: self.file.update_piece(alert.piece, alert.buffer) def _check_meta(self): ''' Servicio encargado de comprobar si los metadatos se han descargado ''' if self.status.state>=3 and self.status.state <= 5 and not self.has_meta: #Guardamos los metadatos self.meta = self._th.get_torrent_info() #Obtenemos la lista de archivos del meta fs=self.meta.files() if isinstance(fs, list): files=fs else: files = [fs.at(i) for i in xrange(fs.num_files())] #Guardamos la lista de archivos self.files=self._find_files(files) #Marcamos el primer archivo como activo self.set_file(self.files[0]) #Damos por iniciada la descarga self.start_time = time.time() #Guardamos el .torrent en el cahce self._cache.file_complete(self._th.get_torrent_info()) self.has_meta = True def priorize_start_file(self): ''' Servicio encargado de priorizar el principio y final de archivo cuando no hay conexion ''' if self.file and not self.file.cursor: num_start_pieces = self.buffer_size - self.last_pieces_priorize #Cantidad de piezas a priorizar al inicio num_end_pieces = self.last_pieces_priorize #Canridad de piezas a priorizar al final pieces_count = 0 #Priorizamos las ultimas piezas for y in range(self.file.last_piece - num_end_pieces, self.file.last_piece + 1): if not self._th.have_piece(y): self.prioritize_piece(y,pieces_count) pieces_count +=1 #Priorizamos las primeras piezas for y in range(self.file.first_piece,self.file.last_piece + 1): if not self._th.have_piece(y): if pieces_count == self.buffer_size: break self.prioritize_piece(y,pieces_count) pieces_count +=1 def print_status(self): """ Servicio encargado de mostrar en el log el estado de la descarga """ s = self.status if self.file: archivo = self.file.index else: archivo = "N/D" logger.info('%.2f%% de %.1fMB %s | %.1f kB/s | #%s %d%% | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d' % \ (s.progress_file , s.file_size, s.str_state, s._download_rate, archivo, s.buffer, s.timeout , s.num_seeds,\ s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers,s.dht_peers, s.pex_peers, s.lsd_peers, s.dht_state, s.dht_nodes, s.trackers))
class Client(object): INITIAL_TRACKERS=['udp://tracker.openbittorrent.com:80', 'udp://tracker.istole.it:80', 'udp://open.demonii.com:80', 'udp://tracker.coppersurfer.tk:80', 'udp://tracker.leechers-paradise.org:6969', 'udp://exodus.desync.com:6969', 'udp://tracker.publicbt.com:80'] VIDEO_EXTS={'.avi':'video/x-msvideo','.mp4':'video/mp4','.mkv':'video/x-matroska', '.m4v':'video/mp4','.mov':'video/quicktime', '.mpg':'video/mpeg','.ogv':'video/ogg', '.ogg':'video/ogg', '.webm':'video/webm', '.ts': 'video/mp2t', '.3gp':'video/3gpp'} def __init__(self, url = None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True, temp_path=None, is_playing_fnc=None, print_status = False): #server self.port = port if port else random.randint(8000,8099) self.ip = ip if ip else "127.0.0.1" self.server = Server((self.ip,self.port), Handler, client=self) #Options self.temp_path = temp_path if temp_path else os.path.join(os.path.dirname(__file__),"tmp") self.is_playing_fnc = is_playing_fnc self.timeout = timeout self.auto_delete = auto_delete self.wait_time= wait_time self.auto_shutdown = auto_shutdown self.buffer_size = 10 self.state_file="state" self.torrent_paramss={'save_path':self.temp_path,'storage_mode':lt.storage_mode_t.storage_mode_sparse} #State self.has_meta = False self.meta = None self.start_time = None self.last_connect = 0 self.connected = False self.closed = False self.file = None self.files = None self._th=None #Sesion self._cache=Cache(self.temp_path) self._ses=lt.session() self._ses.listen_on(0,0) #Cargamos el archivo de estado (si esxiste) if os.path.exists(os.path.join(self.temp_path,self.state_file)): try: with open(os.path.join(self.temp_path,self.state_file), "rb") as f: state=pickle.load(f) self._ses.load_state(state) except: pass self._start_services() #Monitor & Dispatcher self._monitor= Monitor(self) if print_status: self._monitor.add_listener(self.print_status) self._monitor.add_listener(self._check_meta) self._monitor.add_listener(self.save_state) self._monitor.add_listener(self.priorize_start_file) self._monitor.add_listener(self.announce_torrent) if self.auto_shutdown: self._monitor.add_listener(self._auto_shutdown) self._dispatcher=Dispatcher(self) self._dispatcher.add_listener(self._update_ready_pieces) #Iniciamos la URL if url: self.start_url(url) def get_play_list(self): #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) #Comprobamos que haya archivos de video if self.files: if len(self.files) > 1: return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls" else: return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].path) def get_files(self): #Esperamos a lo metadatos while not self.has_meta: time.sleep(1) files = [] #Comprobamos que haya archivos de video if self.files: #Creamos el dict con los archivos for file in self.files: n = file.path u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n) s = file.size files.append({"name":n,"url":u,"size":s}) return files def announce_torrent(self): self._th.force_reannounce() self._th.force_dht_announce() def _auto_shutdown(self, *args, **kwargs): if self.file and self.file.cursor: self.last_connect = time.time() self.connected = True if self.is_playing_fnc and self.is_playing_fnc(): self.last_connect = time.time() self.connected = True if self.auto_shutdown: #shudown por haber cerrado el reproductor if self.connected and self.is_playing_fnc and not self.is_playing_fnc(): if time.time() - self.last_connect - 1 > self.timeout: self.stop() #shutdown por no realizar ninguna conexion if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected: if time.time() - self.start_time - 1 > self.wait_time: self.stop() #shutdown tras la ultima conexion if (not self.file or not self.file.cursor) and self.timeout and self.connected and not self.is_playing_fnc: if time.time() - self.last_connect - 1 > self.timeout: self.stop() def save_state(self): state=self._ses.save_state() with open(os.path.join(self.temp_path,self.state_file), 'wb') as f: pickle.dump(state,f) def _update_ready_pieces(self, alert_type, alert): if alert_type == 'read_piece_alert' and self.file: self.file.update_piece(alert.piece, alert.buffer) def _check_meta(self): if self.status.state>=3 and self.status.state <= 5 and not self.has_meta: #Guardamos los metadatos self.meta = self._th.get_torrent_info() #Obtenemos la lista de archivos del meta fs=self.meta.files() files=fs if isinstance(fs, list) else [fs.at(i) for i in xrange(fs.num_files())] #Guardamos la lista de archivos self.files=self._choose_files(files) #Marcamos el primer archivo como activo self.set_file(self.files[0]) #Damos por iniciada la descarga self.start_time = time.time() #Guardamos el .torrent en el cahce self._cache.file_complete(self._th.get_torrent_info()) self.has_meta = True def _choose_files(self, files, search=None): #Obtenemos los archivos que la extension este en la lista videos=filter(lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files) if not videos: raise Exception('No video files in torrent') for v in videos: videos[videos.index(v)].index = files.index(v) return videos def set_file(self, f): #Seleccionamos el archivo que vamos a servir fmap=self.meta.map_file(f.index, 0,1) self.file=File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self) self.prioritize_file() def priorize_start_file(self): #Si tenemos un archivo seleccionado, pero no hay conexion, priorizamos el inicio del archivo if self.file and not self.file.cursor: for x in range(self.file.first_piece,self.file.last_piece): if not self._th.have_piece(x): for y in range(x,x+self.buffer_size): if y == x+self.buffer_size-1 and not self._th.have_piece(self.file.last_piece): self.prioritize_piece(self.file.last_piece,y-x) else: self.prioritize_piece(y,y-x) break def prioritize_piece(self, pc, idx): piece_duration=1000 min_deadline=2000 dl=idx*piece_duration+min_deadline self._th.set_piece_deadline(pc, dl,lt.deadline_flags.alert_when_available) if idx==0: # it enough when first piece if prioritize, no need to repeat it for following pieces # we do not need to download pieces that are lower then current index, # but last x pieces are special because players often look at end of file # for mp4 metadata - so in this case, we do not want to stop download of previous pieces tail_pieces=9 if (self.file.last_piece - pc) > tail_pieces: for i in xrange(self.file.first_piece,pc): self._th.piece_priority(i,0) self._th.reset_piece_deadline(i) # If we are skipping back we'd like to re-enable all pieces after this one # e.g. give them at least priority 1 for i in xrange(pc+1, self.file.last_piece+1): self._th.piece_priority(i,1) def prioritize_file(self): priorities=[1 if i>= self.file.first_piece and i<= self.file.last_piece else 0 \ for i in xrange(self.meta.num_pieces())] self._th.prioritize_pieces(priorities) def download_torrent(self,url): from core import scrapertools data = scrapertools.downloadpage(url) return data def start_url(self, uri): if self._th: raise Exception('Torrent is already started') if uri.startswith('http://') or uri.startswith('https://'): torrent_data = self.download_torrent(uri) info = lt.torrent_info(lt.bdecode(torrent_data)) tp = {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data elif uri.startswith('magnet:'): tp={'url':uri} resume_data=self._cache.get_resume(info_hash=Cache.hash_from_magnet(uri)) if resume_data: tp['resume_data']=resume_data elif os.path.isfile(uri): if os.access(uri,os.R_OK): info = lt.torrent_info(uri) tp= {'ti':info} resume_data= self._cache.get_resume(info_hash=str(info.info_hash())) if resume_data: tp['resume_data']=resume_data else: raise ValueError('Invalid torrent path %s' % uri) else: raise ValueError("Invalid torrent %s" %uri) tp.update(self.torrent_paramss) self._th = self._ses.add_torrent(tp) for tr in self.INITIAL_TRACKERS: self._th.add_tracker({'url':tr}) self._th.set_sequential_download(True) self._th.force_reannounce() self._th.force_dht_announce() self._monitor.start() self._dispatcher.do_start(self._th, self._ses) self.server.run() def stop(self): self._dispatcher.stop() self._dispatcher.join() self._monitor.stop() self.server.stop() self._dispatcher.stop() if self._ses: self._ses.pause() if self._th: self.save_resume() self.save_state() self._stop_services() self._ses.remove_torrent(self._th, self.auto_delete) del self._ses self.closed = True def _start_services(self): self._ses.add_dht_router("router.bittorrent.com",6881) self._ses.add_dht_router("router.bitcomet.com",554) self._ses.add_dht_router("router.utorrent.com",6881) self._ses.start_dht() self._ses.start_lsd() self._ses.start_upnp() self._ses.start_natpmp() def _stop_services(self): self._ses.stop_natpmp() self._ses.stop_upnp() self._ses.stop_lsd() self._ses.stop_dht() def save_resume(self): if self._th.need_save_resume_data() and self._th.is_valid() and self.meta: r = ResumeData(self) start=time.time() while (time.time() - start) <= 5 : if r.data or r.failed: break time.sleep(0.1) if r.data: self._cache.save_resume(self.unique_file_id,lt.bencode(r.data)) @property def status(self): if self._th: s = self._th.status() s._download_rate = s.download_rate / 1000 if self.file: pieces=s.pieces[self.file.first_piece:self.file.last_piece] progress= float(sum(pieces))/len(pieces) else: progress=0 s.progress_file=progress * 100 s.file_size=self.file.size / 1048576.0 if self.file else 0 if self.file and self.file.cursor: percent = len(self.file.cursor.cache) percent = percent * 100 / self.buffer_size s.buffer = int(percent) elif self.file: bp = [True if (x == self.buffer_size -1 and self._th.have_piece(self.file.last_piece)) or (x < self.buffer_size -1 and self._th.have_piece(x)) else False for x in range(self.buffer_size ) ] percent = len([a for a in bp if a == True]) percent = percent * 100 / self.buffer_size s.buffer = int(percent) else: s.buffer = 0 if self.auto_shutdown: if self.connected: if self.timeout: s.timeout = int(self.timeout - (time.time() - self.last_connect -1)) if self.file and self.file.cursor: s.timeout = self.timeout if s.timeout < 0: s.timeout = "Chiusura" else: s.timeout = "---" else: if self.start_time and self.wait_time: s.timeout = int(self.wait_time - (time.time() - self.start_time -1)) if s.timeout < 0: s.timeout = "Chiusura" else: s.timeout = "---" else: s.timeout = "Off" STATE_STR = ['Coda', 'Test', 'Download metadata', \ 'Download', 'Fatto', 'Seeding', 'Assegnazione', 'Controllo fastresume'] s.str_state = STATE_STR[s.state] if self._ses.dht_state() is not None: s.dht_state = "On" s.dht_nodes=self._ses.status().dht_nodes else: s.dht_state = "Off" s.dht_nodes = 0 s.trackers= len(self._th.trackers()) s.dht_peers = 0 s.trk_peers = 0 s.pex_peers = 0 s.lsd_peers = 0 for peer in self._th.get_peer_info(): if peer.source & 1: s.trk_peers +=1 if peer.source & 2: s.dht_peers +=1 if peer.source & 4: s.pex_peers +=1 if peer.source & 8: s.lsd_peers +=1 return s def print_status(self): s = self.status archivo = "N/D" percent = s.buffer if percent < 75: color= "33" if percent < 40: color= "31" if percent > 75: color= "32" buffer = "\033[%sm%s%s\033[39m" % ( color ,u"\u25A0" * (percent/10), u"\u25A1" *(10-percent/10)) print '\r\033[39;m%.2f%% de %.1fMB %s | %.1f kB/s | #%s %s | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d' % \ (s.progress_file , s.file_size, s.str_state, s._download_rate ,archivo, buffer, s.timeout , s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers,s.dht_peers, s.pex_peers, s.lsd_peers, s.dht_state, s.dht_nodes, s.trackers),
class Client(object): initial_trackers = [ 'udp://tracker.openbittorrent.com:80', 'udp://tracker.istole.it:80', 'udp://open.demonii.com:80', 'udp://tracker.coppersurfer.tk:80', 'udp://tracker.leechers-paradise.org:6969', 'udp://exodus.desync.com:6969', 'udp://tracker.publicbt.com:80' ] video_exts = { '.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg', '.ogv': 'video/ogg', '.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp' } def __init__(self, url=None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True, temp_path=None, is_playing_fnc=None, print_status=False): # server self.port = port or random.randint(8000, 8099) self.ip = ip or "127.0.0.1" self.server = Server((self.ip, self.port), Handler, client=self) # Options self.temp_path = temp_path or os.path.join(os.path.dirname(__file__), "tmp") self.is_playing_fnc = is_playing_fnc self.timeout = timeout self.auto_delete = auto_delete self.wait_time = wait_time self.auto_shutdown = auto_shutdown self.buffer_size = 10 self.last_pieces_priorize = 1 self.state_file = "state" self.torrent_paramss = { 'save_path': self.temp_path, 'storage_mode': lt.storage_mode_t.storage_mode_sparse } # State self.has_meta = False self.meta = None self.start_time = None self.last_connect = 0 self.connected = False self.closed = False self.files = [] self.th = None # Sesion self.cache = Cache(self.temp_path) self.ses = lt.session() self.ses.listen_on(0, 0) # Cargamos el archivo de estado (si esxiste) if os.path.exists(os.path.join(self.temp_path, self.state_file)): try: f = open(os.path.join(self.temp_path, self.state_file), "rb") state = pickle.load(f) self.ses.load_state(state) f.close() except Exception: pass self._start_services() # Monitor & Dispatcher self._monitor = Monitor(self) if print_status: self._monitor.add_listener(self.print_status) self._monitor.add_listener(self._check_meta) self._monitor.add_listener(self.save_state) self._monitor.add_listener(self.priorize_service) self._monitor.add_listener(self.announce_torrent) if self.auto_shutdown: self._monitor.add_listener(self._auto_shutdown) self._dispatcher = Dispatcher(self) self._dispatcher.add_listener(self._update_ready_pieces) # Iniciamos la URL if url: self.start_url(url) def wait_metadata(self): while not self.has_meta: time.sleep(1) def get_play_list(self): """ Función encargada de generar el playlist """ # Esperamos a lo metadatos self.wait_metadata() # Comprobamos que haya archivos de video if self.files: if len(self.files) > 1: return "http://" + self.ip + ":" + str( self.port) + "/playlist.pls" else: return "http://" + self.ip + ":" + str( self.port) + "/" + urllib.quote(self.files[0].path) def get_files(self): """ Función encargada de genera el listado de archivos """ # Esperamos a lo metadatos self.wait_metadata() files = [] # Comprobamos que haya archivos de video if self.files: # Creamos el dict con los archivos for f in self.files: n = f.path u = "http://" + self.ip + ":" + str( self.port) + "/" + urllib.quote(n) s = f.size files.append({"name": n, "url": u, "size": s}) return files def _find_files(self, files): """ Función encargada de buscar los archivos reproducibles del torrent """ # Obtenemos los archivos que la extension este en la lista videos = filter( lambda f: os.path.splitext(f.path)[1] in self.video_exts, files) if not videos: raise Exception('No video files in torrent') result = [] for v in videos: v.index = files.index(v) result.append( File(v, self.temp_path, self.meta.map_file(v.index, 0, 1), self.meta.piece_length(), self)) return result def set_file(self, f): """ Función encargada de seleccionar el archivo que vamos a servir y por tanto precargar el buffer """ if isinstance(f, str): for ff in self.files: if ff.path == f: ff.wait_initial_buffer(True) return ff else: f.wait_initial_buffer() @staticmethod def download_torrent(url): """ Función encargada de descargar un archivo .torrent """ data = httptools.downloadpage(url).data return data def start_url(self, uri): """ Función encargada iniciar la descarga del torrent desde la url, permite: - Url apuntando a un .torrent - Url magnet - Archivo .torrent local """ if self.th: raise Exception('Torrent is already started') if uri.startswith('http://') or uri.startswith('https://'): torrent_data = self.download_torrent(uri) info = lt.torrent_info(lt.bdecode(torrent_data)) tp = {'ti': info} resume_data = self.cache.get_resume( info_hash=str(info.info_hash())) if resume_data: tp['resume_data'] = resume_data elif uri.startswith('magnet:'): tp = {'url': uri} resume_data = self.cache.get_resume( info_hash=Cache.hash_from_magnet(uri)) if resume_data: tp['resume_data'] = resume_data elif os.path.isfile(uri): if os.access(uri, os.R_OK): info = lt.torrent_info(uri) tp = {'ti': info} resume_data = self.cache.get_resume( info_hash=str(info.info_hash())) if resume_data: tp['resume_data'] = resume_data else: raise ValueError('Invalid torrent path %s' % uri) else: raise ValueError("Invalid torrent %s" % uri) tp.update(self.torrent_paramss) self.th = self.ses.add_torrent(tp) for tr in self.initial_trackers: self.th.add_tracker({'url': tr}) self.th.set_sequential_download(True) self.th.force_reannounce() self.th.force_dht_announce() self._monitor.start() self._dispatcher.do_start(self.th, self.ses) self.server.run() def stop(self): """ Función encargada de de detener el torrent y salir """ self._dispatcher.stop() self._dispatcher.join() self._monitor.stop() self.server.stop() if self.ses: self.ses.pause() if self.th: ResumeData(self).save_resume_data() self.save_state() self._stop_services() self.ses.remove_torrent(self.th, self.auto_delete) del self.ses self.closed = True def _start_services(self): """ Función encargada de iniciar los servicios de libtorrent: dht, lsd, upnp, natpnp """ self.ses.add_dht_router("router.bittorrent.com", 6881) self.ses.add_dht_router("router.bitcomet.com", 554) self.ses.add_dht_router("router.utorrent.com", 6881) self.ses.start_dht() self.ses.start_lsd() self.ses.start_upnp() self.ses.start_natpmp() def _stop_services(self): """ Función encargada de detener los servicios de libtorrent: dht, lsd, upnp, natpnp """ self.ses.stop_natpmp() self.ses.stop_upnp() self.ses.stop_lsd() self.ses.stop_dht() @property def buffer_status(self): s = [] if not self.files: return 0 for x in self.files: if x.buffering: s.append( len([p for p in x.buffer if x.buffer[p]]) * 100 / len(x.buffer)) if s: return sum(s) / len(s) else: return 100 @property def active_file(self): for f in self.files: if f.cursors: return f @property def status(self): """ Función encargada de devolver el estado del torrent """ if self.th: s = self.th.status() st = dict([(a, getattr(s, a)) for a in dir(s) if not a.startswith('__')]) # Download Rate st['download_rate'] = s.download_rate / 1000 # Progreso del archivo if self.active_files: pieces = s.pieces[self.active_file.first_piece:self. active_file.last_piece] progress = float(sum(pieces)) / len(pieces) else: progress = 0 st['progress_file'] = progress * 100 # Tamaño del archivo if self.active_file: st['file_size'] = self.active_file.size / 1048576.0 else: st['file_size'] = 0 # Estado del buffer st['buffer'] = self.buffer_status # Tiempo restante para cerrar en caso de tener el timeout activo if self.auto_shutdown: if self.connected: if self.timeout: st['timeout'] = int(self.timeout - (time.time() - self.last_connect - 1)) if self.active_file and self.active_file.cursors: st['timeout'] = self.timeout if st['timeout'] < 0: st['timeout'] = "Cerrando" else: st['timeout'] = "---" else: if self.start_time and self.wait_time: st['timeout'] = int(self.wait_time - (time.time() - self.start_time - 1)) if st['timeout'] < 0: st['timeout'] = "Cerrando" else: st['timeout'] = "---" else: st['timeout'] = "Off" # Estado de la descarga STATE_STR = [ 'En cola', 'Comprobando', 'Descargando metadata', 'Descargando', 'Finalizado', 'Seeding', 'Allocating', 'Comprobando fastresume' ] st['str_state'] = STATE_STR[s.state] # Estado DHT if self.ses.dht_state() is not None: st['dht_state'] = "On" st['dht_nodes'] = self.ses.status().dht_nodes else: st['dht_state'] = "Off" st['dht_nodes'] = 0 # Cantidad de Trackers st['trackers'] = len(self.th.trackers()) # Origen de los peers st['dht_peers'] = 0 st['trk_peers'] = 0 st['pex_peers'] = 0 st['lsd_peers'] = 0 for peer in self.th.get_peer_info(): if peer.source & 1: st['dht_peers'] += 1 if peer.source & 2: st['trk_peers'] += 1 if peer.source & 4: st['pex_peers'] += 1 if peer.source & 8: st['lsd_peers'] += 1 return type('', (), st) """ Servicios: - Estas funciones se ejecutan de forma automatica cada x tiempo en otro Thread. - Estas funciones son ejecutadas mientras el torrent esta activo algunas pueden desactivarse segun la configuracion como por ejemplo la escritura en el log """ def _auto_shutdown(self, *args, **kwargs): """ Servicio encargado de autoapagar el servidor """ if self.active_file and self.active_file.cursors: self.last_connect = time.time() self.connected = True if self.is_playing_fnc and self.is_playing_fnc(): self.last_connect = time.time() self.connected = True if self.auto_shutdown: # shudown por haber cerrado el reproductor if self.connected and self.is_playing_fnc and not self.is_playing_fnc( ): if time.time() - self.last_connect - 1 > self.timeout: self.stop() # shutdown por no realizar ninguna conexion if ( not self.active_file or not self.active_file.cursors ) and self.start_time and self.wait_time and not self.connected: if time.time() - self.start_time - 1 > self.wait_time: self.stop() # shutdown tras la ultima conexion if ( not self.active_file or not self.active_file.cursors ) and self.timeout and self.connected and not self.is_playing_fnc: if time.time() - self.last_connect - 1 > self.timeout: self.stop() def announce_torrent(self): """ Servicio encargado de anunciar el torrent """ self.th.force_reannounce() self.th.force_dht_announce() def save_state(self): """ Servicio encargado de guardar el estado """ state = self.ses.save_state() f = open(os.path.join(self.temp_path, self.state_file), 'wb') pickle.dump(state, f) f.close() def _update_ready_pieces(self, alert_type, alert): """ Servicio encargado de informar que hay una pieza disponible """ if alert_type == 'read_piece_alert': for f in self.files: f.add_to_buffer(alert.piece, alert.buffer) def _check_meta(self): """ Servicio encargado de comprobar si los metadatos se han descargado """ if 3 <= self.status.state <= 5 and not self.has_meta: # Guardamos los metadatos self.meta = self.th.get_torrent_info() # Obtenemos la lista de archivos del meta fs = self.meta.files() if isinstance(fs, list): files = fs else: files = [fs.at(i) for i in xrange(fs.num_files())] # Guardamos la lista de archivos self.files = self._find_files(files) # Marcamos el primer archivo como activo self.set_file(self.files[0]) # Damos por iniciada la descarga self.start_time = time.time() # Guardamos el .torrent en el cahce self.cache.file_complete(self.th.get_torrent_info()) print self.th.get_torrent_info() self.has_meta = True def priorize_service(self): """ Servicio encargado de priorizar el principio y final de archivo cuando no hay conexion """ for f in self.files: if f.buffering: if self.th.file_priority(f.index) != 7: self.th.file_priority(f.index, 7) elif f.cursors: if self.th.file_priority(f.index) != 7: self.th.file_priority(f.index, 7) else: if self.th.file_priority(f.index) != 1: self.th.file_priority(f.index, 1) @property def active_files(self): return [f for f in self.files if f.cursors] def print_status(self): """ Servicio encargado de mostrar en el log el estado de la descarga """ s = self.status logger.debug( '%.2f%% de %.1fMB %s | %.1f kB/s | AutoClose: %s | S: %d(%d) P: %d(%d)) | ' 'TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d' % (s.progress_file, s.file_size, s.str_state, s.download_rate, s.timeout, s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers, s.dht_state, s.dht_nodes, s.trackers)) for f in self.files: for l in str(f).splitlines(): logger.debug(l) logger.debug( '_____________________________________________________________________' )