diff --git a/lib/pyrrent2http/engine.py b/lib/pyrrent2http/engine.py index 925d340..df61396 100644 --- a/lib/pyrrent2http/engine.py +++ b/lib/pyrrent2http/engine.py @@ -99,8 +99,8 @@ class Engine: enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False, log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=False, keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False, - listen_port=6881, use_random_port=False, max_idle_timeout=None, no_sparse=False, resume_file=None, - user_agent=None, startup_timeout=5, state_file=None, enable_utp=True, enable_tcp=True, + listen_port=6881, use_random_port=False, max_idle_timeout=None, no_sparse=False, resume_file='', + user_agent=None, startup_timeout=5, state_file='', enable_utp=True, enable_tcp=True, debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50, peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3, dht_routers=None, trackers=None): @@ -239,61 +239,46 @@ class Engine: self.bind_port = port kwargs = { - '--bind': "%s:%s" % (self.bind_host, self.bind_port), - '--uri': self.uri, - '--file-index': start_index, - '--dl-path': download_path, - '--connections-limit': self.connections_limit, - '--dl-rate': self.download_kbps, - '--ul-rate': self.upload_kbps, - '--enable-dht': self.enable_dht, - '--enable-lsd': self.enable_lsd, - '--enable-natpmp': self.enable_natpmp, - '--enable-upnp': self.enable_upnp, - '--enable-scrape': self.enable_scrape, - '--encryption': self.encryption, - '--show-stats': self.log_stats, - '--files-progress': self.log_files_progress, - '--overall-progress': self.log_overall_progress, - '--pieces-progress': self.log_pieces_progress, - '--listen-port': self.listen_port, - '--random-port': self.use_random_port, - '--keep-complete': self.keep_complete, - '--keep-incomplete': self.keep_incomplete, - '--keep-files': self.keep_files, - '--max-idle': self.max_idle_timeout, - '--no-sparse': self.no_sparse, - '--resume-file': self.resume_file, - '--user-agent': self.user_agent, - '--state-file': self.state_file, - '--enable-utp': self.enable_utp, - '--enable-tcp': self.enable_tcp, - '--debug-alerts': self.debug_alerts, - '--torrent-connect-boost': self.torrent_connect_boost, - '--connection-speed': self.connection_speed, - '--peer-connect-timeout': self.peer_connect_timeout, - '--request-timeout': self.request_timeout, - '--min-reconnect-time': self.min_reconnect_time, - '--max-failcount': self.max_failcount, - '--dht-routers': ",".join(self.dht_routers), - '--trackers': ",".join(self.trackers), - } + 'torrentConnectBoost': self.torrent_connect_boost, + 'trackers': ",".join(self.trackers), + 'resumeFile': self.resume_file, + 'minReconnectTime': self.min_reconnect_time, + 'enableUPNP': self.enable_upnp, + 'showAllStats': self.log_stats, + 'debugAlerts': self.debug_alerts, + 'keepComplete': self.keep_complete, + 'dhtRouters': ",".join(self.dht_routers), + 'userAgent': self.user_agent, + 'enableLSD': self.enable_lsd, + 'uri': self.uri, + 'randomPort': self.use_random_port, + 'noSparseFile': self.no_sparse, + 'maxUploadRate': self.upload_kbps, + 'downloadPath': download_path, + 'showOverallProgress': self.log_overall_progress, + 'enableDHT': self.enable_dht, + 'showFilesProgress': self.log_files_progress, + 'requestTimeout': self.request_timeout, + 'bindAddress': "%s:%s" % (self.bind_host, self.bind_port), + 'maxDownloadRate': self.download_kbps, + 'connectionSpeed': self.connection_speed, + 'keepIncomplete': self.keep_incomplete, + 'enableTCP': self.enable_tcp, + 'listenPort': self.listen_port, + 'keepFiles': self.keep_files, + 'stateFile': self.state_file, + 'peerConnectTimeout': self.peer_connect_timeout, + 'maxFailCount': self.max_failcount, + 'showPiecesProgress': self.log_pieces_progress, + 'idleTimeout': self.max_idle_timeout, + 'fileIndex': start_index, + 'connectionsLimit': self.connections_limit, + 'enableScrape': self.enable_scrape, + 'enableUTP': self.enable_utp, + 'encryption': self.encryption, + 'enableNATPMP': self.enable_natpmp - args = [] - for k, v in kwargs.iteritems(): - if v is not None: - if isinstance(v, bool): - if v: - args.append(k) - else: - args.append("%s=false" % k) - else: - args.append(k) - if isinstance(v, str) or isinstance(v, unicode): - v = ensure_fs_encoding(v) - else: - v = str(v) - args.append(v) + } self._log("Invoking pyrrent2http") class Logging(object): @@ -306,22 +291,19 @@ class Engine: if LOGGING: self._log('ERROR: %s' % (message,)) pyrrent2http.logging = Logging(self._log) -# startupinfo = None -# if self.platform.system == "windows": -# startupinfo = subprocess.STARTUPINFO() -# startupinfo.dwFlags |= 1 -# startupinfo.wShowWindow = 0 -# -# self.logpipe = logpipe.LogPipe(self._log) -# try: -# self.process = subprocess.Popen(args, stderr=self.logpipe, stdout=self.logpipe, startupinfo=startupinfo) -# except OSError, e: -# raise Error("Can't start pyrrent2http: %r" % e, Error.POPEN_ERROR) - self.pyrrent2http = pyrrent2http.Pyrrent2http() - self.pyrrent2http.parseFlags(args) - self.pyrrent2http.startSession() + self._log('init config') + try: + self.pyrrent2http.initConfig(**kwargs) + except Exception as e: + self._log('%s' % (e.args,)) + self._log('Starting session') + try: + self.pyrrent2http.startSession() + except Exception as e: + self._log('%s' % (e.args,)) + self._log('starting services') self.pyrrent2http.startServices() self.pyrrent2http.addTorrent() self.pyrrent2http.startHTTP() @@ -440,34 +422,6 @@ class Engine: def is_alive(self): return self.pyrrent2http_loop.is_alive() - - __to_del = '''@staticmethod - def _decode(response): - try: - return json.loads(response) - except (KeyError, ValueError), e: - raise Error("Can't decode response from pyrrent2http: %r" % e, Error.REQUEST_ERROR) -''' - __to_del = '''def _request(self, cmd, timeout=None): - if not self.started: - raise Error("pyrrent2http is not started", Error.REQUEST_ERROR) - try: - url = "http://%s:%s/%s" % (self.bind_host, self.bind_port, cmd) - kwargs = {} - if timeout is not None: - kwargs['timeout'] = timeout - return urllib2.urlopen(url, **kwargs).read() - except (urllib2.URLError, httplib.HTTPException) as e: - if isinstance(e, urllib2.URLError) and isinstance(e.reason, socket.timeout): - raise Error("Timeout occurred while sending command '%s' to pyrrent2http" % cmd, Error.TIMEOUT) - elif not self.is_alive() and self.started: - raise Error("pyrrent2http has crashed.", Error.CRASHED) - else: - raise Error("Can't send command '%s' to pyrrent2http: %r" % (cmd, e), Error.REQUEST_ERROR) - except socket.error as e: - reason = e[1] if isinstance(e, tuple) else e - raise Error("Can't read from pyrrent2http: %s" % reason, Error.REQUEST_ERROR) -''' def wait_on_close(self, wait_timeout=10): """ By default, close() method sends shutdown command to pyrrent2http, stops logging and returns immediately, not @@ -483,16 +437,12 @@ class Engine: Shuts down pyrrent2http and stops logging. If wait_on_close() was called earlier, it will wait until pyrrent2http successfully exits. """ -# if self.logpipe and self.wait_on_close_timeout is None: -# self.logpipe.close() if self.is_alive(): self._log("Shutting down pyrrent2http...") -# self._request('shutdown') self.pyrrent2http.shutdown() finished = False if self.wait_on_close_timeout is not None: start = time.time() -# os.close(self.logpipe.write_fd) while (time.time() - start) < self.wait_on_close_timeout: time.sleep(0.5) if not self.is_alive(): @@ -500,11 +450,9 @@ class Engine: break if not finished: self._log("PANIC: Timeout occurred while shutting down pyrrent2http thread") - #self.process.kill() else: self._log("pyrrent2http successfully shut down.") self.wait_on_close_timeout = None -# self.process.wait() self.started = False self.logpipe = None self.process = None diff --git a/lib/pyrrent2http/pyrrent2http.py b/lib/pyrrent2http/pyrrent2http.py index e4d2d1e..9eab889 100644 --- a/lib/pyrrent2http/pyrrent2http.py +++ b/lib/pyrrent2http/pyrrent2http.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import argparse import sys, os import json import chardet @@ -28,7 +27,7 @@ import BaseHTTPServer import SocketServer import threading import io -from util import localize_path +from util import localize_path, Struct @@ -423,20 +422,6 @@ class TorrentFS(object): ############################################################# -class AttributeDict(dict): - def __getattr__(self, attr): - return self[attr] - def __setattr__(self, attr, value): - self[attr] = value - -class BoolArg(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - #print(repr(values)) - if values is None: v = True - elif values.lower() == 'true': v = True - elif values.lower() == 'false': v = False - setattr(namespace, self.dest, v) - class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): def handle_error(self, *args, **kwargs): '''Обходим злосчастный "Broken Pipe" и прочие трейсы''' @@ -591,68 +576,67 @@ class Pyrrent2http(object): self.forceShutdown = False self.session = None self.magnet = False - def parseFlags(self, params = None): - parser = argparse.ArgumentParser(add_help=True, version=VERSION) - parser.add_argument('--uri', type=str, default='', help='Magnet URI or .torrent file URL', dest='uri') - parser.add_argument('--bind', type=str, default='localhost:5001', help='Bind address of torrent2http', dest='bindAddress') - parser.add_argument('--dl-path', type=str, default='.', help='Download path', dest='downloadPath') - parser.add_argument('--max-idle', type=int, default=-1, help='Automatically shutdown if no connection are active after a timeout', dest='idleTimeout') - parser.add_argument('--file-index', type=int, default=-1, help='Start downloading file with specified index immediately (or start in paused state otherwise)', dest='fileIndex') - parser.add_argument('--keep-complete', nargs='?', action=BoolArg, default=False, help='Keep complete files after exiting', dest='keepComplete', choices=('true', 'false')) - parser.add_argument('--keep-incomplete', nargs='?', action=BoolArg, default=False, help='Keep incomplete files after exiting', dest='keepIncomplete', choices=('true', 'false')) - parser.add_argument('--keep-files', nargs='?', action=BoolArg, default=False, help='Keep all files after exiting (incl. -keep-complete and -keep-incomplete)', dest='keepFiles', choices=('true', 'false')) - parser.add_argument('--show-stats', nargs='?', action=BoolArg, default=False, help='Show all stats (incl. -overall-progress -files-progress -pieces-progress)', dest='showAllStats', choices=('true', 'false')) - parser.add_argument('--overall-progress', nargs='?', action=BoolArg, default=False, help='Show overall progress', dest='showOverallProgress', choices=('true', 'false')) - parser.add_argument('--files-progress', nargs='?', action=BoolArg, default=False, help='Show files progress', dest='showFilesProgress', choices=('true', 'false')) - parser.add_argument('--pieces-progress', nargs='?', action=BoolArg, default=False, help='Show pieces progress', dest='showPiecesProgress', choices=('true', 'false')) - parser.add_argument('--debug-alerts', nargs='?', action=BoolArg, default=False, help='Show debug alert notifications', dest='debugAlerts', choices=('true', 'false')) - parser.add_argument('--exit-on-finish', nargs='?', action=BoolArg, default=False, help='Exit when download finished', dest='exitOnFinish', choices=('true', 'false')) - parser.add_argument('--resume-file', type=str, default='', help='Use fast resume file', dest='resumeFile') - parser.add_argument('--state-file', type=str, default='', help='Use file for saving/restoring session state', dest='stateFile') - parser.add_argument('--user-agent', type=str, default=USER_AGENT, help='Set an user agent', dest='userAgent') - parser.add_argument('--dht-routers', type=str, default='', help='Additional DHT routers (comma-separated host:port pairs)', dest='dhtRouters') - parser.add_argument('--trackers', type=str, default='', help='Additional trackers (comma-separated URLs)', dest='trackers') - parser.add_argument('--listen-port', type=int, default=6881, help='Use specified port for incoming connections', dest='listenPort') - parser.add_argument('--torrent-connect-boost', type=int, default=50, help='The number of peers to try to connect to immediately when the first tracker response is received for a torrent', dest='torrentConnectBoost') - parser.add_argument('--connection-speed', type=int, default=50, help='The number of peer connection attempts that are made per second', dest='connectionSpeed') - parser.add_argument('--peer-connect-timeout', type=int, default=15, help='The number of seconds to wait after a connection attempt is initiated to a peer', dest='peerConnectTimeout') - parser.add_argument('--request-timeout', type=int, default=20, help='The number of seconds until the current front piece request will time out', dest='requestTimeout') - parser.add_argument('--dl-rate', type=int, default=-1, help='Max download rate (kB/s)', dest='maxDownloadRate') - parser.add_argument('--ul-rate', type=int, default=-1, help='Max upload rate (kB/s)', dest='maxUploadRate') - parser.add_argument('--connections-limit', type=int, default=200, help='Set a global limit on the number of connections opened', dest='connectionsLimit') - parser.add_argument('--encryption', type=int, default=1, help='Encryption: 0=forced 1=enabled (default) 2=disabled', dest='encryption') - parser.add_argument('--min-reconnect-time', type=int, default=60, help='The time to wait between peer connection attempts. If the peer fails, the time is multiplied by fail counter', dest='minReconnectTime') - parser.add_argument('--max-failcount', type=int, default=3, help='The maximum times we try to connect to a peer before stop connecting again', dest='maxFailCount') - parser.add_argument('--no-sparse', nargs='?', action=BoolArg, default=False, help='Do not use sparse file allocation', dest='noSparseFile', choices=('true', 'false')) - parser.add_argument('--random-port', nargs='?', action=BoolArg, default=False, help='Use random listen port (49152-65535)', dest='randomPort', choices=('true', 'false')) - parser.add_argument('--enable-scrape', nargs='?', action=BoolArg, default=False, help='Enable sending scrape request to tracker (updates total peers/seeds count)', dest='enableScrape', choices=('true', 'false')) - parser.add_argument('--enable-dht', nargs='?', action=BoolArg, default=True, help='Enable DHT (Distributed Hash Table)', dest='enableDHT', choices=('true', 'false')) - parser.add_argument('--enable-lsd', nargs='?', action=BoolArg, default=True, help='Enable LSD (Local Service Discovery)', dest='enableLSD', choices=('true', 'false')) - parser.add_argument('--enable-upnp', nargs='?', action=BoolArg, default=True, help='Enable UPnP (UPnP port-mapping)', dest='enableUPNP', choices=('true', 'false')) - parser.add_argument('--enable-natpmp', nargs='?', action=BoolArg, default=True, help='Enable NATPMP (NAT port-mapping)', dest='enableNATPMP', choices=('true', 'false')) - parser.add_argument('--enable-utp', nargs='?', action=BoolArg, default=True, help='Enable uTP protocol', dest='enableUTP', choices=('true', 'false')) - parser.add_argument('--enable-tcp', nargs='?', action=BoolArg, default=True, help='Enable TCP protocol', dest='enableTCP', choices=('true', 'false')) - if params is None: - config_ = parser.parse_args() - else: - config_ = parser.parse_args(args = params) - self.config = AttributeDict() - for k in config_.__dict__.keys(): - self.config[k] = config_.__dict__[k] + def initConfig(self, uri = '', bindAddress = 'localhost:5001', downloadPath = '.', + idleTimeout = -1, fileIndex = -1, keepComplete = False, + keepIncomplete = False, keepFiles = False, showAllStats = False, + showOverallProgress = False, showFilesProgress = False, + showPiecesProgress = False, debugAlerts = False, + exitOnFinish = False, resumeFile = '', stateFile = '', + userAgent = USER_AGENT, dhtRouters = '', trackers = '', + listenPort = 6881, torrentConnectBoost = 50, connectionSpeed = 50, + peerConnectTimeout = 15, requestTimeout = 20, maxDownloadRate = -1, + maxUploadRate = -1, connectionsLimit = 200, encryption = 1, + minReconnectTime = 60, maxFailCount = 3, noSparseFile = False, + randomPort = False, enableScrape = False, enableDHT = True, + enableLSD = True, enableUPNP = True, enableNATPMP = True, enableUTP = True, enableTCP = True): + self.config = Struct() + self.config.uri = uri + self.config.bindAddress = bindAddress + self.config.downloadPath = downloadPath + self.config.idleTimeout = idleTimeout + self.config.fileIndex = fileIndex + self.config.keepComplete = keepComplete + self.config.keepIncomplete = keepIncomplete + self.config.keepFiles = keepFiles + self.config.showAllStats = showAllStats + self.config.showOverallProgress = showOverallProgress + self.config.showFilesProgress = showFilesProgress + self.config.showPiecesProgress = showPiecesProgress + self.config.debugAlerts = debugAlerts + self.config.exitOnFinish = exitOnFinish + self.config.resumeFile = resumeFile + self.config.stateFile = stateFile + self.config.userAgent = userAgent + self.config.dhtRouters = dhtRouters + self.config.trackers = trackers + self.config.listenPort = listenPort + self.config.torrentConnectBoost = torrentConnectBoost + self.config.connectionSpeed = connectionSpeed + self.config.peerConnectTimeout = peerConnectTimeout + self.config.requestTimeout = requestTimeout + self.config.maxDownloadRate = maxDownloadRate + self.config.maxUploadRate = maxUploadRate + self.config.connectionsLimit = connectionsLimit + self.config.encryption = encryption + self.config.minReconnectTime = minReconnectTime + self.config.maxFailCount = maxFailCount + self.config.noSparseFile = noSparseFile + self.config.randomPort = randomPort + self.config.enableScrape = enableScrape + self.config.enableDHT = enableDHT + self.config.enableLSD = enableLSD + self.config.enableUPNP = enableUPNP + self.config.enableNATPMP = enableNATPMP + self.config.enableUTP = enableUTP + self.config.enableTCP = enableTCP if self.config.uri == '': - parser.print_usage() - if STANDALONE: - sys.exit(1) - else: - raise "Invalid argument" + raise Exception("uri is empty string") if self.config.uri.startswith('magnet:'): self.magnet = True + if self.config.resumeFile is None: self.config.resumeFile = '' if self.config.resumeFile != '' and not self.config.keepFiles: - logging.error('Usage of option --resume-file is allowed only along with --keep-files') - if STANDALONE: - sys.exit(1) - else: - raise + raise Exception('Не должно быть файла восстановления, если мы не храним файлы') + def buildTorrentParams(self, uri): if uri[1] == ':' and sys.platform.startswith('win'): uri = 'file:///' + uri @@ -796,7 +780,7 @@ class Pyrrent2http(object): self.session.set_settings(settings) if self.config.stateFile != '': - logging.info('Loading session state from %s', self.config.stateFile) + logging.info('Loading session state from %s' % (self.config.stateFile,)) try: with open(self.config.stateFile, 'rb') as f: bytes__ = f.read() @@ -900,7 +884,7 @@ class Pyrrent2http(object): 'offset': file_.offset, 'download': file_.Downloaded(), 'progress': file_.Progress(), - 'save_path': file_.SavePath(), + 'save_path': file_.save_path, 'url': Url } retFiles['files'].append(fi) @@ -1019,6 +1003,7 @@ class Pyrrent2http(object): data = lt.bencode(entry) logging.info('Saving session state to: %s' % (self.config.stateFile,)) try: + logging.info('Saving session state to: %s' % (self.config.stateFile,)) with open(self.config.stateFile, 'wb') as f: f.write(data) except IOError as e: @@ -1044,8 +1029,8 @@ class Pyrrent2http(object): if self.TorrentFS.HasTorrentInfo(): for file in self.TorrentFS.Files(): if (not self.config.keepComplete or not file.IsComplete()) and (not self.config.keepIncomplete or file.IsComplete()): - if os.path.exists(file.SavePath()): - files.append(file.SavePath()) + if os.path.exists(file.save_path): + files.append(file.save_path) return files def removeTorrent(self): files = [] diff --git a/lib/pyrrent2http/util.py b/lib/pyrrent2http/util.py index d6df7bd..ded31cd 100644 --- a/lib/pyrrent2http/util.py +++ b/lib/pyrrent2http/util.py @@ -3,6 +3,13 @@ import socket import chardet +class Struct(dict): + def __getattr__(self, attr): + return self[attr] + def __setattr__(self, attr, value): + self[attr] = value + + def localize_path(path): path = path.decode(chardet.detect(path)['encoding']) if not sys.platform.startswith('win'):