2016-03-04 16:15:00 +03:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
import sys, os
|
|
|
|
|
import json
|
2016-03-05 17:55:44 +03:00
|
|
|
|
import chardet
|
2016-03-10 20:45:10 +03:00
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
try:
|
2016-03-04 18:52:14 +03:00
|
|
|
|
from python_libtorrent import get_libtorrent
|
|
|
|
|
lt=get_libtorrent()
|
|
|
|
|
print('Imported libtorrent v%s from python_libtorrent' %(lt.version, ))
|
|
|
|
|
except Exception, e:
|
|
|
|
|
print('Error importing python_libtorrent.Exception: %s' %(str(e),))
|
2016-03-04 16:15:00 +03:00
|
|
|
|
try:
|
|
|
|
|
import libtorrent as lt
|
|
|
|
|
except Exception as e:
|
|
|
|
|
strerror = e.args
|
2016-03-04 18:52:14 +03:00
|
|
|
|
print(strerror)
|
2016-03-10 20:45:10 +03:00
|
|
|
|
raise
|
2016-03-04 22:00:13 +03:00
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
from random import SystemRandom
|
|
|
|
|
import time
|
|
|
|
|
import urlparse, urllib
|
|
|
|
|
import BaseHTTPServer
|
|
|
|
|
import SocketServer
|
|
|
|
|
import threading
|
|
|
|
|
import io
|
2016-03-14 13:29:14 +03:00
|
|
|
|
from util import localize_path, Struct, detect_media_type, uri2path, encode_msg
|
2016-03-04 16:15:00 +03:00
|
|
|
|
|
2016-03-10 20:50:17 +03:00
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
######################################################################################
|
|
|
|
|
|
|
|
|
|
if not hasattr(os, 'getppid'):
|
|
|
|
|
import ctypes
|
|
|
|
|
|
|
|
|
|
TH32CS_SNAPPROCESS = 0x02L
|
|
|
|
|
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
|
|
|
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId
|
|
|
|
|
|
|
|
|
|
MAX_PATH = 260
|
|
|
|
|
|
|
|
|
|
_kernel32dll = ctypes.windll.Kernel32
|
|
|
|
|
CloseHandle = _kernel32dll.CloseHandle
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Process32First = _kernel32dll.Process32FirstW
|
|
|
|
|
Process32Next = _kernel32dll.Process32NextW
|
|
|
|
|
|
|
|
|
|
def getppid():
|
|
|
|
|
'''
|
|
|
|
|
:return: The pid of the parent of this process.
|
|
|
|
|
'''
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
os.getppid = getppid
|
|
|
|
|
|
|
|
|
|
#################################################################################
|
|
|
|
|
|
|
|
|
|
AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True
|
2016-03-05 02:44:38 +03:00
|
|
|
|
VERSION = "0.6.0"
|
2016-03-04 16:15:00 +03:00
|
|
|
|
USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version
|
|
|
|
|
|
|
|
|
|
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'}
|
|
|
|
|
######################################################################################
|
|
|
|
|
|
|
|
|
|
class Ticker(object):
|
|
|
|
|
def __init__(self, interval):
|
|
|
|
|
self.tick = False
|
|
|
|
|
self._timer = None
|
|
|
|
|
self.interval = interval
|
|
|
|
|
self.is_running = False
|
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
#######################################################################################
|
|
|
|
|
|
|
|
|
|
class TorrentFile(object):
|
|
|
|
|
tfs = None
|
|
|
|
|
closed = True
|
2016-03-10 10:21:17 +03:00
|
|
|
|
save_path = str()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
fileEntry = None
|
2016-03-12 00:18:11 +03:00
|
|
|
|
index = 0
|
2016-03-04 16:15:00 +03:00
|
|
|
|
filePtr = None
|
2016-03-12 00:18:11 +03:00
|
|
|
|
downloaded = 0
|
|
|
|
|
progress = 0.0
|
2016-03-04 16:15:00 +03:00
|
|
|
|
pdl_thread = None
|
|
|
|
|
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
|
2016-03-05 18:09:26 +03:00
|
|
|
|
self.unicode_name = self.name.decode(chardet.detect(self.name)['encoding'])
|
2016-03-12 00:18:11 +03:00
|
|
|
|
self.media_type = detect_media_type(self.unicode_name)
|
2016-03-10 10:21:17 +03:00
|
|
|
|
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()
|
2016-03-10 20:45:10 +03:00
|
|
|
|
self.pieces_deadlined = [False] * (self.endPiece - self.startPiece)
|
2016-03-12 01:02:35 +03:00
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
def Downloaded(self):
|
|
|
|
|
return self.downloaded
|
|
|
|
|
def Progress(self):
|
|
|
|
|
return self.progress
|
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:
|
2016-03-10 10:21:17 +03:00
|
|
|
|
while not os.path.exists(self.save_path):
|
2016-03-13 17:28:01 +03:00
|
|
|
|
logging.info('Waiting for file: %s' % (self.save_path,))
|
2016-03-14 12:15:47 +03:00
|
|
|
|
self.tfs.handle.flush_cache()
|
2016-03-06 02:23:42 +03:00
|
|
|
|
time.sleep(0.5)
|
2016-03-10 10:21:17 +03:00
|
|
|
|
self.filePtr = io.open(self.save_path, 'rb')
|
2016-03-04 16:15:00 +03:00
|
|
|
|
return self.filePtr
|
|
|
|
|
def log(self, message):
|
2016-03-12 01:02:35 +03:00
|
|
|
|
fnum = self.index
|
2016-03-04 16:15:00 +03:00
|
|
|
|
logging.info("[%d] %s\n" % (fnum, message))
|
|
|
|
|
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
|
|
|
|
|
def SetPriority(self, priority):
|
|
|
|
|
self.tfs.setPriority(self.index, priority)
|
|
|
|
|
def readOffset(self):
|
|
|
|
|
return self.filePtr.seek(0, io.SEEK_CUR)
|
|
|
|
|
def havePiece(self, piece):
|
|
|
|
|
return self.tfs.handle.have_piece(piece)
|
|
|
|
|
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
|
|
|
|
|
def waitForPiece(self, piece):
|
|
|
|
|
def set_deadlines(p):
|
|
|
|
|
next_piece = p + 1
|
2016-03-05 02:44:38 +03:00
|
|
|
|
BUF_SIZE = 2 # Лучшее враг хорошего
|
2016-03-04 16:15:00 +03:00
|
|
|
|
for i in range(BUF_SIZE):
|
|
|
|
|
if (next_piece + i < self.endPiece and
|
2016-03-05 01:43:47 +03:00
|
|
|
|
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))
|
2016-03-05 01:43:47 +03:00
|
|
|
|
self.pieces_deadlined[(next_piece + i) - self.startPiece] = True
|
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():
|
|
|
|
|
self.pdl_thread = threading.Thread(target = set_deadlines, args = (piece,))
|
|
|
|
|
self.pdl_thread.start()
|
|
|
|
|
return True
|
|
|
|
|
def Close(self):
|
|
|
|
|
if self.closed: return
|
2016-03-05 18:09:26 +03:00
|
|
|
|
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:
|
|
|
|
|
self.filePtr.close()
|
|
|
|
|
self.filePtr = None
|
|
|
|
|
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_)
|
|
|
|
|
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)
|
|
|
|
|
for i in range(startPiece, endPiece + 1):
|
|
|
|
|
if not self.waitForPiece(i):
|
|
|
|
|
raise IOError
|
|
|
|
|
read = filePtr.readinto(buf)
|
|
|
|
|
return read
|
|
|
|
|
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
|
|
|
|
|
newOffset = filePtr.seek(offset, whence)
|
|
|
|
|
self.log('Seeking to %d/%d' % (newOffset, self.size))
|
|
|
|
|
return newOffset
|
|
|
|
|
def IsComplete(self):
|
|
|
|
|
return self.downloaded == self.size
|
|
|
|
|
|
|
|
|
|
#######################################################################################
|
|
|
|
|
|
|
|
|
|
class TorrentFS(object):
|
|
|
|
|
handle = None
|
|
|
|
|
info = None
|
|
|
|
|
priorities = list()
|
|
|
|
|
openedFiles = list()
|
|
|
|
|
lastOpenedFile = None
|
|
|
|
|
shuttingDown = False
|
|
|
|
|
fileCounter = int()
|
|
|
|
|
progresses = list()
|
2016-03-10 10:21:17 +03:00
|
|
|
|
save_path = None
|
2016-03-04 16:15:00 +03:00
|
|
|
|
|
|
|
|
|
def __init__(self, root, handle, startIndex):
|
|
|
|
|
self.root = root
|
|
|
|
|
self.handle = handle
|
|
|
|
|
self.waitForMetadata()
|
2016-03-10 10:21:17 +03:00
|
|
|
|
self.save_path = localize_path(self.root.torrentParams['save_path'])
|
2016-03-12 00:18:11 +03:00
|
|
|
|
self.priorities = list(self.handle.file_priorities())
|
2016-03-12 19:45:18 +03:00
|
|
|
|
self.files = self.__files_()
|
2016-03-17 22:09:20 +03:00
|
|
|
|
self.handle.set_piece_deadline(self.files[startIndex].startPiece, 50)
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if startIndex < 0:
|
|
|
|
|
logging.info('No -file-index specified, downloading will be paused until any file is requested')
|
2016-03-17 20:33:02 +03:00
|
|
|
|
|
2016-03-17 19:43:10 +03:00
|
|
|
|
num_files = self.info.num_files()
|
2016-03-17 20:33:02 +03:00
|
|
|
|
|
2016-03-17 19:43:10 +03:00
|
|
|
|
for i in range(num_files):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if startIndex == i:
|
|
|
|
|
self.setPriority(i, 1)
|
|
|
|
|
else:
|
|
|
|
|
self.setPriority(i, 0)
|
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()
|
|
|
|
|
def addOpenedFile(self, file_):
|
|
|
|
|
self.openedFiles.append(file_)
|
|
|
|
|
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)
|
|
|
|
|
def findOpenedFile(self, file):
|
|
|
|
|
for i, f in enumerate(self.openedFiles):
|
|
|
|
|
if f == file:
|
|
|
|
|
return i
|
|
|
|
|
return -1
|
|
|
|
|
def removeOpenedFile(self, file):
|
|
|
|
|
pos = self.findOpenedFile(file)
|
|
|
|
|
if pos >= 0:
|
|
|
|
|
del self.openedFiles[pos]
|
|
|
|
|
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()
|
|
|
|
|
def HasTorrentInfo(self):
|
|
|
|
|
return self.info is not None
|
|
|
|
|
def LoadFileProgress(self):
|
|
|
|
|
self.progresses = self.handle.file_progress()
|
2016-03-12 00:18:11 +03:00
|
|
|
|
for i, f in enumerate(self.files):
|
|
|
|
|
f.downloaded = self.getFileDownloadedBytes(i)
|
|
|
|
|
if f.size > 0: f.progress = float(f.downloaded)/float(f.size)
|
2016-03-04 16:15:00 +03:00
|
|
|
|
def getFileDownloadedBytes(self, i):
|
|
|
|
|
try:
|
2016-03-12 00:18:11 +03:00
|
|
|
|
bytes_ = self.progresses[i]
|
2016-03-04 16:15:00 +03:00
|
|
|
|
except IndexError:
|
2016-03-12 00:18:11 +03:00
|
|
|
|
bytes_ = 0
|
|
|
|
|
return bytes_
|
2016-03-12 19:45:18 +03:00
|
|
|
|
def __files_(self):
|
2016-03-13 19:03:33 +03:00
|
|
|
|
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:
|
|
|
|
|
file_.progress = float(file_.downloaded)/float(file_.size)
|
2016-03-17 19:47:45 +03:00
|
|
|
|
logging.info('adding file: %s' % (file_.name,))
|
2016-03-12 20:07:49 +03:00
|
|
|
|
files_.append(file_)
|
|
|
|
|
return files_
|
2016-03-12 19:45:18 +03:00
|
|
|
|
def __file_at_(self, index):
|
2016-03-13 19:03:33 +03:00
|
|
|
|
info = self.info
|
2016-03-04 16:15:00 +03:00
|
|
|
|
fileEntry = info.file_at(index)
|
2016-03-06 02:23:42 +03:00
|
|
|
|
fe_path = fileEntry.path
|
2016-03-10 10:21:17 +03:00
|
|
|
|
path = os.path.abspath(os.path.join(self.save_path, localize_path(fe_path)))
|
2016-03-04 16:15:00 +03:00
|
|
|
|
return TorrentFile(
|
|
|
|
|
self,
|
|
|
|
|
fileEntry,
|
|
|
|
|
path,
|
|
|
|
|
index
|
|
|
|
|
)
|
|
|
|
|
def FileByName(self, name):
|
2016-03-10 10:21:17 +03:00
|
|
|
|
savePath = os.path.abspath(os.path.join(self.save_path, localize_path(name)))
|
2016-03-12 19:45:18 +03:00
|
|
|
|
for file_ in self.files:
|
2016-03-10 10:21:17 +03:00
|
|
|
|
if file_.save_path == savePath:
|
2016-03-04 16:15:00 +03:00
|
|
|
|
return file_
|
|
|
|
|
raise IOError
|
|
|
|
|
def Open(self, name):
|
|
|
|
|
if self.shuttingDown or not self.HasTorrentInfo():
|
|
|
|
|
raise IOError
|
|
|
|
|
return self.OpenFile(name)
|
|
|
|
|
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)
|
|
|
|
|
def OpenFile(self, name):
|
|
|
|
|
try:
|
|
|
|
|
tf = self.FileByName(name)
|
|
|
|
|
except IOError:
|
|
|
|
|
return
|
|
|
|
|
tf.closed = False
|
|
|
|
|
self.fileCounter += 1
|
|
|
|
|
tf.num = self.fileCounter
|
2016-03-05 18:09:26 +03:00
|
|
|
|
tf.log('Opening %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.addOpenedFile(tf)
|
|
|
|
|
self.checkPriorities()
|
|
|
|
|
return tf
|
|
|
|
|
|
|
|
|
|
#############################################################
|
|
|
|
|
|
|
|
|
|
class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
|
|
|
|
def handle_error(self, *args, **kwargs):
|
|
|
|
|
'''Обходим злосчастный "Broken Pipe" и прочие трейсы'''
|
|
|
|
|
if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT:
|
|
|
|
|
BaseHTTPServer.HTTPServer.handle_error(self, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def HttpHandlerFactory():
|
|
|
|
|
class HttpHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
|
|
|
def do_GET(self):
|
|
|
|
|
#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 == '/status':
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.statusHandler()
|
|
|
|
|
elif self.path == '/ls':
|
|
|
|
|
self.lsHandler()
|
|
|
|
|
elif self.path == '/peers':
|
|
|
|
|
self.peersHandler()
|
|
|
|
|
elif self.path == '/trackers':
|
|
|
|
|
self.trackersHandler()
|
|
|
|
|
elif self.path.startswith('/get/'): # Неясно, зачем
|
|
|
|
|
return
|
|
|
|
|
# self.getHandler() # этот запрос?
|
|
|
|
|
elif self.path == '/shutdown':
|
|
|
|
|
self.server.root_obj.forceShutdown = True
|
|
|
|
|
self.server.server_close()
|
|
|
|
|
self.end_headers()
|
|
|
|
|
self.wfile.write('OK')
|
2016-03-12 19:45:18 +03:00
|
|
|
|
elif self.path.startswith('/files/'):'''
|
|
|
|
|
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()
|
|
|
|
|
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
|
|
|
|
|
buf = bytearray(chunk)
|
|
|
|
|
while chunk > 0:
|
|
|
|
|
if start_range + chunk > end_range:
|
|
|
|
|
chunk = end_range - start_range
|
|
|
|
|
buf = bytearray(chunk)
|
|
|
|
|
try:
|
2016-03-12 01:02:35 +03:00
|
|
|
|
if f.Read(buf) < 1: break
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.wfile.write(buf)
|
|
|
|
|
except:
|
|
|
|
|
break
|
|
|
|
|
total += chunk
|
|
|
|
|
start_range += chunk
|
|
|
|
|
f.Close()
|
|
|
|
|
def send_head(self):
|
|
|
|
|
fname = urllib.unquote(self.path.lstrip('/files/'))
|
|
|
|
|
try:
|
|
|
|
|
f = self.server.root_obj.TorrentFS.Open(fname)
|
|
|
|
|
except IOError:
|
|
|
|
|
self.send_error(404, "File not found")
|
|
|
|
|
return (None, 0, 0)
|
|
|
|
|
_, ext = os.path.splitext(fname)
|
|
|
|
|
ctype = (ext != '' and ext in VIDEO_EXTS.keys())and VIDEO_EXTS[ext] or 'application/octet-stream'
|
|
|
|
|
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()
|
|
|
|
|
#print "Sending Bytes ",start_range, " to ", end_range, "...\n"
|
|
|
|
|
return (f, start_range, end_range)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
'''def statusHandler(self):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.send_response(200)
|
|
|
|
|
self.send_header("Content-type", "application/json")
|
|
|
|
|
self.end_headers()
|
2016-03-04 17:52:41 +03:00
|
|
|
|
status = self.server.root_obj.Status()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
output = json.dumps(status)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
self.wfile.write(output)'''
|
|
|
|
|
'''def lsHandler(self):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.send_response(200)
|
|
|
|
|
self.send_header("Content-type", "application/json")
|
|
|
|
|
self.end_headers()
|
2016-03-04 18:02:14 +03:00
|
|
|
|
retFiles = self.server.root_obj.Ls()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
output = json.dumps(retFiles)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
self.wfile.write(output)'''
|
|
|
|
|
'''def peersHandler(self):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.send_response(200)
|
|
|
|
|
self.send_header("Content-type", "application/json")
|
|
|
|
|
self.end_headers()
|
2016-03-04 18:13:38 +03:00
|
|
|
|
peers = self.server.root_obj.Peers()
|
|
|
|
|
output = json.dumps(peers)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
self.wfile.write(output)'''
|
|
|
|
|
'''def trackersHandler(self):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.send_response(200)
|
|
|
|
|
self.send_header("Content-type", "application/json")
|
|
|
|
|
self.end_headers()
|
|
|
|
|
ret = list()
|
|
|
|
|
try:
|
2016-03-04 18:13:38 +03:00
|
|
|
|
info = self.server.root_obj.torrentHandler.torrent_file()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
except:
|
2016-03-04 18:13:38 +03:00
|
|
|
|
info = self.server.root_obj.torrentHandler.get_torrent_info()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
for tracker in info.trackers():
|
|
|
|
|
pi = {
|
|
|
|
|
'Url': tracker.url,
|
|
|
|
|
'NextAnnounceIn': self.server.root_obj.torrentHandler.status().next_announce.seconds,
|
|
|
|
|
'MinAnnounceIn': 10, # FIXME неясно, откуда брать
|
|
|
|
|
'ErrorCode': 0, #FIXME неясно, откуда брать
|
|
|
|
|
'ErrorMessage': u'', #FIXME неясно, откуда брать
|
|
|
|
|
'Message': u'', #FIXME неясно, откуда брать
|
|
|
|
|
'Tier': tracker.tier,
|
|
|
|
|
'FailLimit': tracker.fail_limit,
|
|
|
|
|
'Fails': tracker.fails,
|
|
|
|
|
'Source': tracker.source,
|
|
|
|
|
'Verified': tracker.verified,
|
|
|
|
|
'Updating': tracker.updating,
|
|
|
|
|
'StartSent': tracker.start_sent,
|
|
|
|
|
'CompleteSent': tracker.complete_sent,
|
|
|
|
|
}
|
|
|
|
|
ret.append(pi)
|
|
|
|
|
output = json.dumps(ret)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
self.wfile.write(output)'''
|
2016-03-04 18:13:38 +03:00
|
|
|
|
# Вырубаем access-log
|
2016-03-04 16:15:00 +03:00
|
|
|
|
def log_message(self, format, *args):
|
|
|
|
|
return
|
|
|
|
|
return HttpHandler
|
|
|
|
|
|
|
|
|
|
class Pyrrent2http(object):
|
2016-03-14 09:39:33 +03:00
|
|
|
|
def __init__(self, uri = '', bindAddress = 'localhost:5001', downloadPath = '.',
|
2016-03-10 19:55:25 +03:00
|
|
|
|
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):
|
2016-03-14 09:39:33 +03:00
|
|
|
|
self.torrentHandle = None
|
|
|
|
|
self.forceShutdown = False
|
|
|
|
|
self.session = None
|
|
|
|
|
self.magnet = False
|
|
|
|
|
|
2016-03-10 19:55:25 +03:00
|
|
|
|
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
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if self.config.uri == '':
|
2016-03-10 19:55:25 +03:00
|
|
|
|
raise Exception("uri is empty string")
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if self.config.uri.startswith('magnet:'):
|
|
|
|
|
self.magnet = True
|
2016-03-10 19:55:25 +03:00
|
|
|
|
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:
|
2016-03-10 19:55:25 +03:00
|
|
|
|
raise Exception('Не должно быть файла восстановления, если мы не храним файлы')
|
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
def buildTorrentParams(self, uri):
|
2016-03-12 02:29:23 +03:00
|
|
|
|
try:
|
|
|
|
|
absPath = uri2path(uri)
|
2016-03-14 13:36:27 +03:00
|
|
|
|
logging.info('Opening local torrent file: %s' % (encode_msg(absPath),))
|
2016-03-14 13:29:14 +03:00
|
|
|
|
torrent_info = lt.torrent_info(lt.bdecode(open(absPath, 'rb').read()))
|
2016-03-12 02:29:23 +03:00
|
|
|
|
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 = {}
|
2016-03-12 02:29:23 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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"] = lt.storage_mode_t.storage_mode_allocate
|
|
|
|
|
return torrentParams
|
|
|
|
|
|
|
|
|
|
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 != '':
|
|
|
|
|
trackers = self.config.trackers.split(',')
|
|
|
|
|
startTier = 256 - len(trackers)
|
|
|
|
|
for n in range(len(trackers)):
|
|
|
|
|
tracker = trackers[n].strip()
|
2016-03-14 13:42:35 +03:00
|
|
|
|
logging.info('Adding tracker: %s' % (tracker,) )
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.torrentHandle.add_tracker(tracker, startTier + n)
|
|
|
|
|
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:
|
|
|
|
|
self.TorrentFS = TorrentFS(self, self.torrentHandle, self.config.fileIndex)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.error(e.args)
|
2016-03-04 16:15:00 +03:00
|
|
|
|
|
|
|
|
|
def startHTTP(self):
|
|
|
|
|
#def http_server_loop(listener, alive):
|
|
|
|
|
# while alive.is_set():
|
|
|
|
|
# print('+++handle request+++')
|
|
|
|
|
# listener.handle_request()
|
|
|
|
|
# listener.server_close()
|
|
|
|
|
#self.main_alive = threading.Event()
|
|
|
|
|
#self.main_alive.set()
|
|
|
|
|
logging.info('Starting HTTP Server...')
|
|
|
|
|
handler = HttpHandlerFactory()
|
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
|
|
|
|
|
#self.httpListener.timeout = 0.5
|
|
|
|
|
#thread = threading.Thread(target = http_server_loop, args = (self.httpListener, self.main_alive))
|
|
|
|
|
thread = threading.Thread(target = self.httpListener.serve_forever)
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
def startSession(self):
|
|
|
|
|
logging.info('Starting session...')
|
|
|
|
|
self.session = lt.session(lt.fingerprint('LT', lt.version_major, lt.version_minor, 0, 0),
|
|
|
|
|
flags=int(lt.session_flags_t.add_default_plugins))
|
|
|
|
|
alertMask = (lt.alert.category_t.error_notification |
|
|
|
|
|
lt.alert.category_t.storage_notification |
|
|
|
|
|
lt.alert.category_t.tracker_notification |
|
|
|
|
|
lt.alert.category_t.status_notification)
|
|
|
|
|
if self.config.debugAlerts:
|
|
|
|
|
alertMask |= lt.alert.category_t.debug_notification
|
|
|
|
|
self.session.set_alert_mask(alertMask)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
self.session.set_settings(settings)
|
2016-03-04 22:00:13 +03:00
|
|
|
|
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if self.config.stateFile != '':
|
2016-03-10 19:55:25 +03:00
|
|
|
|
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(lt.bdecode(bytes__))
|
|
|
|
|
|
|
|
|
|
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)
|
2016-03-10 20:45:10 +03:00
|
|
|
|
raise
|
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)
|
|
|
|
|
|
|
|
|
|
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)
|
2016-03-10 20:45:10 +03:00
|
|
|
|
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 = lt.pe_settings()
|
|
|
|
|
encryptionSettings.out_enc_policy = lt.enc_policy(self.config.encryption)
|
|
|
|
|
encryptionSettings.in_enc_policy = lt.enc_policy(self.config.encryption)
|
|
|
|
|
encryptionSettings.allowed_enc_level = lt.enc_level.both
|
|
|
|
|
encryptionSettings.prefer_rc4 = True
|
|
|
|
|
self.session.set_pe_settings(encryptionSettings)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.info('Encryption not supported: %s' % (e.args,))
|
2016-03-04 17:52:41 +03:00
|
|
|
|
|
|
|
|
|
def Status(self):
|
2016-03-17 20:41:50 +03:00
|
|
|
|
info = self.TorrentFS.info
|
2016-03-17 21:22:34 +03:00
|
|
|
|
#tstatus = self.torrentHandle.status()
|
2016-03-17 21:24:38 +03:00
|
|
|
|
#tstatus = self.TorrentFS.handle.status()
|
2016-03-17 21:45:50 +03:00
|
|
|
|
tstatus = self.torrentHandle.status()
|
2016-03-17 20:22:07 +03:00
|
|
|
|
|
2016-03-04 17:52:41 +03:00
|
|
|
|
status = {
|
2016-03-17 20:33:02 +03:00
|
|
|
|
'name' : info.name(),
|
2016-03-17 21:40:41 +03:00
|
|
|
|
'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
|
2016-03-04 17:52:41 +03:00
|
|
|
|
}
|
|
|
|
|
return status
|
2016-03-04 18:02:14 +03:00
|
|
|
|
def Ls(self):
|
|
|
|
|
retFiles = {'files': []}
|
|
|
|
|
if self.TorrentFS.HasTorrentInfo():
|
2016-03-12 19:45:18 +03:00
|
|
|
|
files = self.TorrentFS.files
|
2016-03-04 18:02:14 +03:00
|
|
|
|
for file_ in files:
|
2016-03-05 18:09:26 +03:00
|
|
|
|
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.quote(file_.name)
|
2016-03-04 18:02:14 +03:00
|
|
|
|
fi = {
|
2016-03-05 18:09:26 +03:00
|
|
|
|
'name': file_.unicode_name,
|
2016-03-12 00:18:11 +03:00
|
|
|
|
'media_type': file_.media_type,
|
2016-03-04 18:02:14 +03:00
|
|
|
|
'size': file_.size,
|
|
|
|
|
'offset': file_.offset,
|
2016-03-12 00:18:11 +03:00
|
|
|
|
'download': file_.downloaded,
|
|
|
|
|
'progress': file_.progress,
|
|
|
|
|
'save_path': file_.save_path,
|
2016-03-04 18:02:14 +03:00
|
|
|
|
'url': Url
|
|
|
|
|
}
|
|
|
|
|
retFiles['files'].append(fi)
|
|
|
|
|
return retFiles
|
2016-03-04 18:13:38 +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 = {
|
|
|
|
|
'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
|
2016-03-04 16:15:00 +03:00
|
|
|
|
def stats(self):
|
|
|
|
|
status = self.torrentHandle.status()
|
|
|
|
|
dhtStatusStr = ''
|
|
|
|
|
if not status.has_metadata:
|
|
|
|
|
return
|
|
|
|
|
if self.config.showAllStats or self.config.showOverallProgress:
|
|
|
|
|
sessionStatus = self.session.status()
|
|
|
|
|
if self.session.is_dht_running():
|
|
|
|
|
dhtStatusStr = ', DHT nodes: %d' % (sessionStatus.dht_nodes,)
|
|
|
|
|
errorStr = ''
|
|
|
|
|
if len(status.error) > 0:
|
|
|
|
|
errorStr = ' (%s)' % (status.error,)
|
|
|
|
|
logging.info('%s, overall progress: %.2f%%, dl/ul: %.3f/%.3f kbps, peers/seeds: %d/%d' % (
|
|
|
|
|
str(status.state),
|
|
|
|
|
status.progress * 100,
|
|
|
|
|
float(status.download_rate)/1024,
|
|
|
|
|
float(status.upload_rate)/1024,
|
|
|
|
|
status.num_peers,
|
|
|
|
|
status.num_seeds
|
|
|
|
|
) + dhtStatusStr + errorStr
|
|
|
|
|
)
|
|
|
|
|
if self.config.showFilesProgress or self.config.showAllStats:
|
|
|
|
|
str_ = 'Files: '
|
2016-03-12 19:45:18 +03:00
|
|
|
|
for i, f in enumerate(self.TorrentFS.files):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
str_ += '[%d] %.2f%% ' % (i, f.Progress()*100)
|
|
|
|
|
logging.info(str_)
|
2016-03-12 19:45:18 +03:00
|
|
|
|
if (self.config.showPiecesProgress or self.config.showAllStats) and self.TorrentFS.lastOpenedFile != None:
|
|
|
|
|
self.TorrentFS.lastOpenedFile.ShowPieces()
|
2016-03-04 16:15:00 +03:00
|
|
|
|
|
|
|
|
|
def consumeAlerts(self):
|
|
|
|
|
alerts = self.session.pop_alerts()
|
|
|
|
|
for alert in alerts:
|
2016-03-14 12:15:47 +03:00
|
|
|
|
if type(alert) == lt.save_resume_data_alert:
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.processSaveResumeDataAlert(alert)
|
2016-03-12 00:18:11 +03:00
|
|
|
|
break
|
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
|
2016-03-04 22:00:13 +03:00
|
|
|
|
def loop(self):
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.statsTicker = Ticker(30)
|
|
|
|
|
self.saveResumeDataTicker = Ticker(5)
|
|
|
|
|
time_start = time.time()
|
|
|
|
|
while True:
|
|
|
|
|
if self.forceShutdown:
|
|
|
|
|
return
|
|
|
|
|
if time.time() - time_start > 0.5:
|
2016-03-12 00:18:11 +03:00
|
|
|
|
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.statsTicker.true:
|
|
|
|
|
self.stats()
|
|
|
|
|
if self.saveResumeDataTicker.true:
|
|
|
|
|
self.saveResumeData(True)
|
2016-03-12 00:18:11 +03:00
|
|
|
|
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),))
|
2016-03-04 16:15:00 +03:00
|
|
|
|
data = lt.bencode(alert.resume_data)
|
|
|
|
|
try:
|
|
|
|
|
with open(self.config.resumeFile, 'wb') as f:
|
|
|
|
|
f.write(data)
|
|
|
|
|
except IOError as e:
|
|
|
|
|
strerror = e.args
|
|
|
|
|
logging.error(strerror)
|
|
|
|
|
def saveResumeData(self, async = False):
|
|
|
|
|
if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '':
|
|
|
|
|
return False
|
2016-03-14 12:15:47 +03:00
|
|
|
|
self.torrentHandle.save_resume_data(lt.save_resume_flags_t.flush_disk_cache)
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if not async:
|
|
|
|
|
alert = self.waitForAlert(lt.save_resume_data_alert, 5)
|
|
|
|
|
if alert == None:
|
|
|
|
|
return False
|
|
|
|
|
self.processSaveResumeDataAlert(alert)
|
|
|
|
|
return True
|
|
|
|
|
def saveSessionState(self):
|
|
|
|
|
if self.config.stateFile == '':
|
|
|
|
|
return
|
|
|
|
|
entry = self.session.save_state()
|
|
|
|
|
data = lt.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)
|
|
|
|
|
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_
|
|
|
|
|
def filesToRemove(self):
|
|
|
|
|
files = []
|
|
|
|
|
if self.TorrentFS.HasTorrentInfo():
|
2016-03-12 19:45:18 +03:00
|
|
|
|
for file in self.TorrentFS.files:
|
2016-03-04 16:15:00 +03:00
|
|
|
|
if (not self.config.keepComplete or not file.IsComplete()) and (not self.config.keepIncomplete or file.IsComplete()):
|
2016-03-10 19:55:25 +03:00
|
|
|
|
if os.path.exists(file.save_path):
|
|
|
|
|
files.append(file.save_path)
|
2016-03-04 22:18:52 +03:00
|
|
|
|
return files
|
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(lt.options_t.delete_files)
|
|
|
|
|
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(lt.torrent_deleted_alert, 15)
|
|
|
|
|
self.removeFiles(files)
|
|
|
|
|
def shutdown(self):
|
|
|
|
|
logging.info('Stopping pyrrent2http...')
|
2016-03-04 17:41:28 +03:00
|
|
|
|
self.forceShutdown = True
|
2016-03-04 16:15:00 +03:00
|
|
|
|
self.statsTicker.stop()
|
|
|
|
|
self.saveResumeDataTicker.stop()
|
|
|
|
|
self.httpListener.shutdown()
|
|
|
|
|
self.TorrentFS.Shutdown()
|
|
|
|
|
if self.session != None:
|
|
|
|
|
self.session.pause()
|
|
|
|
|
self.waitForAlert(lt.torrent_paused_alert, 10)
|
|
|
|
|
if self.torrentHandle is not None:
|
|
|
|
|
self.saveResumeData(False)
|
|
|
|
|
self.saveSessionState()
|
|
|
|
|
self.removeTorrent()
|
|
|
|
|
logging.info('Aborting the session')
|
|
|
|
|
del self.session
|
|
|
|
|
logging.info('Bye bye')
|