Files
headphones/headphones/utorrent.py
T
rembo10 ab4dd18be4 Initial python3 changes
Mostly just updating libraries, removing string encoding/decoding,
fixing some edge cases. No new functionality was added in this
commit.
2022-01-14 10:38:06 +05:30

276 lines
9.1 KiB
Python

# This file is part of Headphones.
#
# Headphones 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.
#
# Headphones 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 Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error
import json
import time
from collections import namedtuple
import urllib.request, urllib.error, urllib.parse
import urllib.parse
import http.cookiejar
import re
import os
import headphones
from headphones import logger
class utorrentclient(object):
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
UTSetting = namedtuple("UTSetting", ["name", "int", "str", "access"])
def __init__(self, base_url=None, username=None, password=None, ):
host = headphones.CONFIG.UTORRENT_HOST
if not host.startswith('http'):
host = 'http://' + host
if host.endswith('/'):
host = host[:-1]
if host.endswith('/gui'):
host = host[:-4]
self.base_url = host
self.username = headphones.CONFIG.UTORRENT_USERNAME
self.password = headphones.CONFIG.UTORRENT_PASSWORD
self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password)
self.token = self._get_token()
# TODO refresh token, when necessary
def _make_opener(self, realm, base_url, username, password):
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
auth = urllib.request.HTTPBasicAuthHandler()
auth.add_password(realm=realm, uri=base_url, user=username, passwd=password)
opener = urllib.request.build_opener(auth)
urllib.request.install_opener(opener)
cookie_jar = http.cookiejar.CookieJar()
cookie_handler = urllib.request.HTTPCookieProcessor(cookie_jar)
handlers = [auth, cookie_handler]
opener = urllib.request.build_opener(*handlers)
return opener
def _get_token(self):
url = urllib.parse.urljoin(self.base_url, 'gui/token.html')
try:
response = self.opener.open(url)
except urllib.error.HTTPError as err:
logger.debug('URL: ' + str(url))
logger.debug('Error getting Token. uTorrent responded with error: ' + str(err))
return
match = re.search(utorrentclient.TOKEN_REGEX, response.read())
return match.group(1)
def list(self, **kwargs):
params = [('list', '1')]
params += list(kwargs.items())
return self._action(params)
def add_url(self, url):
# can receive magnet or normal .torrent link
params = [('action', 'add-url'), ('s', url)]
return self._action(params)
def start(self, *hashes):
params = [('action', 'start'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def stop(self, *hashes):
params = [('action', 'stop'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def pause(self, *hashes):
params = [('action', 'pause'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def forcestart(self, *hashes):
params = [('action', 'forcestart'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def getfiles(self, hash):
params = [('action', 'getfiles'), ('hash', hash)]
return self._action(params)
def getprops(self, hash):
params = [('action', 'getprops'), ('hash', hash)]
return self._action(params)
def setprops(self, hash, s, v):
params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)]
return self._action(params)
def setprio(self, hash, priority, *files):
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
for file_index in files:
params.append(('f', str(file_index)))
return self._action(params)
def get_settings(self, key=None):
params = [('action', 'getsettings'), ]
status, value = self._action(params)
settings = {}
for args in value['settings']:
settings[args[0]] = self.UTSetting(*args)
if key:
return settings[key]
return settings
def remove(self, hash, remove_data=False):
if remove_data:
params = [('action', 'removedata'), ('hash', hash)]
else:
params = [('action', 'remove'), ('hash', hash)]
return self._action(params)
def _action(self, params, body=None, content_type=None):
if not self.token:
return
url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.parse.urlencode(params)
request = urllib.request.Request(url)
if body:
request.add_data(body)
request.add_header('Content-length', len(body))
if content_type:
request.add_header('Content-type', content_type)
try:
response = self.opener.open(request)
return response.code, json.loads(response.read())
except urllib.error.HTTPError as err:
logger.debug('URL: ' + str(url))
logger.debug('uTorrent webUI raised the following error: ' + str(err))
def labelTorrent(hash):
label = headphones.CONFIG.UTORRENT_LABEL
uTorrentClient = utorrentclient()
if label:
uTorrentClient.setprops(hash, 'label', label)
def removeTorrent(hash, remove_data=False):
uTorrentClient = utorrentclient()
status, torrentList = uTorrentClient.list()
torrents = torrentList['torrents']
for torrent in torrents:
if torrent[0].upper() == hash.upper():
if torrent[21] == 'Finished':
logger.info('%s has finished seeding, removing torrent and data' % torrent[2])
uTorrentClient.remove(hash, remove_data)
return True
else:
logger.info(
'%s has not finished seeding yet, torrent will not be removed, will try again on next run' %
torrent[2])
return False
return False
def setSeedRatio(hash, ratio):
uTorrentClient = utorrentclient()
uTorrentClient.setprops(hash, 'seed_override', '1')
if ratio != 0:
uTorrentClient.setprops(hash, 'seed_ratio', ratio * 10)
else:
# TODO passing -1 should be unlimited
uTorrentClient.setprops(hash, 'seed_ratio', -10)
def dirTorrent(hash, cacheid=None, return_name=None):
uTorrentClient = utorrentclient()
if not cacheid:
status, torrentList = uTorrentClient.list()
else:
params = [('list', '1'), ('cid', cacheid)]
status, torrentList = uTorrentClient._action(params)
if 'torrentp' in torrentList:
torrents = torrentList['torrentp']
else:
torrents = torrentList['torrents']
cacheid = torrentList['torrentc']
for torrent in torrents:
if torrent[0].upper() == hash.upper():
if not return_name:
return torrent[26], cacheid
else:
return torrent[2], cacheid
return None, None
def addTorrent(link):
uTorrentClient = utorrentclient()
uTorrentClient.add_url(link)
def getFolder(hash):
# Get Active Directory from settings
active_dir, completed_dir = getSettingsDirectories()
if not active_dir:
logger.error(
'Could not get "Put new downloads in:" directory from uTorrent settings, please ensure it is set')
return None
# Get Torrent Folder Name
torrent_folder, cacheid = dirTorrent(hash)
# If there's no folder yet then it's probably a magnet, try until folder is populated
if torrent_folder == active_dir or not torrent_folder:
tries = 1
while (torrent_folder == active_dir or torrent_folder is None) and tries <= 10:
tries += 1
time.sleep(6)
torrent_folder, cacheid = dirTorrent(hash, cacheid)
if torrent_folder == active_dir or not torrent_folder:
torrent_folder, cacheid = dirTorrent(hash, cacheid, return_name=True)
return torrent_folder
else:
if headphones.SYS_PLATFORM != "win32":
torrent_folder = torrent_folder.replace('\\', '/')
return os.path.basename(os.path.normpath(torrent_folder))
def getSettingsDirectories():
uTorrentClient = utorrentclient()
settings = uTorrentClient.get_settings()
active = None
completed = None
if 'dir_active_download' in settings:
active = settings['dir_active_download'][2]
if 'dir_completed_download' in settings:
completed = settings['dir_completed_download'][2]
return active, completed