mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 08:56:17 +00:00
pyupgrade
This commit is contained in:
parent
d6f1a11aca
commit
81d141e540
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -9,10 +8,15 @@
|
|||||||
# Distributed under terms of the GNU AGPLv3 license.
|
# Distributed under terms of the GNU AGPLv3 license.
|
||||||
|
|
||||||
from supysonic.web import create_application
|
from supysonic.web import create_application
|
||||||
|
|
||||||
app = create_application()
|
app = create_application()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
if app:
|
if app:
|
||||||
import sys
|
import sys
|
||||||
app.run(host = sys.argv[1] if len(sys.argv) > 1 else None, port = int(sys.argv[2]) if len(sys.argv) > 2 else 5000, debug = True)
|
|
||||||
|
|
||||||
|
app.run(
|
||||||
|
host=sys.argv[1] if len(sys.argv) > 1 else None,
|
||||||
|
port=int(sys.argv[2]) if len(sys.argv) > 2 else 5000,
|
||||||
|
debug=True,
|
||||||
|
)
|
||||||
|
1
setup.py
1
setup.py
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -25,7 +23,7 @@ from .exceptions import AggregateException, GenericError, MissingParameter, NotF
|
|||||||
|
|
||||||
|
|
||||||
def star_single(cls, eid):
|
def star_single(cls, eid):
|
||||||
""" Stars an entity
|
"""Stars an entity
|
||||||
|
|
||||||
:param cls: entity class, Folder, Artist, Album or Track
|
:param cls: entity class, Folder, Artist, Album or Track
|
||||||
:param eid: id of the entity to star
|
:param eid: id of the entity to star
|
||||||
@ -47,7 +45,7 @@ def star_single(cls, eid):
|
|||||||
|
|
||||||
|
|
||||||
def unstar_single(cls, eid):
|
def unstar_single(cls, eid):
|
||||||
""" Unstars an entity
|
"""Unstars an entity
|
||||||
|
|
||||||
:param cls: entity class, Folder, Artist, Album or Track
|
:param cls: entity class, Folder, Artist, Album or Track
|
||||||
:param eid: id of the entity to unstar
|
:param eid: id of the entity to unstar
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -30,7 +28,7 @@ class GenericError(SubsonicAPIException):
|
|||||||
api_code = 0
|
api_code = 0
|
||||||
|
|
||||||
def __init__(self, message, *args, **kwargs):
|
def __init__(self, message, *args, **kwargs):
|
||||||
super(GenericError, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
@ -41,14 +39,14 @@ class ServerError(GenericError):
|
|||||||
class UnsupportedParameter(GenericError):
|
class UnsupportedParameter(GenericError):
|
||||||
def __init__(self, parameter, *args, **kwargs):
|
def __init__(self, parameter, *args, **kwargs):
|
||||||
message = "Unsupported parameter '{}'".format(parameter)
|
message = "Unsupported parameter '{}'".format(parameter)
|
||||||
super(UnsupportedParameter, self).__init__(message, *args, **kwargs)
|
super().__init__(message, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class MissingParameter(SubsonicAPIException):
|
class MissingParameter(SubsonicAPIException):
|
||||||
api_code = 10
|
api_code = 10
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(MissingParameter, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.message = "A required parameter is missing."
|
self.message = "A required parameter is missing."
|
||||||
|
|
||||||
|
|
||||||
@ -90,13 +88,13 @@ class NotFound(SubsonicAPIException):
|
|||||||
api_code = 70
|
api_code = 70
|
||||||
|
|
||||||
def __init__(self, entity, *args, **kwargs):
|
def __init__(self, entity, *args, **kwargs):
|
||||||
super(NotFound, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.message = "{} not found".format(entity)
|
self.message = "{} not found".format(entity)
|
||||||
|
|
||||||
|
|
||||||
class AggregateException(SubsonicAPIException):
|
class AggregateException(SubsonicAPIException):
|
||||||
def __init__(self, exceptions, *args, **kwargs):
|
def __init__(self, exceptions, *args, **kwargs):
|
||||||
super(AggregateException, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.exceptions = []
|
self.exceptions = []
|
||||||
for exc in exceptions:
|
for exc in exceptions:
|
||||||
@ -114,7 +112,7 @@ class AggregateException(SubsonicAPIException):
|
|||||||
if len(self.exceptions) == 1:
|
if len(self.exceptions) == 1:
|
||||||
return self.exceptions[0].get_response()
|
return self.exceptions[0].get_response()
|
||||||
|
|
||||||
codes = set(exc.api_code for exc in self.exceptions)
|
codes = {exc.api_code for exc in self.exceptions}
|
||||||
errors = [
|
errors = [
|
||||||
dict(code=exc.api_code, message=exc.message) for exc in self.exceptions
|
dict(code=exc.api_code, message=exc.message) for exc in self.exceptions
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -13,7 +11,7 @@ from xml.etree import ElementTree
|
|||||||
from . import API_VERSION
|
from . import API_VERSION
|
||||||
|
|
||||||
|
|
||||||
class BaseFormatter(object):
|
class BaseFormatter:
|
||||||
def make_response(self, elem, data):
|
def make_response(self, elem, data):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -93,12 +91,12 @@ class JSONPFormatter(JSONBaseFormatter):
|
|||||||
class XMLFormatter(BaseFormatter):
|
class XMLFormatter(BaseFormatter):
|
||||||
def __dict2xml(self, elem, dictionary):
|
def __dict2xml(self, elem, dictionary):
|
||||||
"""Convert a dict structure to xml. The game is trivial. Nesting uses the [] parenthesis.
|
"""Convert a dict structure to xml. The game is trivial. Nesting uses the [] parenthesis.
|
||||||
ex. { 'musicFolder': {'id': 1234, 'name': "sss" } }
|
ex. { 'musicFolder': {'id': 1234, 'name': "sss" } }
|
||||||
ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]}
|
ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]}
|
||||||
ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } }
|
ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } }
|
||||||
ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] }
|
ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] }
|
||||||
ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]},
|
ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]},
|
||||||
"status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}}
|
"status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}}
|
||||||
"""
|
"""
|
||||||
if not isinstance(dictionary, dict):
|
if not isinstance(dictionary, dict):
|
||||||
raise TypeError("Expecting a dict")
|
raise TypeError("Expecting a dict")
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -332,7 +330,7 @@ def lyrics():
|
|||||||
logger.debug("Found lyrics file: " + lyrics_path)
|
logger.debug("Found lyrics file: " + lyrics_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(lyrics_path, "rt") as f:
|
with open(lyrics_path) as f:
|
||||||
lyrics = f.read()
|
lyrics = f.read()
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
# Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or
|
# Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -86,7 +86,14 @@ def old_search():
|
|||||||
@api.route("/search2.view", methods=["GET", "POST"])
|
@api.route("/search2.view", methods=["GET", "POST"])
|
||||||
def new_search():
|
def new_search():
|
||||||
query = request.values["query"]
|
query = request.values["query"]
|
||||||
artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
|
(
|
||||||
|
artist_count,
|
||||||
|
artist_offset,
|
||||||
|
album_count,
|
||||||
|
album_offset,
|
||||||
|
song_count,
|
||||||
|
song_offset,
|
||||||
|
) = map(
|
||||||
request.values.get,
|
request.values.get,
|
||||||
[
|
[
|
||||||
"artistCount",
|
"artistCount",
|
||||||
@ -131,7 +138,14 @@ def new_search():
|
|||||||
@api.route("/search3.view", methods=["GET", "POST"])
|
@api.route("/search3.view", methods=["GET", "POST"])
|
||||||
def search_id3():
|
def search_id3():
|
||||||
query = request.values["query"]
|
query = request.values["query"]
|
||||||
artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
|
(
|
||||||
|
artist_count,
|
||||||
|
artist_offset,
|
||||||
|
album_count,
|
||||||
|
album_offset,
|
||||||
|
song_count,
|
||||||
|
song_offset,
|
||||||
|
) = map(
|
||||||
request.values.get,
|
request.values.get,
|
||||||
[
|
[
|
||||||
"artistCount",
|
"artistCount",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -38,7 +36,7 @@ CacheEntry = namedtuple("CacheEntry", ["size", "expires"])
|
|||||||
NULL_ENTRY = CacheEntry(0, 0)
|
NULL_ENTRY = CacheEntry(0, 0)
|
||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
class Cache:
|
||||||
"""Provides a common interface for caching files to disk"""
|
"""Provides a common interface for caching files to disk"""
|
||||||
|
|
||||||
# Modeled after werkzeug.contrib.cache.FileSystemCache
|
# Modeled after werkzeug.contrib.cache.FileSystemCache
|
||||||
|
@ -33,7 +33,7 @@ class TimedProgressDisplay:
|
|||||||
|
|
||||||
def __call__(self, name, scanned):
|
def __call__(self, name, scanned):
|
||||||
if time.time() - self.__last_display > self.__interval:
|
if time.time() - self.__last_display > self.__interval:
|
||||||
progress = "Scanning '{0}': {1} files scanned".format(name, scanned)
|
progress = "Scanning '{}': {} files scanned".format(name, scanned)
|
||||||
self.__stdout.write("\b" * self.__last_len)
|
self.__stdout.write("\b" * self.__last_len)
|
||||||
self.__stdout.write(progress)
|
self.__stdout.write(progress)
|
||||||
self.__stdout.flush()
|
self.__stdout.flush()
|
||||||
@ -188,7 +188,7 @@ class SupysonicCLI(cmd.Cmd):
|
|||||||
self.write_line("Name\t\tPath\n----\t\t----")
|
self.write_line("Name\t\tPath\n----\t\t----")
|
||||||
self.write_line(
|
self.write_line(
|
||||||
"\n".join(
|
"\n".join(
|
||||||
"{0: <16}{1}".format(f.name, f.path)
|
"{: <16}{}".format(f.name, f.path)
|
||||||
for f in Folder.select(lambda f: f.root)
|
for f in Folder.select(lambda f: f.root)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -354,7 +354,7 @@ class SupysonicCLI(cmd.Cmd):
|
|||||||
self.write_line("----\t\t-----\t-------\t-----")
|
self.write_line("----\t\t-----\t-------\t-----")
|
||||||
self.write_line(
|
self.write_line(
|
||||||
"\n".join(
|
"\n".join(
|
||||||
"{0: <16}{1}\t{2}\t{3}".format(
|
"{: <16}{}\t{}\t{}".format(
|
||||||
u.name, "*" if u.admin else "", "*" if u.jukebox else "", u.mail
|
u.name, "*" if u.admin else "", "*" if u.jukebox else "", u.mail
|
||||||
)
|
)
|
||||||
for u in User.select()
|
for u in User.select()
|
||||||
@ -393,16 +393,16 @@ class SupysonicCLI(cmd.Cmd):
|
|||||||
else:
|
else:
|
||||||
if admin:
|
if admin:
|
||||||
user.admin = True
|
user.admin = True
|
||||||
self.write_line("Granted '{0}' admin rights".format(name))
|
self.write_line("Granted '{}' admin rights".format(name))
|
||||||
elif noadmin:
|
elif noadmin:
|
||||||
user.admin = False
|
user.admin = False
|
||||||
self.write_line("Revoked '{0}' admin rights".format(name))
|
self.write_line("Revoked '{}' admin rights".format(name))
|
||||||
if jukebox:
|
if jukebox:
|
||||||
user.jukebox = True
|
user.jukebox = True
|
||||||
self.write_line("Granted '{0}' jukebox rights".format(name))
|
self.write_line("Granted '{}' jukebox rights".format(name))
|
||||||
elif nojukebox:
|
elif nojukebox:
|
||||||
user.jukebox = False
|
user.jukebox = False
|
||||||
self.write_line("Revoked '{0}' jukebox rights".format(name))
|
self.write_line("Revoked '{}' jukebox rights".format(name))
|
||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def user_changepass(self, name, password):
|
def user_changepass(self, name, password):
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -21,7 +19,7 @@ def get_current_config():
|
|||||||
return current_config or DefaultConfig()
|
return current_config or DefaultConfig()
|
||||||
|
|
||||||
|
|
||||||
class DefaultConfig(object):
|
class DefaultConfig:
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
tempdir = os.path.join(tempfile.gettempdir(), "supysonic")
|
tempdir = os.path.join(tempfile.gettempdir(), "supysonic")
|
||||||
@ -67,7 +65,7 @@ class IniConfig(DefaultConfig):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, paths):
|
def __init__(self, paths):
|
||||||
super(IniConfig, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
parser = RawConfigParser()
|
parser = RawConfigParser()
|
||||||
parser.read(paths)
|
parser.read(paths)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -27,7 +25,7 @@ NAMING_SCORE_RULES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoverFile(object):
|
class CoverFile:
|
||||||
__clean_regex = re.compile(r"[^a-z]")
|
__clean_regex = re.compile(r"[^a-z]")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -63,7 +61,7 @@ def is_valid_cover(path):
|
|||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
with Image.open(path):
|
with Image.open(path):
|
||||||
return True
|
return True
|
||||||
except IOError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -16,7 +14,7 @@ from ..utils import get_secret_key
|
|||||||
__all__ = ["DaemonClient"]
|
__all__ = ["DaemonClient"]
|
||||||
|
|
||||||
|
|
||||||
class DaemonCommand(object):
|
class DaemonCommand:
|
||||||
def apply(self, connection, daemon):
|
def apply(self, connection, daemon):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -102,7 +100,7 @@ class JukeboxCommand(DaemonCommand):
|
|||||||
connection.send(rv)
|
connection.send(rv)
|
||||||
|
|
||||||
|
|
||||||
class DaemonCommandResult(object):
|
class DaemonCommandResult:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +126,7 @@ class JukeboxResult(DaemonCommandResult):
|
|||||||
self.playlist = ()
|
self.playlist = ()
|
||||||
|
|
||||||
|
|
||||||
class DaemonClient(object):
|
class DaemonClient:
|
||||||
def __init__(self, address=None):
|
def __init__(self, address=None):
|
||||||
self.__address = address or get_current_config().DAEMON["socket"]
|
self.__address = address or get_current_config().DAEMON["socket"]
|
||||||
self.__key = get_secret_key("daemon_key")
|
self.__key = get_secret_key("daemon_key")
|
||||||
@ -138,7 +136,7 @@ class DaemonClient(object):
|
|||||||
raise DaemonUnavailableError("No daemon address set")
|
raise DaemonUnavailableError("No daemon address set")
|
||||||
try:
|
try:
|
||||||
return Client(address=self.__address, authkey=self.__key)
|
return Client(address=self.__address, authkey=self.__key)
|
||||||
except IOError:
|
except OSError:
|
||||||
raise DaemonUnavailableError(
|
raise DaemonUnavailableError(
|
||||||
"Couldn't connect to daemon at {}".format(self.__address)
|
"Couldn't connect to daemon at {}".format(self.__address)
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -26,7 +24,7 @@ __all__ = ["Daemon"]
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Daemon(object):
|
class Daemon:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.__config = config
|
self.__config = config
|
||||||
self.__listener = None
|
self.__listener = None
|
||||||
|
@ -46,7 +46,7 @@ def sqlite_case_insensitive_like(db, connection):
|
|||||||
cursor.execute("PRAGMA case_sensitive_like = OFF")
|
cursor.execute("PRAGMA case_sensitive_like = OFF")
|
||||||
|
|
||||||
|
|
||||||
class PathMixin(object):
|
class PathMixin:
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, *args, **kwargs):
|
def get(cls, *args, **kwargs):
|
||||||
if kwargs:
|
if kwargs:
|
||||||
@ -529,7 +529,7 @@ class Playlist(db.Entity):
|
|||||||
id=str(self.id),
|
id=str(self.id),
|
||||||
name=self.name
|
name=self.name
|
||||||
if self.user.id == user.id
|
if self.user.id == user.id
|
||||||
else "[%s] %s" % (self.user.name, self.name),
|
else "[{}] {}".format(self.user.name, self.name),
|
||||||
owner=self.user.name,
|
owner=self.user.name,
|
||||||
public=self.public,
|
public=self.public,
|
||||||
songCount=len(tracks),
|
songCount=len(tracks),
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -22,7 +20,7 @@ from .db import Track
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Jukebox(object):
|
class Jukebox:
|
||||||
def __init__(self, cmd):
|
def __init__(self, cmd):
|
||||||
self.__cmd = shlex.split(cmd)
|
self.__cmd = shlex.split(cmd)
|
||||||
self.__playlist = []
|
self.__playlist = []
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -26,14 +24,14 @@ from .db import RatingFolder, RatingTrack
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class StatsDetails(object):
|
class StatsDetails:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.artists = 0
|
self.artists = 0
|
||||||
self.albums = 0
|
self.albums = 0
|
||||||
self.tracks = 0
|
self.tracks = 0
|
||||||
|
|
||||||
|
|
||||||
class Stats(object):
|
class Stats:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.scanned = 0
|
self.scanned = 0
|
||||||
self.added = StatsDetails()
|
self.added = StatsDetails()
|
||||||
@ -66,7 +64,7 @@ class Scanner(Thread):
|
|||||||
on_folder_end=None,
|
on_folder_end=None,
|
||||||
on_done=None,
|
on_done=None,
|
||||||
):
|
):
|
||||||
super(Scanner, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
if extensions is not None and not isinstance(extensions, list):
|
if extensions is not None and not isinstance(extensions, list):
|
||||||
raise TypeError("Invalid extensions type")
|
raise TypeError("Invalid extensions type")
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fenc=utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -45,10 +42,10 @@ def process_table(connection, table, fields, nullable_fields=()):
|
|||||||
sql = "UPDATE {0} SET {1}=%s WHERE {1}=%s".format(table, field)
|
sql = "UPDATE {0} SET {1}=%s WHERE {1}=%s".format(table, field)
|
||||||
c.executemany(sql, map(lambda v: (UUID(v).bytes, v), values))
|
c.executemany(sql, map(lambda v: (UUID(v).bytes, v), values))
|
||||||
for field in fields:
|
for field in fields:
|
||||||
sql = "ALTER TABLE {0} MODIFY {1} BINARY(16) NOT NULL".format(table, field)
|
sql = "ALTER TABLE {} MODIFY {} BINARY(16) NOT NULL".format(table, field)
|
||||||
c.execute(sql)
|
c.execute(sql)
|
||||||
for field in nullable_fields:
|
for field in nullable_fields:
|
||||||
sql = "ALTER TABLE {0} MODIFY {1} BINARY(16)".format(table, field)
|
sql = "ALTER TABLE {} MODIFY {} BINARY(16)".format(table, field)
|
||||||
c.execute(sql)
|
c.execute(sql)
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
@ -20,17 +20,17 @@ def process_table(connection, table):
|
|||||||
c = connection.cursor()
|
c = connection.cursor()
|
||||||
|
|
||||||
c.execute(
|
c.execute(
|
||||||
r"ALTER TABLE {0} ADD COLUMN path_hash BYTEA NOT NULL DEFAULT E'\\0000'".format(
|
r"ALTER TABLE {} ADD COLUMN path_hash BYTEA NOT NULL DEFAULT E'\\0000'".format(
|
||||||
table
|
table
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
hashes = dict()
|
hashes = dict()
|
||||||
c.execute("SELECT path FROM {0}".format(table))
|
c.execute("SELECT path FROM {}".format(table))
|
||||||
for row in c.fetchall():
|
for row in c.fetchall():
|
||||||
hashes[row[0]] = hashlib.sha1(row[0].encode("utf-8")).digest()
|
hashes[row[0]] = hashlib.sha1(row[0].encode("utf-8")).digest()
|
||||||
c.executemany(
|
c.executemany(
|
||||||
"UPDATE {0} SET path_hash=%s WHERE path=%s".format(table),
|
"UPDATE {} SET path_hash=%s WHERE path=%s".format(table),
|
||||||
[(bytes(h), p) for p, h in hashes.items()],
|
[(bytes(h), p) for p, h in hashes.items()],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fenc=utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -17,14 +17,14 @@ def process_table(connection, table):
|
|||||||
c = connection.cursor()
|
c = connection.cursor()
|
||||||
|
|
||||||
c.execute(
|
c.execute(
|
||||||
"ALTER TABLE {0} ADD COLUMN path_hash BLOB NOT NULL DEFAULT ROWID".format(table)
|
"ALTER TABLE {} ADD COLUMN path_hash BLOB NOT NULL DEFAULT ROWID".format(table)
|
||||||
)
|
)
|
||||||
|
|
||||||
hashes = dict()
|
hashes = dict()
|
||||||
for row in c.execute("SELECT path FROM {0}".format(table)):
|
for row in c.execute("SELECT path FROM {}".format(table)):
|
||||||
hashes[row[0]] = hashlib.sha1(row[0].encode("utf-8")).digest()
|
hashes[row[0]] = hashlib.sha1(row[0].encode("utf-8")).digest()
|
||||||
c.executemany(
|
c.executemany(
|
||||||
"UPDATE {0} SET path_hash=? WHERE path=?".format(table),
|
"UPDATE {} SET path_hash=? WHERE path=?".format(table),
|
||||||
[(bytes(h), p) for p, h in hashes.items()],
|
[(bytes(h), p) for p, h in hashes.items()],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -36,13 +34,11 @@ class SupysonicWatcherEventHandler(PatternMatchingEventHandler):
|
|||||||
patterns = list(map(lambda e: "*." + e.lower(), extensions.split())) + list(
|
patterns = list(map(lambda e: "*." + e.lower(), extensions.split())) + list(
|
||||||
map(lambda e: "*" + e, covers.EXTENSIONS)
|
map(lambda e: "*" + e, covers.EXTENSIONS)
|
||||||
)
|
)
|
||||||
super(SupysonicWatcherEventHandler, self).__init__(
|
super().__init__(patterns=patterns, ignore_directories=True)
|
||||||
patterns=patterns, ignore_directories=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def dispatch(self, event):
|
def dispatch(self, event):
|
||||||
try:
|
try:
|
||||||
super(SupysonicWatcherEventHandler, self).dispatch(event)
|
super().dispatch(event)
|
||||||
except Exception as e: # pragma: nocover
|
except Exception as e: # pragma: nocover
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
|
||||||
@ -85,7 +81,7 @@ class SupysonicWatcherEventHandler(PatternMatchingEventHandler):
|
|||||||
self.queue.put(event.dest_path, op, src_path=event.src_path)
|
self.queue.put(event.dest_path, op, src_path=event.src_path)
|
||||||
|
|
||||||
|
|
||||||
class Event(object):
|
class Event:
|
||||||
def __init__(self, path, operation, **kwargs):
|
def __init__(self, path, operation, **kwargs):
|
||||||
if operation & (OP_SCAN | OP_REMOVE) == (OP_SCAN | OP_REMOVE):
|
if operation & (OP_SCAN | OP_REMOVE) == (OP_SCAN | OP_REMOVE):
|
||||||
raise Exception("Flags SCAN and REMOVE both set") # pragma: nocover
|
raise Exception("Flags SCAN and REMOVE both set") # pragma: nocover
|
||||||
@ -131,7 +127,7 @@ class Event(object):
|
|||||||
|
|
||||||
class ScannerProcessingQueue(Thread):
|
class ScannerProcessingQueue(Thread):
|
||||||
def __init__(self, delay):
|
def __init__(self, delay):
|
||||||
super(ScannerProcessingQueue, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.__timeout = delay
|
self.__timeout = delay
|
||||||
self.__cond = Condition()
|
self.__cond = Condition()
|
||||||
@ -254,7 +250,7 @@ class ScannerProcessingQueue(Thread):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SupysonicWatcher(object):
|
class SupysonicWatcher:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.__delay = config.DAEMON["wait_delay"]
|
self.__delay = config.DAEMON["wait_delay"]
|
||||||
self.__handler = SupysonicWatcherEventHandler(config.BASE["scanner_extensions"])
|
self.__handler = SupysonicWatcherEventHandler(config.BASE["scanner_extensions"])
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ class ApiTestBase(TestBase):
|
|||||||
__with_api__ = True
|
__with_api__ = True
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ApiTestBase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
xsd = etree.parse("tests/assets/subsonic-rest-api-1.10.2.xsd")
|
xsd = etree.parse("tests/assets/subsonic-rest-api-1.10.2.xsd")
|
||||||
self.schema = etree.XMLSchema(xsd)
|
self.schema = etree.XMLSchema(xsd)
|
||||||
|
@ -21,7 +21,7 @@ class AlbumSongsTestCase(ApiTestBase):
|
|||||||
# Let's just check paramter validation and ensure coverage
|
# Let's just check paramter validation and ensure coverage
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AlbumSongsTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = Folder(name="Root", root=True, path="tests/assets")
|
folder = Folder(name="Root", root=True, path="tests/assets")
|
||||||
|
@ -18,7 +18,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class AnnotationTestCase(ApiTestBase):
|
class AnnotationTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AnnotationTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
root = Folder(name="Root", root=True, path="tests")
|
root = Folder(name="Root", root=True, path="tests")
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -22,7 +21,7 @@ class ApiSetupTestCase(TestBase):
|
|||||||
__with_api__ = True
|
__with_api__ = True
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ApiSetupTestCase, self).setUp()
|
super().setUp()
|
||||||
self._patch_client()
|
self._patch_client()
|
||||||
|
|
||||||
def __basic_auth_get(self, username, password):
|
def __basic_auth_get(self, username, password):
|
||||||
|
@ -20,7 +20,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class BrowseTestCase(ApiTestBase):
|
class BrowseTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(BrowseTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
Folder(root=True, name="Empty root", path="/tmp")
|
Folder(root=True, name="Empty root", path="/tmp")
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -21,7 +20,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class LyricsTestCase(ApiTestBase):
|
class LyricsTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(LyricsTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = Folder(
|
folder = Folder(
|
||||||
|
@ -22,7 +22,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class MediaTestCase(ApiTestBase):
|
class MediaTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(MediaTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = Folder(
|
folder = Folder(
|
||||||
@ -61,7 +61,7 @@ class MediaTestCase(ApiTestBase):
|
|||||||
artist=artist,
|
artist=artist,
|
||||||
album=album,
|
album=album,
|
||||||
path=os.path.abspath(
|
path=os.path.abspath(
|
||||||
"tests/assets/formats/silence.{0}".format(self.formats[i])
|
"tests/assets/formats/silence.{}".format(self.formats[i])
|
||||||
),
|
),
|
||||||
root_folder=folder,
|
root_folder=folder,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -19,7 +18,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class PlaylistTestCase(ApiTestBase):
|
class PlaylistTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(PlaylistTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
root = Folder(root=True, name="Root folder", path="tests/assets")
|
root = Folder(root=True, name="Root folder", path="tests/assets")
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -19,7 +18,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class RadioStationTestCase(ApiTestBase):
|
class RadioStationTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(RadioStationTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def assertRadioStationCountEqual(self, count):
|
def assertRadioStationCountEqual(self, count):
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -18,10 +17,10 @@ from supysonic.api.formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
|||||||
from ..testbase import TestBase
|
from ..testbase import TestBase
|
||||||
|
|
||||||
|
|
||||||
class UnwrapperMixin(object):
|
class UnwrapperMixin:
|
||||||
def make_response(self, elem, data):
|
def make_response(self, elem, data):
|
||||||
with self.request_context():
|
with self.request_context():
|
||||||
rv = super(UnwrapperMixin, self).make_response(elem, data)
|
rv = super().make_response(elem, data)
|
||||||
return rv.get_data(as_text=True)
|
return rv.get_data(as_text=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -34,7 +33,7 @@ class UnwrapperMixin(object):
|
|||||||
|
|
||||||
class ResponseHelperJsonTestCase(TestBase, UnwrapperMixin.create_from(JSONFormatter)):
|
class ResponseHelperJsonTestCase(TestBase, UnwrapperMixin.create_from(JSONFormatter)):
|
||||||
def make_response(self, elem, data):
|
def make_response(self, elem, data):
|
||||||
rv = super(ResponseHelperJsonTestCase, self).make_response(elem, data)
|
rv = super().make_response(elem, data)
|
||||||
return flask.json.loads(rv)
|
return flask.json.loads(rv)
|
||||||
|
|
||||||
def process_and_extract(self, d):
|
def process_and_extract(self, d):
|
||||||
@ -117,7 +116,7 @@ class ResponseHelperJsonpTestCase(TestBase, UnwrapperMixin.create_from(JSONPForm
|
|||||||
|
|
||||||
class ResponseHelperXMLTestCase(TestBase, UnwrapperMixin.create_from(XMLFormatter)):
|
class ResponseHelperXMLTestCase(TestBase, UnwrapperMixin.create_from(XMLFormatter)):
|
||||||
def make_response(self, elem, data):
|
def make_response(self, elem, data):
|
||||||
xml = super(ResponseHelperXMLTestCase, self).make_response(elem, data)
|
xml = super().make_response(elem, data)
|
||||||
xml = xml.replace('xmlns="http://subsonic.org/restapi"', "")
|
xml = xml.replace('xmlns="http://subsonic.org/restapi"', "")
|
||||||
root = ElementTree.fromstring(xml)
|
root = ElementTree.fromstring(xml)
|
||||||
return root
|
return root
|
||||||
@ -131,7 +130,7 @@ class ResponseHelperXMLTestCase(TestBase, UnwrapperMixin.create_from(XMLFormatte
|
|||||||
self.assertDictEqual(elem.attrib, d)
|
self.assertDictEqual(elem.attrib, d)
|
||||||
|
|
||||||
def test_root(self):
|
def test_root(self):
|
||||||
xml = super(ResponseHelperXMLTestCase, self).make_response("tag", {})
|
xml = super().make_response("tag", {})
|
||||||
self.assertIn("<subsonic-response ", xml)
|
self.assertIn("<subsonic-response ", xml)
|
||||||
self.assertIn('xmlns="http://subsonic.org/restapi"', xml)
|
self.assertIn('xmlns="http://subsonic.org/restapi"', xml)
|
||||||
self.assertTrue(xml.strip().endswith("</subsonic-response>"))
|
self.assertTrue(xml.strip().endswith("</subsonic-response>"))
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -20,7 +19,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class SearchTestCase(ApiTestBase):
|
class SearchTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SearchTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
root = Folder(root=True, name="Root folder", path="tests/assets")
|
root = Folder(root=True, name="Root folder", path="tests/assets")
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -22,7 +22,7 @@ from .apitestbase import ApiTestBase
|
|||||||
|
|
||||||
class TranscodingTestCase(ApiTestBase):
|
class TranscodingTestCase(ApiTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TranscodingTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = FolderManager.add("Folder", "tests/assets/folder")
|
folder = FolderManager.add("Folder", "tests/assets/folder")
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -74,8 +73,7 @@ class CacheTestCase(unittest.TestCase):
|
|||||||
val = [b"0", b"12", b"345", b"6789"]
|
val = [b"0", b"12", b"345", b"6789"]
|
||||||
|
|
||||||
def gen():
|
def gen():
|
||||||
for b in val:
|
yield from val
|
||||||
yield b
|
|
||||||
|
|
||||||
t = []
|
t = []
|
||||||
for x in cache.set_generated("key", gen):
|
for x in cache.set_generated("key", gen):
|
||||||
@ -160,8 +158,7 @@ class CacheTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def gen():
|
def gen():
|
||||||
# Cause a TypeError halfway through
|
# Cause a TypeError halfway through
|
||||||
for b in [b"0", b"12", object(), b"345", b"6789"]:
|
yield from [b"0", b"12", object(), b"345", b"6789"]
|
||||||
yield b
|
|
||||||
|
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
for x in cache.set_generated("key", gen):
|
for x in cache.set_generated("key", gen):
|
||||||
@ -174,8 +171,7 @@ class CacheTestCase(unittest.TestCase):
|
|||||||
cache = Cache(self.__dir, 20)
|
cache = Cache(self.__dir, 20)
|
||||||
|
|
||||||
def gen():
|
def gen():
|
||||||
for b in [b"0", b"12", b"345", b"6789"]:
|
yield from [b"0", b"12", b"345", b"6789"]
|
||||||
yield b
|
|
||||||
|
|
||||||
g1 = cache.set_generated("key", gen)
|
g1 = cache.set_generated("key", gen)
|
||||||
g2 = cache.set_generated("key", gen)
|
g2 = cache.set_generated("key", gen)
|
||||||
|
@ -42,7 +42,7 @@ class CLITestCase(unittest.TestCase):
|
|||||||
os.remove(self.__db[1])
|
os.remove(self.__db[1])
|
||||||
|
|
||||||
def __add_folder(self, name, path):
|
def __add_folder(self, name, path):
|
||||||
self.__cli.onecmd("folder add {0} {1}".format(name, shlex.quote(path)))
|
self.__cli.onecmd("folder add {} {}".format(name, shlex.quote(path)))
|
||||||
|
|
||||||
def test_folder_add(self):
|
def test_folder_add(self):
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -21,7 +20,7 @@ class LastFmTestCase(unittest.TestCase):
|
|||||||
logging.getLogger("supysonic.lastfm").addHandler(logging.NullHandler())
|
logging.getLogger("supysonic.lastfm").addHandler(logging.NullHandler())
|
||||||
lastfm = LastFm({"api_key": "key", "secret": "secret"}, None)
|
lastfm = LastFm({"api_key": "key", "secret": "secret"}, None)
|
||||||
|
|
||||||
rv = lastfm._LastFm__api_request(False, method="dummy", accents=u"àéèùö")
|
rv = lastfm._LastFm__api_request(False, method="dummy", accents="àéèùö")
|
||||||
self.assertIsInstance(rv, dict)
|
self.assertIsInstance(rv, dict)
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class ScannerTestCase(unittest.TestCase):
|
|||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
dir=os.path.dirname(track.path), delete=False
|
dir=os.path.dirname(track.path), delete=False
|
||||||
) as tf:
|
) as tf:
|
||||||
with io.open(track.path, "rb") as f:
|
with open(track.path, "rb") as f:
|
||||||
tf.write(f.read())
|
tf.write(f.read())
|
||||||
try:
|
try:
|
||||||
yield tf.name
|
yield tf.name
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -28,7 +28,7 @@ class WatcherTestConfig(TestConfig):
|
|||||||
DAEMON = {"wait_delay": 0.5, "log_file": "/dev/null", "log_level": "DEBUG"}
|
DAEMON = {"wait_delay": 0.5, "log_file": "/dev/null", "log_level": "DEBUG"}
|
||||||
|
|
||||||
def __init__(self, db_uri):
|
def __init__(self, db_uri):
|
||||||
super(WatcherTestConfig, self).__init__(False, False)
|
super().__init__(False, False)
|
||||||
self.BASE["database_uri"] = db_uri
|
self.BASE["database_uri"] = db_uri
|
||||||
|
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ class WatcherTestBase(unittest.TestCase):
|
|||||||
|
|
||||||
class WatcherTestCase(WatcherTestBase):
|
class WatcherTestCase(WatcherTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(WatcherTestCase, self).setUp()
|
super().setUp()
|
||||||
self.__dir = tempfile.mkdtemp()
|
self.__dir = tempfile.mkdtemp()
|
||||||
with db_session:
|
with db_session:
|
||||||
FolderManager.add("Folder", self.__dir)
|
FolderManager.add("Folder", self.__dir)
|
||||||
@ -73,7 +73,7 @@ class WatcherTestCase(WatcherTestBase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self._stop()
|
self._stop()
|
||||||
shutil.rmtree(self.__dir)
|
shutil.rmtree(self.__dir)
|
||||||
super(WatcherTestCase, self).tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _tempname():
|
def _tempname():
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -14,7 +12,7 @@ class FrontendTestBase(TestBase):
|
|||||||
__with_webui__ = True
|
__with_webui__ = True
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FrontendTestBase, self).setUp()
|
super().setUp()
|
||||||
self._patch_client()
|
self._patch_client()
|
||||||
|
|
||||||
def _login(self, username, password):
|
def _login(self, username, password):
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -19,7 +18,7 @@ from .frontendtestbase import FrontendTestBase
|
|||||||
|
|
||||||
class PlaylistTestCase(FrontendTestBase):
|
class PlaylistTestCase(FrontendTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(PlaylistTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = Folder(name="Root", path="tests/assets", root=True)
|
folder = Folder(name="Root", path="tests/assets", root=True)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
@ -20,7 +19,7 @@ from .frontendtestbase import FrontendTestBase
|
|||||||
|
|
||||||
class UserTestCase(FrontendTestBase):
|
class UserTestCase(FrontendTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(UserTestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
self.users = {u.name: u.id for u in User.select()}
|
self.users = {u.name: u.id for u in User.select()}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
@ -21,7 +19,7 @@ from .testbase import TestBase
|
|||||||
|
|
||||||
class Issue129TestCase(TestBase):
|
class Issue129TestCase(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(Issue129TestCase, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
with db_session:
|
with db_session:
|
||||||
folder = FolderManager.add("folder", os.path.abspath("tests/assets/folder"))
|
folder = FolderManager.add("folder", os.path.abspath("tests/assets/folder"))
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
|
||||||
#
|
#
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
|
@ -71,7 +71,7 @@ class UserManagerTestCase(unittest.TestCase):
|
|||||||
("d68c95a91ed7773aa57c7c044d2309a5bf1da2e7", "pepper"),
|
("d68c95a91ed7773aa57c7c044d2309a5bf1da2e7", "pepper"),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
func(u"éèàïô", "ABC+"), ("b639ba5217b89c906019d89d5816b407d8730898", "ABC+")
|
func("éèàïô", "ABC+"), ("b639ba5217b89c906019d89d5816b407d8730898", "ABC+")
|
||||||
)
|
)
|
||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
|
@ -32,7 +32,7 @@ class TestConfig(DefaultConfig):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, with_webui, with_api):
|
def __init__(self, with_webui, with_api):
|
||||||
super(TestConfig, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
for cls in reversed(inspect.getmro(self.__class__)):
|
for cls in reversed(inspect.getmro(self.__class__)):
|
||||||
for attr, value in cls.__dict__.items():
|
for attr, value in cls.__dict__.items():
|
||||||
@ -47,7 +47,7 @@ class TestConfig(DefaultConfig):
|
|||||||
self.WEBAPP.update({"mount_webui": with_webui, "mount_api": with_api})
|
self.WEBAPP.update({"mount_webui": with_webui, "mount_api": with_api})
|
||||||
|
|
||||||
|
|
||||||
class MockResponse(object):
|
class MockResponse:
|
||||||
def __init__(self, response):
|
def __init__(self, response):
|
||||||
self.__status_code = response.status_code
|
self.__status_code = response.status_code
|
||||||
self.__data = response.get_data(as_text=True)
|
self.__data = response.get_data(as_text=True)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
# This file is part of Supysonic.
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
# Supysonic is a Python implementation of the Subsonic server API.
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user