Compare commits

...
Author SHA1 Message Date
rembo10 c1edc9cde0 gh-workflow: rename, run on pull request, change python version 2022-01-23 14:34:10 +05:30
rembo10 ce98d0d6ca Ignore line length in flake8 2022-01-23 14:27:11 +05:30
rembo10 1bd7cc2ffd Whitespace fixes 2022-01-23 14:27:05 +05:30
rembo10 ad858576aa Add .flake8 configuration 2022-01-23 14:25:26 +05:30
rembo10 455b7d4940 Remove pylintrc 2022-01-23 14:25:26 +05:30
rembo10 cd14c3f4e2 travis -> github-actions 2022-01-23 14:25:26 +05:30
28 changed files with 181 additions and 431 deletions
+3
View File
@@ -0,0 +1,3 @@
[flake8]
exclude = .git,data,init-scripts,lib
ignore = E501
+29
View File
@@ -0,0 +1,29 @@
name: check
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 .
- name: Test with nosetests
run: |
nosetests
-25
View File
@@ -1,25 +0,0 @@
# Travis CI configuration file
# http://about.travis-ci.org/docs/
language: python
sudo: false
cache:
pip: true
directories:
- lib
python:
- "2.7"
install:
- pip install -r requirements-dev.txt
script:
- pep8 headphones
- pyflakes headphones
- nosetests
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then coveralls; fi
+6 -2
View File
@@ -474,8 +474,12 @@ class Api(object):
# Handle situations where the torrent url contains arguments that are # Handle situations where the torrent url contains arguments that are
# parsed # parsed
if kwargs: if kwargs:
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
url = urllib.parse.quote( url = urllib.parse.quote(
url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs) url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs)
+4 -4
View File
@@ -388,9 +388,9 @@ class Cache(object):
else: else:
if dbalbum['Type'] != "part of": if dbalbum['Type'] != "part of":
data = lastfm.request_lastfm("album.getinfo", data = lastfm.request_lastfm("album.getinfo",
artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']),
album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']),
api_key=LASTFM_API_KEY) api_key=LASTFM_API_KEY)
else: else:
# Series, use actual artist for the release-group # Series, use actual artist for the release-group
@@ -484,7 +484,7 @@ class Cache(object):
self.id + '_fanart_' + '.' + helpers.today() + ext) self.id + '_fanart_' + '.' + helpers.today() + ext)
else: else:
artwork_path = os.path.join(self.path_to_art_cache, artwork_path = os.path.join(self.path_to_art_cache,
self.id + '.' + helpers.today() + ext) self.id + '.' + helpers.today() + ext)
try: try:
with open(artwork_path, 'wb') as f: with open(artwork_path, 'wb') as f:
f.write(artwork) f.write(artwork)
+3 -1
View File
@@ -18,7 +18,9 @@
####################################### #######################################
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
from .common import USER_AGENT from .common import USER_AGENT
+1 -1
View File
@@ -77,7 +77,7 @@ class Quality:
toReturn = {} toReturn = {}
for x in list(Quality.qualityStrings.keys()): for x in list(Quality.qualityStrings.keys()):
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + \ toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + \
Quality.qualityStrings[x] + ")" Quality.qualityStrings[x] + ")"
return toReturn return toReturn
@staticmethod @staticmethod
+5 -4
View File
@@ -31,6 +31,7 @@ class path(str):
def __repr__(self): def __repr__(self):
return 'headphones.config.path(%s)' % self return 'headphones.config.path(%s)' % self
_CONFIG_DEFINITIONS = { _CONFIG_DEFINITIONS = {
'ADD_ALBUM_ART': (int, 'General', 0), 'ADD_ALBUM_ART': (int, 'General', 0),
'ADVANCEDENCODER': (str, 'General', ''), 'ADVANCEDENCODER': (str, 'General', ''),
@@ -365,7 +366,7 @@ class Config(object):
my_val = definition_type(self._config[section][ini_key]) my_val = definition_type(self._config[section][ini_key])
# ConfigParser interprets empty strings in the config # ConfigParser interprets empty strings in the config
# literally, so we need to sanitize it. It's not really # literally, so we need to sanitize it. It's not really
# a config upgrade, since a user can at any time put # a config upgrade, since a user can at any time put
# some_key = '' # some_key = ''
if my_val == '""' or my_val == "''": if my_val == '""' or my_val == "''":
my_val = '' my_val = ''
@@ -407,7 +408,7 @@ class Config(object):
""" Return the extra newznab tuples """ """ Return the extra newznab tuples """
extra_newznabs = list( extra_newznabs = list(
zip(*[itertools.islice(self.EXTRA_NEWZNABS, i, None, 3) zip(*[itertools.islice(self.EXTRA_NEWZNABS, i, None, 3)
for i in range(3)]) for i in range(3)])
) )
return extra_newznabs return extra_newznabs
@@ -426,7 +427,7 @@ class Config(object):
""" Return the extra torznab tuples """ """ Return the extra torznab tuples """
extra_torznabs = list( extra_torznabs = list(
zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 4) zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 4)
for i in range(4)]) for i in range(4)])
) )
return extra_torznabs return extra_torznabs
@@ -503,7 +504,7 @@ class Config(object):
if self.EXTRA_TORZNABS: if self.EXTRA_TORZNABS:
extra_torznabs = list( extra_torznabs = list(
zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 3) zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 3)
for i in range(3)]) for i in range(3)])
) )
new_torznabs = [] new_torznabs = []
for torznab in extra_torznabs: for torznab in extra_torznabs:
-1
View File
@@ -18,7 +18,6 @@
################################### ###################################
import time import time
import sqlite3 import sqlite3
+29 -30
View File
@@ -35,7 +35,6 @@
# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # along with SickRage. If not, see <http://www.gnu.org/licenses/>.
from headphones import logger from headphones import logger
import time import time
@@ -89,7 +88,7 @@ def addTorrent(link, data=None, name=None):
if link.lower().startswith('magnet:'): if link.lower().startswith('magnet:'):
logger.debug('Deluge: Got a magnet link: %s' % _scrubber(link)) logger.debug('Deluge: Got a magnet link: %s' % _scrubber(link))
result = {'type': 'magnet', result = {'type': 'magnet',
'url': link} 'url': link}
retid = _add_torrent_magnet(result) retid = _add_torrent_magnet(result)
elif link.lower().startswith('http://') or link.lower().startswith('https://'): elif link.lower().startswith('http://') or link.lower().startswith('https://'):
@@ -143,8 +142,8 @@ def addTorrent(link, data=None, name=None):
except: except:
logger.debug('Deluge: Sending Deluge torrent with problematic name and some content') logger.debug('Deluge: Sending Deluge torrent with problematic name and some content')
result = {'type': 'torrent', result = {'type': 'torrent',
'name': name, 'name': name,
'content': torrentfile} 'content': torrentfile}
retid = _add_torrent_file(result) retid = _add_torrent_file(result)
# elif link.endswith('.torrent') or data: # elif link.endswith('.torrent') or data:
@@ -175,8 +174,8 @@ def addTorrent(link, data=None, name=None):
except UnicodeDecodeError: except UnicodeDecodeError:
logger.debug('Deluge: Sending Deluge torrent with name %s and content [%s...]' % (name.decode('utf-8'), str(torrentfile)[:40])) logger.debug('Deluge: Sending Deluge torrent with name %s and content [%s...]' % (name.decode('utf-8'), str(torrentfile)[:40]))
result = {'type': 'torrent', result = {'type': 'torrent',
'name': name, 'name': name,
'content': torrentfile} 'content': torrentfile}
retid = _add_torrent_file(result) retid = _add_torrent_file(result)
else: else:
@@ -208,7 +207,7 @@ def getTorrentFolder(result):
], ],
"id": 21}) "id": 21})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['total_done'] = json.loads(response.text)['result']['total_done'] result['total_done'] = json.loads(response.text)['result']['total_done']
tries = 0 tries = 0
@@ -216,7 +215,7 @@ def getTorrentFolder(result):
tries += 1 tries += 1
time.sleep(5) time.sleep(5)
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['total_done'] = json.loads(response.text)['result']['total_done'] result['total_done'] = json.loads(response.text)['result']['total_done']
post_data = json.dumps({"method": "web.get_torrent_status", post_data = json.dumps({"method": "web.get_torrent_status",
@@ -235,7 +234,7 @@ def getTorrentFolder(result):
"id": 23}) "id": 23})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['save_path'] = json.loads(response.text)['result']['save_path'] result['save_path'] = json.loads(response.text)['result']['save_path']
result['name'] = json.loads(response.text)['result']['name'] result['name'] = json.loads(response.text)['result']['name']
@@ -264,7 +263,7 @@ def removeTorrent(torrentid, remove_data=False):
"id": 26}) "id": 26})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
try: try:
state = json.loads(response.text)['result']['state'] state = json.loads(response.text)['result']['state']
@@ -283,10 +282,10 @@ def removeTorrent(torrentid, remove_data=False):
"params": [ "params": [
torrentid, torrentid,
remove_data remove_data
], ],
"id": 25}) "id": 25})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result = json.loads(response.text)['result'] result = json.loads(response.text)['result']
return result return result
@@ -329,12 +328,12 @@ def _get_auth():
"id": 1}) "id": 1})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except requests.ConnectionError: except requests.ConnectionError:
try: try:
logger.debug('Deluge: Connection failed, let\'s try HTTPS just in case') logger.debug('Deluge: Connection failed, let\'s try HTTPS just in case')
response = requests.post(delugeweb_url.replace('http:', 'https:'), data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url.replace('http:', 'https:'), data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
# If the previous line didn't fail, change delugeweb_url for the rest of this session # If the previous line didn't fail, change delugeweb_url for the rest of this session
logger.error('Deluge: Switching to HTTPS, but certificate won\'t be verified because NO CERTIFICATE WAS CONFIGURED!') logger.error('Deluge: Switching to HTTPS, but certificate won\'t be verified because NO CERTIFICATE WAS CONFIGURED!')
delugeweb_url = delugeweb_url.replace('http:', 'https:') delugeweb_url = delugeweb_url.replace('http:', 'https:')
@@ -359,7 +358,7 @@ def _get_auth():
"id": 10}) "id": 10})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -376,7 +375,7 @@ def _get_auth():
"id": 11}) "id": 11})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -395,7 +394,7 @@ def _get_auth():
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -408,7 +407,7 @@ def _get_auth():
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -433,7 +432,7 @@ def _add_torrent_magnet(result):
"params": [result['url'], {}], "params": [result['url'], {}],
"id": 2}) "id": 2})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['hash'] = json.loads(response.text)['result'] result['hash'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
@@ -453,7 +452,7 @@ def _add_torrent_url(result):
"params": [result['url'], {}], "params": [result['url'], {}],
"id": 32}) "id": 32})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['location'] = json.loads(response.text)['result'] result['location'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
@@ -472,10 +471,10 @@ def _add_torrent_file(result):
# content is torrent file contents that needs to be encoded to base64 # content is torrent file contents that needs to be encoded to base64
post_data = json.dumps({"method": "core.add_torrent_file", post_data = json.dumps({"method": "core.add_torrent_file",
"params": [result['name'] + '.torrent', "params": [result['name'] + '.torrent',
b64encode(result['content']).decode(), {}], b64encode(result['content']).decode(), {}],
"id": 2}) "id": 2})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['hash'] = json.loads(response.text)['result'] result['hash'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
@@ -502,7 +501,7 @@ def setTorrentLabel(result):
"params": [], "params": [],
"id": 3}) "id": 3})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
labels = json.loads(response.text)['result'] labels = json.loads(response.text)['result']
if labels is not None: if labels is not None:
@@ -513,7 +512,7 @@ def setTorrentLabel(result):
"params": [label], "params": [label],
"id": 4}) "id": 4})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
logger.debug('Deluge: %s label added to Deluge' % label) logger.debug('Deluge: %s label added to Deluge' % label)
except Exception as e: except Exception as e:
logger.error('Deluge: Setting label failed: %s' % str(e)) logger.error('Deluge: Setting label failed: %s' % str(e))
@@ -525,7 +524,7 @@ def setTorrentLabel(result):
"params": [result['hash'], label], "params": [result['hash'], label],
"id": 5}) "id": 5})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
logger.debug('Deluge: %s label added to torrent' % label) logger.debug('Deluge: %s label added to torrent' % label)
else: else:
logger.debug('Deluge: Label plugin not detected') logger.debug('Deluge: Label plugin not detected')
@@ -549,12 +548,12 @@ def setSeedRatio(result):
"params": [result['hash'], True], "params": [result['hash'], True],
"id": 5}) "id": 5})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
post_data = json.dumps({"method": "core.set_torrent_stop_ratio", post_data = json.dumps({"method": "core.set_torrent_stop_ratio",
"params": [result['hash'], float(ratio)], "params": [result['hash'], float(ratio)],
"id": 6}) "id": 6})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
@@ -577,7 +576,7 @@ def setTorrentPath(result):
"params": [result['hash'], True], "params": [result['hash'], True],
"id": 7}) "id": 7})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
if headphones.CONFIG.DELUGE_DONE_DIRECTORY: if headphones.CONFIG.DELUGE_DONE_DIRECTORY:
move_to = headphones.CONFIG.DELUGE_DONE_DIRECTORY move_to = headphones.CONFIG.DELUGE_DONE_DIRECTORY
@@ -591,7 +590,7 @@ def setTorrentPath(result):
"params": [result['hash'], move_to], "params": [result['hash'], move_to],
"id": 8}) "id": 8})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
@@ -614,7 +613,7 @@ def setTorrentPause(result):
"params": [[result['hash']]], "params": [[result['hash']]],
"id": 9}) "id": 9})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
+5 -3
View File
@@ -42,6 +42,7 @@ RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I) RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I) RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
def cmp(x, y): def cmp(x, y):
""" """
Replacement for built-in function cmp that was removed in Python 3 Replacement for built-in function cmp that was removed in Python 3
@@ -54,6 +55,7 @@ def cmp(x, y):
""" """
return (x > y) - (x < y) return (x > y) - (x < y)
def multikeysort(items, columns): def multikeysort(items, columns):
comparers = [ comparers = [
((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1))
@@ -232,7 +234,7 @@ def pattern_substitute(pattern, dic, normalize=False):
j = unicodedata.normalize('NFC', j) j = unicodedata.normalize('NFC', j)
except TypeError: except TypeError:
j = unicodedata.normalize('NFC', j = unicodedata.normalize('NFC',
j.decode(headphones.SYS_ENCODING, 'replace')) j.decode(headphones.SYS_ENCODING, 'replace'))
new_dic[i] = j new_dic[i] = j
dic = new_dic dic = new_dic
return pathrender.render(pattern, dic)[0] return pathrender.render(pattern, dic)[0]
@@ -277,7 +279,7 @@ _XLATE_GRAPHICAL_AND_DIACRITICAL = {
'Ǥ': 'G', 'ǥ': 'g', 'DZ': 'DZ', 'Dz': 'Dz', 'dz': 'dz', 'Ǥ': 'G', 'ǥ': 'g', 'DZ': 'DZ', 'Dz': 'Dz', 'dz': 'dz',
'Ȥ': 'Z', 'ȥ': 'z', '': 'No.', 'Ȥ': 'Z', 'ȥ': 'z', '': 'No.',
'º': 'o.', # normalize Nº abbrev (popular w/ classical music), 'º': 'o.', # normalize Nº abbrev (popular w/ classical music),
# this is 'masculine ordering indicator', not degree # this is 'masculine ordering indicator', not degree
} }
_XLATE_SPECIAL = { _XLATE_SPECIAL = {
@@ -882,7 +884,7 @@ def smartMove(src, dest, delete=True):
shutil.copy(source_path, dest_path) shutil.copy(source_path, dest_path)
return True return True
except Exception as e: except Exception as e:
logger.warn(f"Error copying {filename}: {e}") logger.warn(f"Error copying {filename}: {e}")
def walk_directory(basedir, followlinks=True): def walk_directory(basedir, followlinks=True):
+2 -2
View File
@@ -14,9 +14,9 @@ class HelpersTest(TestCase):
'Symphonęy Nº9': 'Symphoney No.9', 'Symphonęy Nº9': 'Symphoney No.9',
'ÆæßðÞIJij': 'AeaessdThIJıj', 'ÆæßðÞIJij': 'AeaessdThIJıj',
'Obsessió (Cerebral Apoplexy remix)': 'obsessio cerebral ' 'Obsessió (Cerebral Apoplexy remix)': 'obsessio cerebral '
'apoplexy remix', 'apoplexy remix',
'Doktór Hałabała i siedmiu zbojów': 'doktor halabala i siedmiu ' 'Doktór Hałabała i siedmiu zbojów': 'doktor halabala i siedmiu '
'zbojow', 'zbojow',
'Arbetets Söner och Döttrar': 'arbetets soner och dottrar', 'Arbetets Söner och Döttrar': 'arbetets soner och dottrar',
'Björk Guðmundsdóttir': 'bjork gudmundsdottir', 'Björk Guðmundsdóttir': 'bjork gudmundsdottir',
'L\'Arc~en~Ciel': 'larc en ciel', 'L\'Arc~en~Ciel': 'larc en ciel',
+1 -1
View File
@@ -39,7 +39,7 @@ def is_exists(artistid):
if any(artistid in x for x in artistlist): if any(artistid in x for x in artistlist):
logger.info(artistlist[0][ logger.info(artistlist[0][
1] + " is already in the database. Updating 'have tracks', but not artist information") 1] + " is already in the database. Updating 'have tracks', but not artist information")
return True return True
else: else:
return False return False
+11 -14
View File
@@ -152,7 +152,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
# track_list.append(track_dict) # track_list.append(track_dict)
check_exist_track = myDB.action("SELECT * FROM have WHERE Location=?", check_exist_track = myDB.action("SELECT * FROM have WHERE Location=?",
[track_path]).fetchone() [track_path]).fetchone()
# Only attempt to match tracks that are new, haven't yet been matched, or metadata has changed. # Only attempt to match tracks that are new, haven't yet been matched, or metadata has changed.
if not check_exist_track: if not check_exist_track:
# This is a new track # This is a new track
@@ -167,7 +167,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
if f_artist and f_artist != check_exist_track['ArtistName']: if f_artist and f_artist != check_exist_track['ArtistName']:
new_artists.append(f_artist) new_artists.append(f_artist)
elif f_artist and f_artist == check_exist_track['ArtistName'] and \ elif f_artist and f_artist == check_exist_track['ArtistName'] and \
check_exist_track['Matched'] != "Ignored": check_exist_track['Matched'] != "Ignored":
new_artists.append(f_artist) new_artists.append(f_artist)
else: else:
continue continue
@@ -191,26 +191,23 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
# Now we start track matching # Now we start track matching
logger.info(f"{new_track_count} new/modified tracks found and added to the database") logger.info(f"{new_track_count} new/modified tracks found and added to the database")
dbtracks = myDB.action( dbtracks = myDB.action(
"SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", "SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
[f"{dir}%"] [f"{dir}%"]
) )
dbtracks_count = myDB.action( dbtracks_count = myDB.action(
"SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", "SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
[f"{dir}%"] [f"{dir}%"]
).fetchone()[0] ).fetchone()[0]
logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`") logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`")
logger.info("Matching tracks to the appropriate releases....") logger.info("Matching tracks to the appropriate releases....")
# Sort the track_list by most vague (e.g. no trackid or releaseid) # Sort the track_list by most vague (e.g. no trackid or releaseid)
# to most specific (both trackid & releaseid) # to most specific (both trackid & releaseid)
# When we insert into the database, the tracks with the most # When we insert into the database, the tracks with the most
# specific information will overwrite the more general matches # specific information will overwrite the more general matches
sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle']) sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle'])
# We'll use this to give a % completion, just because the # We'll use this to give a % completion, just because the
# track matching might take a while # track matching might take a while
tracks_completed = 0 tracks_completed = 0
@@ -227,8 +224,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
tracks_completed += 1 tracks_completed += 1
completion_percentage = math.floor( completion_percentage = math.floor(
float(tracks_completed) / dbtracks_count * 1000 float(tracks_completed) / dbtracks_count * 1000
) / 10 ) / 10
if completion_percentage >= (last_completion_percentage + 10): if completion_percentage >= (last_completion_percentage + 10):
logger.info("Track matching is " + str(completion_percentage) + "% complete") logger.info("Track matching is " + str(completion_percentage) + "% complete")
+1
View File
@@ -38,6 +38,7 @@ class MetadataDict(dict):
lowercase) in member variable self._lower. If case-sensitive lookup lowercase) in member variable self._lower. If case-sensitive lookup
fails, another case-insensitive attempt is made. fails, another case-insensitive attempt is made.
""" """
def __setitem__(self, key, value): def __setitem__(self, key, value):
super(MetadataDict, self).__setitem__(key, value) super(MetadataDict, self).__setitem__(key, value)
self._lower.__setitem__(key.lower(), value) self._lower.__setitem__(key.lower(), value)
+19 -15
View File
@@ -1,5 +1,7 @@
from urllib.parse import urlencode, quote_plus from urllib.parse import urlencode, quote_plus
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import subprocess import subprocess
import json import json
from email.mime.text import MIMEText from email.mime.text import MIMEText
@@ -7,7 +9,9 @@ import smtplib
import email.utils import email.utils
from http.client import HTTPSConnection from http.client import HTTPSConnection
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import requests as requests import requests as requests
import os.path import os.path
@@ -17,7 +21,7 @@ import cherrypy
import headphones import headphones
import gntp.notifier import gntp.notifier
#import oauth2 as oauth #import oauth2 as oauth
import twitter import twitter
class GROWL(object): class GROWL(object):
@@ -246,7 +250,7 @@ class XBMC(object):
if version < 12: # Eden if version < 12: # Eden
notification = header + "," + message + "," + time + \ notification = header + "," + message + "," + time + \
"," + albumartpath "," + albumartpath
notifycommand = {'command': 'ExecBuiltIn', notifycommand = {'command': 'ExecBuiltIn',
'parameter': 'Notification(' + 'parameter': 'Notification(' +
notification + ')'} notification + ')'}
@@ -440,7 +444,7 @@ class Plex(object):
if version < 12: # Eden if version < 12: # Eden
notification = header + "," + message + "," + time + \ notification = header + "," + message + "," + time + \
"," + albumartpath "," + albumartpath
notifycommand = {'command': 'ExecBuiltIn', notifycommand = {'command': 'ExecBuiltIn',
'parameter': 'Notification(' + 'parameter': 'Notification(' +
notification + ')'} notification + ')'}
@@ -604,12 +608,12 @@ class JOIN(object):
self.url += '&deviceId={deviceid}' self.url += '&deviceId={deviceid}'
response = urllib.request.urlopen(self.url.format(apikey=self.apikey, response = urllib.request.urlopen(self.url.format(apikey=self.apikey,
title=quote_plus(event), title=quote_plus(event),
text=quote_plus( text=quote_plus(
message.encode( message.encode(
"utf-8")), "utf-8")),
icon=icon, icon=icon,
deviceid=self.deviceid)) deviceid=self.deviceid))
if response: if response:
logger.info("Join notifications sent.") logger.info("Join notifications sent.")
@@ -733,8 +737,8 @@ class TwitterNotifier(object):
def notify_download(self, title): def notify_download(self, title):
if headphones.CONFIG.TWITTER_ENABLED: if headphones.CONFIG.TWITTER_ENABLED:
self._notifyTwitter(common.notifyStrings[ self._notifyTwitter(common.notifyStrings[
common.NOTIFY_DOWNLOAD] + ': ' + common.NOTIFY_DOWNLOAD] + ': ' +
title + ' at ' + helpers.now()) title + ' at ' + helpers.now())
def test_notify(self): def test_notify(self):
return self._notifyTwitter( return self._notifyTwitter(
@@ -798,7 +802,7 @@ class TwitterNotifier(object):
if resp['status'] != '200': if resp['status'] != '200':
logger.info('The request for a token with did not succeed: ' + str( logger.info('The request for a token with did not succeed: ' + str(
resp['status']), resp['status']),
logger.ERROR) logger.ERROR)
return False return False
else: else:
logger.info('Your Twitter Access Token key: %s' % access_token[ logger.info('Your Twitter Access Token key: %s' % access_token[
@@ -1020,7 +1024,7 @@ class TELEGRAM(object):
# MusicBrainz link # MusicBrainz link
if rgid: if rgid:
message += '\n\n <a href="https://musicbrainz.org/' \ message += '\n\n <a href="https://musicbrainz.org/' \
'release-group/%s">MusicBrainz</a>' % rgid 'release-group/%s">MusicBrainz</a>' % rgid
# Send image # Send image
response = None response = None
+3
View File
@@ -38,6 +38,7 @@ __author__ = "Andrzej Ciarkowski <andrzej.ciarkowski@gmail.com>"
class _PatternElement(object): class _PatternElement(object):
'''ABC for hierarchy of path name renderer pattern elements.''' '''ABC for hierarchy of path name renderer pattern elements.'''
def render(self, replacement): def render(self, replacement):
# type: (Mapping[str,str]) -> str # type: (Mapping[str,str]) -> str
'''Format this _PatternElement into string using provided substitution dictionary.''' '''Format this _PatternElement into string using provided substitution dictionary.'''
@@ -55,6 +56,7 @@ class _Generator(_PatternElement):
class _Replacement(_Generator): class _Replacement(_Generator):
'''Replacement variable, eg. $title.''' '''Replacement variable, eg. $title.'''
def __init__(self, pattern): def __init__(self, pattern):
# type: (str) # type: (str)
self._pattern = pattern self._pattern = pattern
@@ -81,6 +83,7 @@ class _Replacement(_Generator):
class _LiteralText(_PatternElement): class _LiteralText(_PatternElement):
'''Just a plain piece of text to be rendered "as is".''' '''Just a plain piece of text to be rendered "as is".'''
def __init__(self, text): def __init__(self, text):
# type: (str) # type: (str)
self._text = text self._text = text
+7 -5
View File
@@ -342,6 +342,7 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.warn(f"Could not identify {albumpath}. It may not be the intended album") logger.warn(f"Could not identify {albumpath}. It may not be the intended album")
markAsUnprocessed(albumid, albumpath, keep_original_folder) markAsUnprocessed(albumid, albumpath, keep_original_folder)
def markAsUnprocessed(albumid, albumpath, keep_original_folder=False): def markAsUnprocessed(albumid, albumpath, keep_original_folder=False):
myDB = db.DBConnection() myDB = db.DBConnection()
myDB.action( myDB.action(
@@ -419,7 +420,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.debug("Write check exact error: %s", e) logger.debug("Write check exact error: %s", e)
logger.error( logger.error(
f"`{downloaded_track}` is not writable. This is required " f"`{downloaded_track}` is not writable. This is required "
"for some post processing steps. Not continuing." "for some post processing steps. Not continuing."
) )
if new_folder: if new_folder:
shutil.rmtree(new_folder) shutil.rmtree(new_folder)
@@ -595,7 +596,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_download(pushmessage) # twitter.notify_download(pushmessage)
if headphones.CONFIG.OSX_NOTIFY_ENABLED: if headphones.CONFIG.OSX_NOTIFY_ENABLED:
from headphones import cache from headphones import cache
@@ -786,7 +787,7 @@ def moveFiles(albumpath, release, metadata_dict):
newfolder = temp_folder + '[%i]' % i newfolder = temp_folder + '[%i]' % i
lossless_destination_path = os.path.normpath( lossless_destination_path = os.path.normpath(
os.path.join( os.path.join(
headphones.CONFIG.LOSSLESS_DESTINATION_DIR, headphones.CONFIG.LOSSLESS_DESTINATION_DIR,
newfolder newfolder
) )
) )
@@ -828,7 +829,7 @@ def moveFiles(albumpath, release, metadata_dict):
newfolder = temp_folder + '[%i]' % i newfolder = temp_folder + '[%i]' % i
lossy_destination_path = os.path.normpath( lossy_destination_path = os.path.normpath(
os.path.join( os.path.join(
headphones.CONFIG.DESTINATION_DIR, headphones.CONFIG.DESTINATION_DIR,
newfolder newfolder
) )
) )
@@ -877,7 +878,7 @@ def moveFiles(albumpath, release, metadata_dict):
os.remove(file_to_move) os.remove(file_to_move)
except Exception as e: except Exception as e:
logger.error( logger.error(
f"Error deleting `{file_to_move}` from source directory") f"Error deleting `{file_to_move}` from source directory")
else: else:
logger.error( logger.error(
f"Error copying `{file_to_move}`. " f"Error copying `{file_to_move}`. "
@@ -1135,6 +1136,7 @@ def updateFilePermissions(albumpaths):
logger.error(f"Could not change permissions for `{full_path}`") logger.error(f"Could not change permissions for `{full_path}`")
continue continue
def renameUnprocessedFolder(path, tag): def renameUnprocessedFolder(path, tag):
""" """
Rename a unprocessed folder to a new unique name to indicate a certain Rename a unprocessed folder to a new unique name to indicate a certain
+7 -3
View File
@@ -13,8 +13,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
import http.cookiejar import http.cookiejar
import json import json
import time import time
@@ -81,7 +85,7 @@ class qbittorrentclient(object):
logger.debug('Error getting SID. qBittorrent responded with error: ' + str(err.reason)) logger.debug('Error getting SID. qBittorrent responded with error: ' + str(err.reason))
return return
for cookie in self.cookiejar: for cookie in self.cookiejar:
logger.debug('login cookie: ' + cookie.name + ', value: ' + cookie.value) logger.debug('login cookie: ' + cookie.name + ', value: ' + cookie.value)
return return
def _command(self, command, args=None, content_type=None, files=None): def _command(self, command, args=None, content_type=None, files=None):
+1 -1
View File
@@ -220,7 +220,7 @@ def server_message(response):
# First attempt is to 'read' the response as HTML # First attempt is to 'read' the response as HTML
if response.headers.get("content-type") and \ if response.headers.get("content-type") and \
"text/html" in response.headers.get("content-type"): "text/html" in response.headers.get("content-type"):
try: try:
soup = BeautifulSoup(response.content, "html.parser") soup = BeautifulSoup(response.content, "html.parser")
except Exception: except Exception:
+3 -1
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import time import time
from urllib.parse import urlparse from urllib.parse import urlparse
import re import re
+1 -1
View File
@@ -30,7 +30,7 @@ def sab_api_call(request_type=None, params={}, **kwargs):
if headphones.CONFIG.SAB_HOST.endswith('/'): if headphones.CONFIG.SAB_HOST.endswith('/'):
headphones.CONFIG.SAB_HOST = headphones.CONFIG.SAB_HOST[ headphones.CONFIG.SAB_HOST = headphones.CONFIG.SAB_HOST[
0:len(headphones.CONFIG.SAB_HOST) - 1] 0:len(headphones.CONFIG.SAB_HOST) - 1]
url = headphones.CONFIG.SAB_HOST + "/" + "api?" url = headphones.CONFIG.SAB_HOST + "/" + "api?"
+18 -16
View File
@@ -19,7 +19,9 @@ from base64 import b16encode, b32decode
from hashlib import sha1 from hashlib import sha1
import string import string
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import datetime import datetime
import subprocess import subprocess
import unicodedata import unicodedata
@@ -1081,7 +1083,7 @@ def send_to_downloader(data, bestqual, album):
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_snatch(name) # twitter.notify_snatch(name)
if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH: if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH:
logger.info("Sending NMA notification") logger.info("Sending NMA notification")
nma = notifiers.NMA() nma = notifiers.NMA()
@@ -1503,8 +1505,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
try: try:
logger.info("Attempting to log in to Orpheus.network...") logger.info("Attempting to log in to Orpheus.network...")
orpheusobj = gazelleapi.GazelleAPI(headphones.CONFIG.ORPHEUS_USERNAME, orpheusobj = gazelleapi.GazelleAPI(headphones.CONFIG.ORPHEUS_USERNAME,
headphones.CONFIG.ORPHEUS_PASSWORD, headphones.CONFIG.ORPHEUS_PASSWORD,
headphones.CONFIG.ORPHEUS_URL) headphones.CONFIG.ORPHEUS_URL)
orpheusobj._login() orpheusobj._login()
except Exception as e: except Exception as e:
orpheusobj = None orpheusobj = None
@@ -1550,13 +1552,13 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if usersearchterm: if usersearchterm:
all_torrents.extend( all_torrents.extend(
orpheusobj.search_torrents(searchstr=usersearchterm, format=search_format, orpheusobj.search_torrents(searchstr=usersearchterm, format=search_format,
encoding=bitrate_string, releasetype=album_type)['results']) encoding=bitrate_string, releasetype=album_type)['results'])
else: else:
all_torrents.extend(orpheusobj.search_torrents(artistname=semi_clean_artist_term, all_torrents.extend(orpheusobj.search_torrents(artistname=semi_clean_artist_term,
groupname=semi_clean_album_term, groupname=semi_clean_album_term,
format=search_format, format=search_format,
encoding=bitrate_string, encoding=bitrate_string,
releasetype=album_type)['results']) releasetype=album_type)['results'])
# filter on format, size, and num seeders # filter on format, size, and num seeders
logger.info("Filtering torrents by format, maximum size, and minimum seeders...") logger.info("Filtering torrents by format, maximum size, and minimum seeders...")
@@ -1634,8 +1636,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
try: try:
logger.info("Attempting to log in to Redacted...") logger.info("Attempting to log in to Redacted...")
redobj = gazelleapi.GazelleAPI(headphones.CONFIG.REDACTED_USERNAME, redobj = gazelleapi.GazelleAPI(headphones.CONFIG.REDACTED_USERNAME,
headphones.CONFIG.REDACTED_PASSWORD, headphones.CONFIG.REDACTED_PASSWORD,
providerurl) providerurl)
redobj._login() redobj._login()
except Exception as e: except Exception as e:
redobj = None redobj = None
@@ -1649,12 +1651,12 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if usersearchterm: if usersearchterm:
all_torrents.extend( all_torrents.extend(
redobj.search_torrents(searchstr=usersearchterm, format=search_format, redobj.search_torrents(searchstr=usersearchterm, format=search_format,
encoding=bitrate_string)['results']) encoding=bitrate_string)['results'])
else: else:
all_torrents.extend(redobj.search_torrents(artistname=semi_clean_artist_term, all_torrents.extend(redobj.search_torrents(artistname=semi_clean_artist_term,
groupname=semi_clean_album_term, groupname=semi_clean_album_term,
format=search_format, format=search_format,
encoding=bitrate_string)['results']) encoding=bitrate_string)['results'])
# filter on format, size, and num seeders # filter on format, size, and num seeders
logger.info("Filtering torrents by format, maximum size, and minimum seeders...") logger.info("Filtering torrents by format, maximum size, and minimum seeders...")
@@ -1791,7 +1793,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2243.2 Safari/537.36'} 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2243.2 Safari/537.36'}
provider_url = fix_url(headphones.CONFIG.OLDPIRATEBAY_URL) + \ provider_url = fix_url(headphones.CONFIG.OLDPIRATEBAY_URL) + \
"/search.php?" + urllib.parse.urlencode({"q": tpb_term, "iht": 6}) "/search.php?" + urllib.parse.urlencode({"q": tpb_term, "iht": 6})
data = request.request_soup(url=provider_url, headers=headers) data = request.request_soup(url=provider_url, headers=headers)
+4 -4
View File
@@ -183,15 +183,15 @@ def torrentAction(method, arguments):
if _session_id is not None: if _session_id is not None:
headers = {'x-transmission-session-id': _session_id} headers = {'x-transmission-session-id': _session_id}
response = request.request_response(host, method="POST", response = request.request_response(host, method="POST",
data=data_json, headers=headers, auth=auth, data=data_json, headers=headers, auth=auth,
whitelist_status_code=[200, 401, 409]) whitelist_status_code=[200, 401, 409])
else: else:
response = request.request_response(host, auth=auth, response = request.request_response(host, auth=auth,
whitelist_status_code=[401, 409]) whitelist_status_code=[401, 409])
if response.status_code == 401: if response.status_code == 401:
if auth: if auth:
logger.error("Username and/or password not accepted by " logger.error("Username and/or password not accepted by "
"Transmission") "Transmission")
else: else:
logger.error("Transmission authorization required") logger.error("Transmission authorization required")
return return
+6 -2
View File
@@ -13,11 +13,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
from collections import namedtuple from collections import namedtuple
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import urllib.parse import urllib.parse
import http.cookiejar import http.cookiejar
+8 -4
View File
@@ -19,12 +19,16 @@ from operator import itemgetter
import threading import threading
import secrets import secrets
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
import sys import sys
from html import escape as html_escape from html import escape as html_escape
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import os import os
import re import re
@@ -785,7 +789,7 @@ class WebInterface(object):
track_title = tracks['TrackTitle'] track_title = tracks['TrackTitle']
if tracks['CleanName'] != original_clean: if tracks['CleanName'] != original_clean:
artist_id_check = myDB.action('SELECT ArtistID FROM tracks WHERE CleanName = ?', artist_id_check = myDB.action('SELECT ArtistID FROM tracks WHERE CleanName = ?',
[tracks['CleanName']]).fetchone() [tracks['CleanName']]).fetchone()
if artist_id_check: if artist_id_check:
artist_id = artist_id_check[0] artist_id = artist_id_check[0]
myDB.action( myDB.action(
@@ -1074,7 +1078,7 @@ class WebInterface(object):
data[counter] = album['AlbumTitle'] data[counter] = album['AlbumTitle']
counter += 1 counter += 1
return data return data
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
-284
View File
@@ -1,284 +0,0 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook=sys.path.insert(0, 'lib/')
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#I0011 an inline option disables a pylint message or a messages category
#R0801 a set of similar lines has been detected among multiple file
#W0142 a function or method is called using *args or **kwargs to dispatch argument
# W1201(logging-not-lazy)
# C0330(bad-continuation)
# E1205(logging-too-many-args)
disable=I0011,R0801,W0142,C0103,C0111,C0301,C0302,C0304,C0321,C1001,E0101,E0203,E0602,E1101,E1123,R0201,R0401,R0911,R0912,R0914,R0915,R0923,W0102,W0109,W0120,W0141,W0201,W0212,W0231,W0232,W0233,W0301,W0311,W0401,W0403,W0404,W0511,W0601,W0602,W0603,W0611,W0612,W0613,W0621,W0622,W0633,W0702,W0703,W1401,W1201,C0330
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
#output-format=parseable
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the massage information. See doc for all details
#msg-template=
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=150
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent,objects
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=100
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+4 -7
View File
@@ -1,8 +1,5 @@
coverage==4.0.3 coverage==6.2
coveralls==1.1 coveralls==3.3.1
mock==1.3.0 mock==4.0.3
nose==1.3.7 nose==1.3.7
pep8==1.7.0 flake8==4.0.1
pyflakes==1.1.0
pylint==1.3.1 # pylint 1.4 does not run under python 2.6
pyOpenSSL==0.15.1