plugin.video.torrenter/Libtorrent.py

405 lines
16 KiB
Python

# -*- coding: utf-8 -*-
'''
Torrenter plugin for XBMC
Copyright (C) 2012 Vadim Skorba
vadim.skorba@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
#import time
import thread
import os
import urllib2
import hashlib
import re
import sys
import platform
from StringIO import StringIO
import gzip
from functions import file_decode, file_encode, isSubtitle, DownloadDB
import xbmc
import xbmcgui
import xbmcvfs
import Localization
class Libtorrent:
torrentFile = None
magnetLink = None
storageDirectory = ''
startPart = 0
endPart = 0
partOffset = 0
torrentHandle = None
session = None
downloadThread = None
threadComplete = False
lt = None
def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'):
try:
import libtorrent
print 'Imported libtorrent v' + libtorrent.version + ' from system'
except Exception, e:
print 'Error importing from system. Exception: ' + str(e)
if platform.system() != 'Windows':
if sys.maxsize > 2 ** 32:
system = 'linux_x86_64'
else:
system = 'linux_x86'
else:
system = 'windows'
dirname = os.path.join(xbmc.translatePath('special://home'), 'addons', 'script.module.libtorrent',
'python_libtorrent', system)
sys.path.insert(0, dirname)
try:
import libtorrent
print 'Imported libtorrent v' + libtorrent.version + ' from python_libtorrent.' + system
except Exception, e:
print 'Error importing python_libtorrent.' + system + '. Exception: ' + str(e)
pass
self.lt = libtorrent
del libtorrent
self.storageDirectory = storageDirectory
self.torrentFilesPath=os.path.join(self.storageDirectory, torrentFilesDirectory)+os.sep
if xbmcvfs.exists(torrentFile):
self.torrentFile = torrentFile
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
elif re.match("^magnet\:.+$", torrentFile):
self.magnetLink = torrentFile
def saveTorrent(self, torrentUrl):
if re.match("^magnet\:.+$", torrentUrl):
self.magnetLink = torrentUrl
self.magnetToTorrent(torrentUrl)
self.magnetLink=None
return self.torrentFile
else:
if not xbmcvfs.exists(self.torrentFilesPath):
xbmcvfs.mkdirs(self.torrentFilesPath)
torrentFile = self.torrentFilesPath + self.md5(
torrentUrl) + '.torrent'
try:
if not re.match("^http\:.+$", torrentUrl):
content = xbmcvfs.File(file_decode(torrentUrl), "rb").read()
else:
request = urllib2.Request(torrentUrl)
request.add_header('Referer', torrentUrl)
request.add_header('Accept-encoding', 'gzip')
result = urllib2.urlopen(request)
if result.info().get('Content-Encoding') == 'gzip':
buf = StringIO(result.read())
f = gzip.GzipFile(fileobj=buf)
content = f.read()
else:
content = result.read()
localFile = xbmcvfs.File(torrentFile, "w+b")
localFile.write(content)
localFile.close()
except Exception, e:
print 'Unable to save torrent file from "' + torrentUrl + '" to "' + torrentFile + '" in Torrent::saveTorrent' + '. Exception: ' + str(e)
return
if xbmcvfs.exists(torrentFile):
try:
self.torrentFileInfo = self.lt.torrent_info(file_decode(torrentFile))
except Exception, e:
print 'Exception: ' + str(e)
xbmcvfs.delete(torrentFile)
return
baseName = file_encode(os.path.basename(self.getFilePath()))
if not xbmcvfs.exists(self.torrentFilesPath):
xbmcvfs.mkdirs(self.torrentFilesPath)
newFile = self.torrentFilesPath + baseName + '.' + self.md5(torrentUrl) + '.torrent'
xbmcvfs.delete(newFile)
if not xbmcvfs.exists(newFile):
try:
xbmcvfs.rename(torrentFile, newFile)
except Exception, e:
print 'Unable to rename torrent file from "' + torrentFile + '" to "' + newFile + '" in Torrent::renameTorrent'+ '. Exception: ' + str(e)
return
self.torrentFile = newFile
if not self.torrentFileInfo:
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
return self.torrentFile
def getMagnetInfo(self):
magnetSettings = {
'save_path': self.storageDirectory,
'storage_mode': self.lt.storage_mode_t(0),
'paused': True,
'auto_managed': True,
'duplicate_is_error': True
}
progressBar = xbmcgui.DialogProgress()
progressBar.create(Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting.'))
self.torrentHandle = self.lt.add_magnet_uri(self.session, self.magnetLink, magnetSettings)
iterator = 0
while not self.torrentHandle.has_metadata() and iterator != 100:
progressBar.update(iterator)
iterator += 1
if progressBar.iscanceled():
progressBar.update(0)
progressBar.close()
return
xbmc.sleep(500)
progressBar.update(0)
progressBar.close()
if self.torrentHandle.has_metadata():
return self.torrentHandle.get_torrent_info()
def magnetToTorrent(self, magnet):
self.magnetLink = magnet
self.initSession()
torrentInfo = self.getMagnetInfo()
try:
torrentFile = self.lt.create_torrent(torrentInfo)
baseName = file_encode(os.path.basename(self.storageDirectory + os.sep + torrentInfo.files()[0].path))
if not xbmcvfs.exists(self.torrentFilesPath):
xbmcvfs.mkdirs(self.torrentFilesPath)
self.torrentFile = self.torrentFilesPath + baseName + '.torrent'
torentFileHandler = xbmcvfs.File(self.torrentFile, "w+b")
torentFileHandler.write(self.lt.bencode(torrentFile.generate()))
torentFileHandler.close()
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
except:
xbmc.executebuiltin("Notification(%s, %s, 7500)" % (Localization.localize('Error'), Localization.localize(
'Can\'t download torrent, probably no seeds available.')))
self.torrentFileInfo = torrentInfo
def getUploadRate(self):
if None == self.torrentHandle:
return 0
else:
return self.torrentHandle.status().upload_payload_rate
def getDownloadRate(self):
if None == self.torrentHandle:
return 0
else:
return self.torrentHandle.status().download_payload_rate
def getPeers(self):
if None == self.torrentHandle:
return 0
else:
return self.torrentHandle.status().num_peers
def getSeeds(self):
if None == self.torrentHandle:
return 0
else:
return self.torrentHandle.status().num_seeds
def getFileSize(self, contentId=0):
return self.getContentList()[contentId]['size']
def getFilePath(self, contentId=0):
return os.path.join(self.storageDirectory,self.getContentList()[contentId]['title'])#.decode('utf8')
def getContentList(self):
filelist = []
for contentId, contentFile in enumerate(self.torrentFileInfo.files()):
stringdata = {"title": contentFile.path, "size": contentFile.size, "ind": int(contentId),
'offset': contentFile.offset}
filelist.append(stringdata)
return filelist
def getSubsIds(self, filename):
subs=[]
for i in self.getContentList():
if isSubtitle(filename, i['title']):
subs.append((i['ind'], i['title']))
return subs
def setUploadLimit(self, bytesPerSecond):
self.session.set_upload_rate_limit(int(bytesPerSecond))
def setDownloadLimit(self, bytesPerSecond):
self.session.set_download_rate_limit(int(bytesPerSecond))
def md5(self, string):
hasher = hashlib.md5()
try:
hasher.update(string)
except:
hasher.update(string.encode('utf-8', 'ignore'))
return hasher.hexdigest()
def downloadProcess(self, contentId):
self.startSession()
self.paused=False
db=DownloadDB()
ContentList=self.getContentList()
if contentId!=None: contentId=int(contentId)
if len(ContentList)==1 or contentId not in [None, -1]:
if not contentId: contentId=0
title=os.path.basename(ContentList[contentId]['title'])
path=os.path.join(self.storageDirectory, ContentList[contentId]['title'])
type='file'
else:
contentId=-1
title=ContentList[0]['title'].split('\\')[0]
path=os.path.join(self.storageDirectory, title)
type='folder'
add=db.add(title, path, type, {'progress':0}, 'downloading', self.torrentFile, contentId, self.storageDirectory)
get=db.get(title)
if add or get[5]=='stopped':
if get[5]=='stopped':
db.update_status(get[0], 'downloading')
if contentId not in [None, -1]:
self.continueSession(int(contentId), Offset=0, seeding=False)
else:
for i in range(self.torrentFileInfo.num_pieces()):
self.torrentHandle.piece_priority(i, 6)
thread.start_new_thread(self.downloadLoop, (title,))
def downloadLoop(self, title):
db=DownloadDB()
status='downloading'
while db.get(title) and status!='stopped':
xbmc.sleep(3000)
status=db.get_status(title)
if not self.paused:
if status=='pause':
self.paused=True
self.session.pause()
else:
if status!='pause':
self.paused=False
self.session.resume()
s = self.torrentHandle.status()
info={}
info['upload']=s.upload_payload_rate
info['download']=s.download_payload_rate
info['peers']=s.num_peers
info['seeds']=s.num_seeds
iterator = int(s.progress * 100)
info['progress']=iterator
db.update(title, info)
self.debug()
self.session.remove_torrent(self.torrentHandle)
return
def initSession(self):
try:
self.session.remove_torrent(self.torrentHandle)
except:
pass
self.session = self.lt.session()
self.session.start_dht()
self.session.add_dht_router("router.bittorrent.com", 6881)
self.session.add_dht_router("router.utorrent.com", 6881)
self.session.add_dht_router("router.bitcomet.com", 6881)
self.session.listen_on(6881, 6891)
self.session.set_alert_mask(self.lt.alert.category_t.storage_notification)
def startSession(self):
self.initSession()
if None == self.magnetLink:
self.torrentHandle = self.session.add_torrent({'ti': self.torrentFileInfo,
'save_path': self.storageDirectory,
'flags': 0x300,
#'storage_mode': self.lt.storage_mode_t.storage_mode_allocate,
})
else:
self.torrentFileInfo = self.getMagnetInfo()
self.torrentHandle.set_sequential_download(True)
self.stopSession()
def stopSession(self):
for i in range(self.torrentFileInfo.num_pieces()):
self.torrentHandle.piece_priority(i, 0)
def continueSession(self, contentId=0, Offset=0, seeding=False):
self.piece_length = self.torrentFileInfo.piece_length()
selectedFileInfo = self.getContentList()[contentId]
if not Offset:
Offset = selectedFileInfo['size'] / (1024 * 1024)
self.partOffset = (Offset * 1024 * 1024 / self.piece_length) + 1
#print 'partOffset ' + str(self.partOffset)+str(' ')
self.startPart = selectedFileInfo['offset'] / self.piece_length
self.endPart = int((selectedFileInfo['offset'] + selectedFileInfo['size']) / self.piece_length)
#print 'part ' + str(self.startPart)+ str(' ')+ str(self.endPart)
for i in range(self.startPart, self.startPart + self.partOffset):
if i <= self.endPart:
self.torrentHandle.piece_priority(i, 7)
#print str(i)
self.torrentHandle.piece_priority(self.endPart - 1, 7)
self.torrentHandle.piece_priority(self.endPart, 7)
#thread.start_new_thread(self.checkProcess, ())
#thread.start_new_thread(self.downloadProcess, (contentId,))
#if seeding:# and None == self.magnetLink:
# thread.start_new_thread(self.addToSeeding, (contentId,))
def fetchParts(self):
priorities = self.torrentHandle.piece_priorities()
status = self.torrentHandle.status()
if len(status.pieces) == 0:
return
if priorities[self.startPart] == 0:
self.torrentHandle.piece_priority(self.startPart, 2)
for part in range(self.startPart, self.endPart + 1):
if priorities[part] == 0:
self.torrentHandle.piece_priority(part, 1)
def checkThread(self):
if self.threadComplete == True:
print 'checkThread KIIIIIIIIIIILLLLLLLLLLLLLLL'
self.session.remove_torrent(self.torrentHandle)
def debug(self):
try:
#print str(self.getFilePath(0))
s = self.torrentHandle.status()
#get_settings=self.torrentHandle.status
#print s.num_pieces
#priorities = self.torrentHandle.piece_priorities()
#self.dump(priorities)
#print str('anonymous_mode '+str(get_settings['anonymous_mode']))
state_str = ['queued', 'checking', 'downloading metadata',
'downloading', 'finished', 'seeding', 'allocating']
print '[%s] %.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \
(self.lt.version, s.progress * 100, s.download_rate / 1000,
s.upload_rate / 1000,
s.num_peers, state_str[s.state])
i = 0
#for t in s.pieces:
# if t: i=i+1
#print str(self.session.pop_alert())
#print str(s.pieces[self.startPart:self.endPart])
#print 'True pieces: %d' % i
#print s.current_tracker
#print str(s.pieces)
except:
print 'debug error'
pass
def dump(self, obj):
for attr in dir(obj):
try:
print "'%s':'%s'," % (attr, getattr(obj, attr))
except:
pass