script.module.pyrrent2http/lib/gorrent2http/gorrent2http.py

1012 lines
38 KiB
Python
Raw Normal View History

2016-03-04 16:15:00 +03:00
# -*- coding: utf-8 -*-
2022-03-14 09:10:42 +03:00
import http.server
import io
import os
2022-03-14 09:10:42 +03:00
import socketserver
2016-03-04 16:15:00 +03:00
import threading
import urllib.error
import urllib.parse
import urllib.request
from random import SystemRandom
from . import log as logging
import chardet
import sys
import time
import xbmc
import xbmcvfs
from . import util
2022-03-14 09:10:42 +03:00
from .util import localize_path, Struct, detect_media_type, uri2path, encode_msg
2016-04-02 13:15:55 +03:00
platform = util.get_platform()
dirname = os.path.join(xbmcvfs.translatePath('special://temp'), 'xbmcup', 'script.module.gorrent2http')
dest_path = os.path.join(dirname, platform['system'])
sys.path.insert(0, dest_path)
try:
from gorrent import gorrent as gt
logging.info(f'Imported gorrent v{gt.Version()}')
except Exception:
import traceback
logging.error(f'Error importing gorrent. Exception: {traceback.format_exc()}')
raise
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
from ctypes import *
2022-03-14 09:10:42 +03:00
2016-04-02 13:15:55 +03:00
libc = CDLL('/system/lib/libc.so')
libc.lseek64.restype = c_ulonglong
libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint]
libc.read.restype = c_long
libc.read.argtypes = [c_uint, c_void_p, c_long]
O_RDONLY = 0
O_LARGEFILE = 0x8000
2016-03-04 16:15:00 +03:00
######################################################################################
if not hasattr(os, 'getppid'):
import ctypes
2022-03-14 09:10:42 +03:00
TH32CS_SNAPPROCESS = 0x02
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable
2022-03-14 09:10:42 +03:00
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable
2016-03-04 16:15:00 +03:00
MAX_PATH = 260
_kernel32dll = ctypes.windll.Kernel32
CloseHandle = _kernel32dll.CloseHandle
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
class PROCESSENTRY32(ctypes.Structure):
_fields_ = [
("dwSize", ctypes.c_ulong),
("cntUsage", ctypes.c_ulong),
("th32ProcessID", ctypes.c_ulong),
("th32DefaultHeapID", ctypes.c_int),
("th32ModuleID", ctypes.c_ulong),
("cntThreads", ctypes.c_ulong),
("th32ParentProcessID", ctypes.c_ulong),
("pcPriClassBase", ctypes.c_long),
("dwFlags", ctypes.c_ulong),
("szExeFile", ctypes.c_wchar * MAX_PATH)
]
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
Process32First = _kernel32dll.Process32FirstW
Process32Next = _kernel32dll.Process32NextW
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def getppid():
"""
2016-03-04 16:15:00 +03:00
:return: The pid of the parent of this process.
"""
2016-03-04 16:15:00 +03:00
pe = PROCESSENTRY32()
pe.dwSize = ctypes.sizeof(PROCESSENTRY32)
mypid = GetCurrentProcessId()
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
result = 0
try:
have_record = Process32First(snapshot, ctypes.byref(pe))
while have_record:
if mypid == pe.th32ProcessID:
result = pe.th32ParentProcessID
break
have_record = Process32Next(snapshot, ctypes.byref(pe))
finally:
CloseHandle(snapshot)
return result
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
os.getppid = getppid
#################################################################################
AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True
VERSION = "0.6.0"
2022-03-14 09:10:42 +03:00
# USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version
USER_AGENT = 'libtorrent/1.0.9.0'
2016-03-04 16:15:00 +03:00
2022-03-14 09:10:42 +03:00
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'}
2016-03-04 16:15:00 +03:00
######################################################################################
class Ticker(object):
def __init__(self, interval):
self.tick = False
2022-03-14 09:10:42 +03:00
self._timer = None
self.interval = interval
2016-03-04 16:15:00 +03:00
self.is_running = False
self.start()
2022-03-14 09:10:42 +03:00
@property
2016-03-04 16:15:00 +03:00
def true(self):
if self.tick:
self.tick = False
return True
else:
return False
def _run(self):
self.is_running = False
self.start()
self.tick = True
def start(self):
if not self.is_running:
self._timer = threading.Timer(self.interval, self._run)
self._timer.start()
self.is_running = True
def stop(self):
self._timer.cancel()
self.is_running = False
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
#######################################################################################
class TorrentFile(object):
2022-03-14 09:10:42 +03:00
tfs = None
closed = True
save_path = str()
fileEntry = None
index = 0
filePtr = None
downloaded = 0
progress = 0.0
pdl_thread = None
2016-03-04 16:15:00 +03:00
def __init__(self, tfs, fileEntry, savePath, index):
self.tfs = tfs
self.fileEntry = fileEntry
2016-03-12 19:45:18 +03:00
self.name = self.fileEntry.path
self.unicode_name = isinstance(self.name, str) and self.name or self.name.decode(
chardet.detect(self.name)['encoding'])
self.media_type = detect_media_type(self.unicode_name)
self.save_path = savePath
2016-03-04 16:15:00 +03:00
self.index = index
2016-03-12 01:02:35 +03:00
self.piece_length = int(self.tfs.info.piece_length())
self.size = self.fileEntry.size
self.offset = self.fileEntry.offset
2016-03-04 16:15:00 +03:00
self.startPiece, self.endPiece = self.Pieces()
self.pieces_deadlined = [False] * (self.endPiece - self.startPiece)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Downloaded(self):
return self.downloaded
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Progress(self):
return self.progress
2022-03-14 09:10:42 +03:00
2016-03-12 19:45:18 +03:00
def __fileptr_(self):
2016-03-04 16:15:00 +03:00
if self.closed:
return None
if self.filePtr is None:
while not os.path.exists(self.save_path):
xbmc.log('INFO: Waiting for file: %s' % (self.save_path,))
2016-03-14 12:15:47 +03:00
self.tfs.handle.flush_cache()
time.sleep(0.5)
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
self.filePtr = libc.open(self.save_path, O_RDONLY | O_LARGEFILE, 755)
else:
self.filePtr = io.open(self.save_path, 'rb')
2016-03-04 16:15:00 +03:00
return self.filePtr
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def log(self, message):
fnum = self.tfs.openedFiles.index(self)
xbmc.log("INFO: [Thread No.%d] %s\n" % (fnum, message))
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Pieces(self):
startPiece, _ = self.pieceFromOffset(1)
2016-03-12 01:02:35 +03:00
endPiece, _ = self.pieceFromOffset(self.size - 1)
2016-03-04 16:15:00 +03:00
return startPiece, endPiece
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def SetPriority(self, priority):
self.tfs.setPriority(self.index, priority)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def readOffset(self):
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
return libc.lseek64(self.filePtr, 0, os.SEEK_CUR)
else:
return self.filePtr.seek(0, os.SEEK_CUR)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def havePiece(self, piece):
return self.tfs.handle.have_piece(piece)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def pieceFromOffset(self, offset):
2016-03-12 01:02:35 +03:00
piece = int((self.offset + offset) / self.piece_length)
pieceOffset = int((self.offset + offset) % self.piece_length)
2016-03-04 16:15:00 +03:00
return piece, pieceOffset
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def waitForPiece(self, piece):
def set_deadlines(p):
next_piece = p + 1
2022-03-14 09:10:42 +03:00
BUF_SIZE = 2 # Лучшее враг хорошего
2016-03-04 16:15:00 +03:00
for i in range(BUF_SIZE):
2022-03-14 09:10:42 +03:00
if (next_piece + i < self.endPiece and
not self.pieces_deadlined[(next_piece + i) - self.startPiece] and not self.havePiece(
next_piece + i)):
2016-03-04 16:15:00 +03:00
self.tfs.handle.set_piece_deadline(next_piece + i, 70 + (20 * i))
self.pieces_deadlined[(next_piece + i) - self.startPiece] = True
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
if not self.havePiece(piece):
self.log('Waiting for piece %d' % (piece,))
self.tfs.handle.set_piece_deadline(piece, 50)
while not self.havePiece(piece):
if self.tfs.handle.piece_priority(piece) == 0 or self.closed:
return False
time.sleep(0.1)
if not isinstance(self.pdl_thread, threading.Thread) or not self.pdl_thread.is_alive():
2022-03-14 09:10:42 +03:00
self.pdl_thread = threading.Thread(target=set_deadlines, args=(piece,))
2016-03-04 16:15:00 +03:00
self.pdl_thread.start()
return True
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Close(self):
if self.closed: return
self.log('Closing %s...' % (self.name,))
2016-03-04 16:15:00 +03:00
self.tfs.removeOpenedFile(self)
self.closed = True
if self.filePtr is not None:
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
libc.close(self.filePtr)
else:
self.filePtr.close()
2016-03-04 16:15:00 +03:00
self.filePtr = None
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def ShowPieces(self):
pieces = self.tfs.handle.status().pieces
str_ = ''
for i in range(self.startPiece, self.endPiece + 1):
if pieces[i] == False:
str_ += "-"
else:
str_ += "#"
self.log(str_)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Read(self, buf):
2016-03-12 19:45:18 +03:00
filePtr = self.__fileptr_()
2016-03-04 16:15:00 +03:00
if filePtr is None:
raise IOError
toRead = len(buf)
if toRead > self.piece_length:
toRead = self.piece_length
readOffset = self.readOffset()
startPiece, _ = self.pieceFromOffset(readOffset)
endPiece, _ = self.pieceFromOffset(readOffset + toRead)
2022-03-14 09:10:42 +03:00
for i in range(startPiece, endPiece + 1):
2016-03-04 16:15:00 +03:00
if not self.waitForPiece(i):
raise IOError
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
read = libc.read(self.filePtr, addressof(buf), toRead)
else:
read = filePtr.readinto(buf)
2016-03-04 16:15:00 +03:00
return read
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Seek(self, offset, whence):
2016-03-12 19:45:18 +03:00
filePtr = self.__fileptr_()
2016-03-04 16:15:00 +03:00
if filePtr is None: return
if whence == os.SEEK_END:
offset = self.size - offset
whence = os.SEEK_SET
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
newOffset = libc.lseek64(self.filePtr, offset, whence)
else:
newOffset = filePtr.seek(offset, whence)
2016-03-04 16:15:00 +03:00
self.log('Seeking to %d/%d' % (newOffset, self.size))
return newOffset
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def IsComplete(self):
return self.downloaded == self.size
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
#######################################################################################
class TorrentFS(object):
2022-03-14 09:10:42 +03:00
handle = None
info = None
priorities = list()
openedFiles = list()
lastOpenedFile = None
shuttingDown = False
fileCounter = int()
progresses = list()
save_path = None
2016-03-04 16:15:00 +03:00
2016-03-19 22:00:16 +03:00
def __init__(self, root, handle):
2016-03-04 16:15:00 +03:00
self.root = root
self.handle = handle
self.waitForMetadata()
self.save_path = localize_path(self.root.torrentParams['save_path'])
self.priorities = list(self.handle.file_priorities())
2016-03-19 22:14:32 +03:00
self.files = {}
2016-03-17 19:43:10 +03:00
num_files = self.info.num_files()
for i in range(num_files):
2016-03-19 22:00:16 +03:00
self.setPriority(i, 0)
2022-03-14 09:10:42 +03:00
2016-03-19 22:00:16 +03:00
def file(self, index):
2022-03-14 09:10:42 +03:00
for name in list(self.files.keys()):
if self.files[name].index == index:
return self.files[name]
2016-03-19 22:00:16 +03:00
file_ = self.__file_at_(index)
2016-03-19 22:14:32 +03:00
self.files[file_.name] = file_
2016-03-19 22:00:16 +03:00
self.setPriority(index, 1)
return file_
2016-03-17 21:45:50 +03:00
2016-03-04 16:15:00 +03:00
def Shutdown(self):
self.shuttingDown = True
if len(self.openedFiles) > 0:
logging.info('Closing %d opened file(s)' % (len(self.openedFiles),))
for f in self.openedFiles:
f.Close()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def addOpenedFile(self, file_):
2022-03-14 09:10:42 +03:00
self.openedFiles.append(file_)
2016-03-04 16:15:00 +03:00
def setPriority(self, index, priority):
if self.priorities[index] != priority:
2016-03-04 22:00:13 +03:00
logging.info('Setting %s priority to %d' % (self.info.file_at(index).path, priority))
2016-03-04 16:15:00 +03:00
self.priorities[index] = priority
self.handle.file_priority(index, priority)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def findOpenedFile(self, file):
for i, f in enumerate(self.openedFiles):
if f == file:
return i
return -1
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def removeOpenedFile(self, file):
pos = self.findOpenedFile(file)
if pos >= 0:
del self.openedFiles[pos]
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def waitForMetadata(self):
if not self.handle.status().has_metadata:
time.sleep(0.1)
try:
self.info = self.handle.torrent_file()
except:
self.info = self.handle.get_torrent_info()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def HasTorrentInfo(self):
return self.info is not None
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def LoadFileProgress(self):
self.progresses = self.handle.file_progress()
2022-03-14 09:10:42 +03:00
for k in list(self.files.keys()):
self.files[k].downloaded = self.getFileDownloadedBytes(self.files[k].index)
2022-03-14 09:10:42 +03:00
if self.files[k].size > 0: self.files[k].progress = float(self.files[k].downloaded) / float(
self.files[k].size)
2016-03-04 16:15:00 +03:00
def getFileDownloadedBytes(self, i):
try:
bytes_ = self.progresses[i]
2016-03-04 16:15:00 +03:00
except IndexError:
bytes_ = 0
return bytes_
2022-03-14 09:10:42 +03:00
2016-03-12 19:45:18 +03:00
def __files_(self):
info = self.info
2016-03-12 20:07:49 +03:00
files_ = []
2016-03-04 16:15:00 +03:00
for i in range(info.num_files()):
2016-03-12 19:45:18 +03:00
file_ = self.__file_at_(i)
2016-03-04 16:15:00 +03:00
file_.downloaded = self.getFileDownloadedBytes(i)
2016-03-12 01:02:35 +03:00
if file_.size > 0:
2022-03-14 09:10:42 +03:00
file_.progress = float(file_.downloaded) / float(file_.size)
2016-03-12 20:07:49 +03:00
files_.append(file_)
return files_
2022-03-14 09:10:42 +03:00
2016-03-12 19:45:18 +03:00
def __file_at_(self, index):
info = self.info
2016-03-04 16:15:00 +03:00
fileEntry = info.file_at(index)
fe_path = fileEntry.path
path = os.path.abspath(os.path.join(self.save_path, localize_path(fe_path)))
2016-03-04 16:15:00 +03:00
return TorrentFile(
2022-03-14 09:10:42 +03:00
self,
fileEntry,
path,
index
)
2016-03-04 16:15:00 +03:00
def FileByName(self, name):
for i, f in enumerate(self.info.files()):
if f.path == name:
return self.__file_at_(i)
2016-03-04 16:15:00 +03:00
raise IOError
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def Open(self, name):
if self.shuttingDown or not self.HasTorrentInfo():
raise IOError
return self.OpenFile(name)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def checkPriorities(self):
for index, priority in enumerate(self.priorities):
if priority == 0:
continue
found = False
for f in self.openedFiles:
if f.index == index:
found = True
break
if not found:
self.setPriority(index, 0)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def OpenFile(self, name):
try:
tf = self.FileByName(name)
except IOError:
return
tf.closed = False
self.fileCounter += 1
tf.num = self.fileCounter
self.addOpenedFile(tf)
tf.log('Opened %s...' % (tf.name,))
2016-03-04 16:15:00 +03:00
tf.SetPriority(1)
2016-03-17 22:07:13 +03:00
self.handle.set_piece_deadline(tf.startPiece, 50)
2016-03-04 16:15:00 +03:00
self.lastOpenedFile = tf
self.files[tf.name] = tf
2016-03-04 16:15:00 +03:00
self.checkPriorities()
return tf
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
#############################################################
2022-03-14 09:10:42 +03:00
class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
2016-03-04 16:15:00 +03:00
def handle_error(self, *args, **kwargs):
'''Обходим злосчастный "Broken Pipe" и прочие трейсы'''
if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT:
2022-03-14 09:10:42 +03:00
http.server.HTTPServer.handle_error(self, *args, **kwargs)
2016-03-04 16:15:00 +03:00
def HttpHandlerFactory():
2022-03-14 09:10:42 +03:00
class HttpHandler(http.server.BaseHTTPRequestHandler):
2016-03-04 16:15:00 +03:00
def do_GET(self):
2022-03-14 09:10:42 +03:00
# print ('---Headers---\n%s\n' % (self.headers,))
# print ('---Request---\n%s\n' % (self.path,))
2016-03-12 19:45:18 +03:00
if self.path.startswith('/files/'):
2016-03-04 16:15:00 +03:00
self.filesHandler()
else:
self.send_error(404, 'Not found')
self.end_headers()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def filesHandler(self):
f, start_range, end_range = self.send_head()
if not f.closed:
f.Seek(start_range, 0)
chunk = f.piece_length
total = 0
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
buf = create_string_buffer(chunk)
else:
buf = bytearray(chunk)
while chunk > 0 and not self.server.root_obj.forceShutdown:
2016-03-04 16:15:00 +03:00
if start_range + chunk > end_range:
chunk = end_range - start_range
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
buf = create_string_buffer(chunk)
else:
buf = bytearray(chunk)
2016-03-04 16:15:00 +03:00
try:
2016-03-12 01:02:35 +03:00
if f.Read(buf) < 1: break
while self.server.root_obj.pause and not self.server.root_obj.forceShutdown:
2016-03-27 15:27:14 +03:00
time.sleep(0.1)
continue
2016-04-02 13:15:55 +03:00
if os.getenv('ANDROID_ROOT'):
self.wfile.write(buf.raw)
else:
self.wfile.write(buf)
2016-03-04 16:15:00 +03:00
except:
break
total += chunk
start_range += chunk
f.Close()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def send_head(self):
2022-03-14 09:10:42 +03:00
fname = urllib.parse.unquote(self.path.lstrip('/files/'))
2016-03-04 16:15:00 +03:00
try:
2022-03-14 09:10:42 +03:00
f = self.server.root_obj.TorrentFS.Open(fname)
2016-03-04 16:15:00 +03:00
except IOError:
self.send_error(404, "File not found")
return (None, 0, 0)
_, ext = os.path.splitext(fname)
2022-03-14 09:10:42 +03:00
ctype = (ext != '' and ext in list(VIDEO_EXTS.keys())) and VIDEO_EXTS[ext] or 'application/octet-stream'
2016-03-04 16:15:00 +03:00
if "Range" in self.headers:
self.send_response(206, 'Partial Content')
else:
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header('transferMode.dlna.org', 'Streaming')
size = f.size
start_range = 0
end_range = size
self.send_header("Accept-Ranges", "bytes")
if "Range" in self.headers:
s, e = self.headers['range'][6:].split('-', 1)
sl = len(s)
el = len(e)
if sl > 0:
start_range = int(s)
if el > 0:
end_range = int(e) + 1
elif el > 0:
ei = int(e)
if ei < size:
start_range = size - ei
self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size))
self.send_header("Content-Length", end_range - start_range)
self.send_header("Last-Modified", self.date_time_string(f.fileEntry.mtime))
self.end_headers()
2022-03-14 09:10:42 +03:00
# print "Sending Bytes ",start_range, " to ", end_range, "...\n"
return f, start_range, end_range
# Вырубаем access-log
2022-03-14 09:10:42 +03:00
def log_message(self, fmt, *args):
2016-03-04 16:15:00 +03:00
return
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
return HttpHandler
2022-03-14 09:10:42 +03:00
class Gorrent2http(object):
2016-03-27 15:27:14 +03:00
pause = False
2022-03-14 09:10:42 +03:00
def __init__(self, uri='', bind_address='localhost:5001', download_path='.',
2022-03-14 09:10:42 +03:00
idleTimeout=-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, proxy=None):
2016-03-14 09:39:33 +03:00
self.torrentHandle = None
self.forceShutdown = False
self.session = None
self.magnet = False
self.config = Struct()
self.config.uri = uri
self.config.bindAddress = bind_address
self.config.downloadPath = download_path
self.config.idleTimeout = idleTimeout
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
self.config.proxy = proxy
2016-03-04 16:15:00 +03:00
if self.config.uri == '':
raise Exception("uri is empty string")
2016-03-04 16:15:00 +03:00
if self.config.uri.startswith('magnet:'):
self.magnet = True
if self.config.resumeFile is None: self.config.resumeFile = ''
2016-03-04 16:15:00 +03:00
if self.config.resumeFile != '' and not self.config.keepFiles:
raise Exception('Не должно быть файла восстановления, если мы не храним файлы')
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def buildTorrentParams(self, uri):
try:
absPath = uri2path(uri)
2016-03-14 13:36:27 +03:00
logging.info('Opening local torrent file: %s' % (encode_msg(absPath),))
torrent_info = gt.torrent_info(gt.bdecode(open(absPath, 'rb').read()))
except Exception as e:
strerror = e.args
logging.error('Build torrent params error is (%s)' % (strerror,))
raise
2016-03-04 16:15:00 +03:00
torrentParams = {}
torrentParams['ti'] = torrent_info
2016-03-14 13:36:27 +03:00
logging.info('Setting save path: %s' % (encode_msg(self.config.downloadPath),))
2016-03-04 16:15:00 +03:00
torrentParams['save_path'] = self.config.downloadPath
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
if os.path.exists(self.config.resumeFile):
2016-03-14 13:42:35 +03:00
logging.info('Loading resume file: %s' % (encode_msg(self.config.resumeFile),))
2016-03-04 16:15:00 +03:00
try:
with open(self.config.resumeFile, 'rb') as f:
2016-03-14 12:15:47 +03:00
torrentParams["auto_managed"] = True
torrentParams['resume_data'] = f.read()
2016-03-04 16:15:00 +03:00
except Exception as e:
strerror = e.args
logging.error(strerror)
if self.config.noSparseFile or self.magnet:
logging.info('Disabling sparse file support...')
torrentParams["storage_mode"] = gt.storage_mode_t.storage_mode_allocate
2016-03-04 16:15:00 +03:00
return torrentParams
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def addTorrent(self):
self.torrentParams = self.buildTorrentParams(self.config.uri)
logging.info('Adding torrent')
self.torrentHandle = self.session.add_torrent(self.torrentParams)
2016-03-05 18:33:39 +03:00
self.torrentHandle.set_sequential_download(False)
2016-03-04 16:15:00 +03:00
#
2016-03-05 18:33:39 +03:00
# Хороший флаг, но не в нашем случае. Мы сами указываем, какие куски нам нужны (handle.set_piece_deadline)
2016-03-04 16:15:00 +03:00
# Также, у нас перемотка. Т.е. произвольный доступ.
# Значит, последовательная загрузка нам будет только вредить
2016-03-05 18:33:39 +03:00
#
2016-03-17 21:11:37 +03:00
self.torrentHandle.set_max_connections(60)
2016-03-04 16:15:00 +03:00
if self.config.trackers != '':
2022-03-14 09:10:42 +03:00
trackers = self.config.trackers.split(',')
startTier = 256 - len(trackers)
2016-03-04 16:15:00 +03:00
for n in range(len(trackers)):
tracker = trackers[n].strip()
2022-03-14 09:10:42 +03:00
logging.info('Adding tracker: %s' % (tracker,))
self.torrentHandle.add_tracker({'url': tracker})
2016-03-04 16:15:00 +03:00
if self.config.enableScrape:
logging.info('Sending scrape request to tracker')
self.torrentHandle.scrape_tracker()
try:
info = self.torrentHandle.torrent_file()
except:
info = self.torrentHandle.get_torrent_info()
2016-03-04 22:00:13 +03:00
logging.info('Downloading torrent: %s' % (info.name(),))
2016-03-12 20:07:49 +03:00
try:
2016-03-19 22:00:16 +03:00
self.TorrentFS = TorrentFS(self, self.torrentHandle)
2016-03-12 20:07:49 +03:00
except Exception as e:
logging.error(e.args)
2016-03-19 22:00:16 +03:00
name = self.TorrentFS.info.name()
2022-03-14 09:10:42 +03:00
self.torrent_name = name
2016-03-04 16:15:00 +03:00
def startHTTP(self):
logging.info('Starting HTTP Server...')
handler = HttpHandlerFactory()
handler.protocol_version = 'HTTP/1.1'
2016-03-04 22:00:13 +03:00
logging.info('Listening HTTP on %s...\n' % (self.config.bindAddress,))
2016-03-04 16:15:00 +03:00
host, strport = self.config.bindAddress.split(':')
if len(strport) > 0:
srv_port = int(strport)
self.httpListener = ThreadingHTTPServer((host, srv_port), handler)
self.httpListener.root_obj = self
2022-03-14 09:10:42 +03:00
self.listener_thread = threading.Thread(target=self.httpListener.serve_forever)
self.listener_thread.start()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def startServices(self):
if self.config.enableDHT:
logging.info('Starting DHT...')
self.session.start_dht()
if self.config.enableLSD:
logging.info('Starting LSD...')
self.session.start_lsd()
if self.config.enableUPNP:
logging.info('Starting UPNP...')
self.session.start_upnp()
if self.config.enableNATPMP:
logging.info('Starting NATPMP...')
self.session.start_natpmp()
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def startSession(self):
logging.info('Starting session...')
self.session = gt.session(gt.fingerprint('LT', gt.version_major, gt.version_minor, 0, 0),
flags=int(gt.session_flags_t.add_default_plugins))
alertMask = (gt.alert.category_t.error_notification |
gt.alert.category_t.storage_notification |
gt.alert.category_t.tracker_notification |
gt.alert.category_t.status_notification)
2016-03-04 16:15:00 +03:00
if self.config.debugAlerts:
alertMask |= gt.alert.category_t.debug_notification
2016-03-04 16:15:00 +03:00
self.session.set_alert_mask(alertMask)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
settings = self.session.get_settings()
settings["request_timeout"] = self.config.requestTimeout
settings["peer_connect_timeout"] = self.config.peerConnectTimeout
settings["announce_to_all_trackers"] = True
settings["announce_to_all_tiers"] = True
settings["torrent_connect_boost"] = self.config.torrentConnectBoost
settings["connection_speed"] = self.config.connectionSpeed
settings["min_reconnect_time"] = self.config.minReconnectTime
settings["max_failcount"] = self.config.maxFailCount
settings["recv_socket_buffer_size"] = 1024 * 1024
settings["send_socket_buffer_size"] = 1024 * 1024
settings["rate_limit_ip_overhead"] = True
settings["min_announce_interval"] = 60
settings["tracker_backoff"] = 0
### Непонятно, как заставить использовать прокси только для подключения к трекеру?
if self.config.proxy is not None:
ps = gt.proxy_settings()
2022-03-14 09:10:42 +03:00
# peer_ps = lt.proxy_settings()
# peer_ps.type = lt.proxy_type.none
ps.hostname = self.config.proxy['host']
ps.port = self.config.proxy['port']
ps.type = gt.proxy_type.socks5
2022-03-14 09:10:42 +03:00
# self.session.set_peer_proxy(peer_ps)
self.session.set_proxy(ps)
settings['force_proxy'] = False
settings['proxy_peer_connections'] = False
settings['anonymous_mode'] = False
settings['proxy_tracker_connections'] = True
2016-03-04 16:15:00 +03:00
self.session.set_settings(settings)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
if self.config.stateFile != '':
logging.info('Loading session state from %s' % (self.config.stateFile,))
2016-03-04 16:15:00 +03:00
try:
with open(self.config.stateFile, 'rb') as f:
bytes__ = f.read()
except IOError as e:
strerror = e.args
logging.error(strerror)
else:
self.session.load_state(gt.bdecode(bytes__))
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
rand = SystemRandom(time.time())
portLower = self.config.listenPort
if self.config.randomPort:
portLower = rand.randint(0, 16374) + 49151
portUpper = portLower + 10
try:
self.session.listen_on(portLower, portUpper)
except IOError as e:
strerror = e.args
logging.error(strerror)
raise
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
settings = self.session.get_settings()
if self.config.userAgent != '':
settings['user_agent'] = self.config.userAgent
if self.config.connectionsLimit >= 0:
settings['connections_limit'] = self.config.connectionsLimit
if self.config.maxDownloadRate >= 0:
settings['download_rate_limit'] = self.config.maxDownloadRate * 1024
if self.config.maxUploadRate >= 0:
settings['upload_rate_limit'] = self.config.maxUploadRate * 1024
settings['enable_incoming_tcp'] = self.config.enableTCP
settings['enable_outgoing_tcp'] = self.config.enableTCP
settings['enable_incoming_utp'] = self.config.enableUTP
settings['enable_outgoing_utp'] = self.config.enableUTP
self.session.set_settings(settings)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
if self.config.dhtRouters != '':
routers = self.config.dhtRouters.split(',')
for router in routers:
router = router.strip()
if router != '':
hostPort = router.split(':')
host = hostPort[0].strip()
try:
port = len(hostPort) > 1 and int(hostPort[1].strip()) or 6881
except ValueError as e:
strerror = e.args
logging.error(strerror)
raise
2016-03-04 16:15:00 +03:00
self.session.add_dht_router(host, port)
2016-03-04 22:00:13 +03:00
logging.info('Added DHT router: %s:%d' % (host, port))
2016-03-04 16:15:00 +03:00
logging.info('Setting encryption settings')
try:
encryptionSettings = gt.pe_settings()
encryptionSettings.out_enc_policy = gt.enc_policy(self.config.encryption)
encryptionSettings.in_enc_policy = gt.enc_policy(self.config.encryption)
encryptionSettings.allowed_enc_level = gt.enc_level.both
2016-03-04 16:15:00 +03:00
encryptionSettings.prefer_rc4 = True
self.session.set_pe_settings(encryptionSettings)
except Exception as e:
logging.info('Encryption not supported: %s' % (e.args,))
2022-03-14 09:10:42 +03:00
def Status(self):
2016-03-17 20:41:50 +03:00
info = self.TorrentFS.info
2016-03-17 21:45:50 +03:00
tstatus = self.torrentHandle.status()
2016-03-17 20:22:07 +03:00
status = {
2022-03-14 09:10:42 +03:00
'name': self.torrent_name,
'state': int(tstatus.state),
'state_str': str(tstatus.state),
'error': tstatus.error,
'progress': tstatus.progress,
'download_rate': tstatus.download_rate // 1024,
'upload_rate': tstatus.upload_rate // 1024,
'total_download': tstatus.total_download,
'total_upload': tstatus.total_upload,
'num_peers': tstatus.num_peers,
'num_seeds': tstatus.num_seeds,
'total_seeds': tstatus.num_complete,
'total_peers': tstatus.num_incomplete
}
return status
2022-03-14 09:10:42 +03:00
2016-03-19 22:00:16 +03:00
def Ls(self, index):
fi = {}
if self.TorrentFS.HasTorrentInfo():
2022-03-14 09:10:42 +03:00
x = [n for n in list(self.TorrentFS.files.keys()) if self.TorrentFS.files[n].index == index]
2016-03-19 22:00:16 +03:00
name = x[0]
2016-03-12 19:45:18 +03:00
files = self.TorrentFS.files
2022-03-14 09:10:42 +03:00
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.parse.quote(name)
2016-03-19 22:00:16 +03:00
fi = {
2022-03-14 09:10:42 +03:00
'index': files[name].index,
'name': files[name].unicode_name,
'media_type': files[name].media_type,
'size': files[name].size,
'offset': files[name].offset,
'download': files[name].downloaded,
'progress': files[name].progress,
'save_path': files[name].save_path,
'url': Url
}
2016-03-19 22:00:16 +03:00
return fi
2022-03-14 09:10:42 +03:00
def Peers(self):
peers = {'peers': []}
for peer in self.torrentHandle.get_peer_info():
if peer.flags & peer.connecting or peer.flags & peer.handshake:
continue
pi = {
2022-03-14 09:10:42 +03:00
'Ip': peer.ip,
'Flags': peer.flags,
'Source': peer.source,
'UpSpeed': peer.up_speed // 1024,
'DownSpeed': peer.down_speed // 1024,
'TotalDownload': peer.total_download,
'TotalUpload': peer.total_upload,
'Country': peer.country,
'Client': peer.client
}
peers['peers'].append(pi)
return peers
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def consumeAlerts(self):
alerts = self.session.pop_alerts()
for alert in alerts:
if type(alert) == gt.save_resume_data_alert:
2016-03-04 16:15:00 +03:00
self.processSaveResumeDataAlert(alert)
break
2022-03-14 09:10:42 +03:00
2016-03-14 12:15:47 +03:00
def waitForAlert(self, alert_type, timeout):
2016-03-04 16:15:00 +03:00
start = time.time()
while True:
alert = self.session.wait_for_alert(100)
if (time.time() - start) > timeout:
return None
if alert is not None:
alert = self.session.pop_alert()
2016-03-14 12:15:47 +03:00
if type(alert) == alert_type:
2016-03-04 16:15:00 +03:00
return alert
2022-03-14 09:10:42 +03:00
2016-03-04 22:00:13 +03:00
def loop(self):
2016-03-04 16:15:00 +03:00
self.saveResumeDataTicker = Ticker(5)
time_start = time.time()
while True:
if self.forceShutdown:
return
if time.time() - time_start > 0.5:
self.consumeAlerts()
2016-03-04 16:15:00 +03:00
self.TorrentFS.LoadFileProgress()
state = self.torrentHandle.status().state
if self.config.exitOnFinish and (state == state.finished or state == state.seeding):
self.forceShutdown = True
if os.getppid() == 1:
self.forceShutdown = True
time_start = time.time()
if self.saveResumeDataTicker.true:
self.saveResumeData(True)
time.sleep(0.3)
2016-03-04 16:15:00 +03:00
def processSaveResumeDataAlert(self, alert):
2016-03-14 13:42:35 +03:00
logging.info('Saving resume data to: %s' % (encode_msg(self.config.resumeFile),))
data = gt.bencode(alert.resume_data)
2016-03-04 16:15:00 +03:00
try:
with open(self.config.resumeFile, 'wb') as f:
f.write(data)
except IOError as e:
strerror = e.args
logging.error(strerror)
2022-03-14 09:10:42 +03:00
def saveResumeData(self, async_=False):
2016-03-04 16:15:00 +03:00
if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '':
return False
self.torrentHandle.save_resume_data(gt.save_resume_flags_t.flush_disk_cache)
2022-03-14 09:10:42 +03:00
if not async_:
alert = self.waitForAlert(gt.save_resume_data_alert, 5)
2022-03-14 09:10:42 +03:00
if alert is None:
2016-03-04 16:15:00 +03:00
return False
self.processSaveResumeDataAlert(alert)
return True
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def saveSessionState(self):
if self.config.stateFile == '':
return
entry = self.session.save_state()
data = gt.bencode(entry)
2016-03-14 13:42:35 +03:00
logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),))
2016-03-04 16:15:00 +03:00
try:
2016-03-14 13:42:35 +03:00
logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),))
2016-03-04 16:15:00 +03:00
with open(self.config.stateFile, 'wb') as f:
f.write(data)
except IOError as e:
strerror = e.args
logging.error(strerror)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def removeFiles(self, files):
for file in files:
try:
os.remove(file)
except Exception as e:
strerror = e.args
logging.error(strerror)
else:
path = os.path.dirname(file)
savePath = os.path.abspath(self.config.downloadPath)
savePath = savePath[-1] == os.path.sep and savePath[:-1] or savePath
while path != savePath:
os.remove(path)
path_ = os.path.dirname(path)
path = path_[-1] == os.path.sep and path_[:-1] or path_
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def filesToRemove(self):
files = []
if self.TorrentFS.HasTorrentInfo():
for i, f in enumerate(self.torrentHandle.files()):
isComplete = self.TorrentFS.progresses[i] == f.size
if (not self.config.keepComplete or not isComplete) and (not self.config.keepIncomplete or isComplete):
path = os.path.abspath(os.path.join(self.TorrentFS.save_path, localize_path(f.path)))
if os.path.exists(path):
files.append(path)
2016-03-04 22:18:52 +03:00
return files
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def removeTorrent(self):
files = []
flag = 0
state = self.torrentHandle.status().state
if state != state.checking_files and not self.config.keepFiles:
if not self.config.keepComplete and not self.config.keepIncomplete:
flag = int(gt.options_t.delete_files)
2016-03-04 16:15:00 +03:00
else:
files = self.filesToRemove()
logging.info('Removing the torrent')
self.session.remove_torrent(self.torrentHandle, flag)
if flag > 0 or len(files) > 0:
logging.info('Waiting for files to be removed')
self.waitForAlert(gt.torrent_deleted_alert, 15)
2016-03-04 16:15:00 +03:00
self.removeFiles(files)
2022-03-14 09:10:42 +03:00
2016-03-04 16:15:00 +03:00
def shutdown(self):
logging.info('Stopping pyrrent2http...')
self.forceShutdown = True
2016-03-04 16:15:00 +03:00
self.saveResumeDataTicker.stop()
self.httpListener.shutdown()
self.httpListener.socket.close()
2016-03-04 16:15:00 +03:00
self.TorrentFS.Shutdown()
if self.session != None:
self.session.pause()
self.waitForAlert(gt.torrent_paused_alert, 10)
2016-03-04 16:15:00 +03:00
if self.torrentHandle is not None:
self.saveResumeData(False)
self.saveSessionState()
self.removeTorrent()
logging.info('Aborting the session')
self.session = None
2016-03-04 16:15:00 +03:00
logging.info('Bye bye')