mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +00:00
Dropping Python 2 support
This commit is contained in:
parent
2f9fa0da6f
commit
1d01450f33
@ -1,7 +1,6 @@
|
|||||||
dist: xenial
|
dist: xenial
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- 2.7
|
|
||||||
- 3.5
|
- 3.5
|
||||||
- 3.6
|
- 3.6
|
||||||
- 3.7
|
- 3.7
|
||||||
|
10
README.md
10
README.md
@ -4,7 +4,7 @@ _Supysonic_ is a Python implementation of the [Subsonic][] server API.
|
|||||||
|
|
||||||
[![Build Status](https://travis-ci.org/spl0k/supysonic.svg?branch=master)](https://travis-ci.org/spl0k/supysonic)
|
[![Build Status](https://travis-ci.org/spl0k/supysonic.svg?branch=master)](https://travis-ci.org/spl0k/supysonic)
|
||||||
[![codecov](https://codecov.io/gh/spl0k/supysonic/branch/master/graph/badge.svg)](https://codecov.io/gh/spl0k/supysonic)
|
[![codecov](https://codecov.io/gh/spl0k/supysonic/branch/master/graph/badge.svg)](https://codecov.io/gh/spl0k/supysonic)
|
||||||
![Python](https://img.shields.io/badge/python-2.7%2C%203.5%2C%203.6%2C%203.7-blue.svg)
|
![Python](https://img.shields.io/badge/python-3.5%2C%203.6%2C%203.7-blue.svg)
|
||||||
|
|
||||||
Current supported features are:
|
Current supported features are:
|
||||||
* browsing (by folders or tags)
|
* browsing (by folders or tags)
|
||||||
@ -51,20 +51,20 @@ or
|
|||||||
|
|
||||||
$ pip install .
|
$ pip install .
|
||||||
|
|
||||||
but not both. Please note that the `pip` method doesn't seem to work with
|
but not both.
|
||||||
Python 2.7.
|
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You'll need these to run _Supysonic_:
|
You'll need these to run _Supysonic_:
|
||||||
|
|
||||||
* Python 2.7 or >= 3.5
|
* Python >= 3.5
|
||||||
* [Flask](http://flask.pocoo.org/)
|
* [Flask](http://flask.pocoo.org/)
|
||||||
* [PonyORM](https://ponyorm.com/)
|
* [PonyORM](https://ponyorm.com/)
|
||||||
* [Python Imaging Library](https://github.com/python-pillow/Pillow)
|
* [Python Imaging Library](https://github.com/python-pillow/Pillow)
|
||||||
* [requests](http://docs.python-requests.org/)
|
* [requests](http://docs.python-requests.org/)
|
||||||
* [mutagen](https://mutagen.readthedocs.io/en/latest/)
|
* [mutagen](https://mutagen.readthedocs.io/en/latest/)
|
||||||
* [watchdog](https://github.com/gorakhargosh/watchdog)
|
* [watchdog](https://github.com/gorakhargosh/watchdog)
|
||||||
|
* [zipstream](https://github.com/allanlei/python-zipstream)
|
||||||
|
|
||||||
All the dependencies will automatically be installed by the
|
All the dependencies will automatically be installed by the
|
||||||
installation command above.
|
installation command above.
|
||||||
@ -142,7 +142,7 @@ _Supysonic_ can run as a WSGI application with the `cgi-bin/supysonic.wsgi`
|
|||||||
file. To run it within an _Apache2_ server, first you need to install the WSGI
|
file. To run it within an _Apache2_ server, first you need to install the WSGI
|
||||||
module and enable it.
|
module and enable it.
|
||||||
|
|
||||||
$ apt install libapache2-mod-wsgi
|
$ apt install libapache2-mod-wsgi-py3
|
||||||
$ a2enmod wsgi
|
$ a2enmod wsgi
|
||||||
|
|
||||||
Next, edit the _Apache_ configuration to load the application. Here's a basic
|
Next, edit the _Apache_ configuration to load the application. Here's a basic
|
||||||
|
3
setup.py
3
setup.py
@ -20,7 +20,6 @@ reqs = [
|
|||||||
"Pillow",
|
"Pillow",
|
||||||
"requests>=1.0.0",
|
"requests>=1.0.0",
|
||||||
"mutagen>=1.33",
|
"mutagen>=1.33",
|
||||||
"scandir<2.0.0; python_version <= '2.7'",
|
|
||||||
"watchdog>=0.8.0",
|
"watchdog>=0.8.0",
|
||||||
"zipstream",
|
"zipstream",
|
||||||
]
|
]
|
||||||
@ -55,8 +54,6 @@ setup(
|
|||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
"Intended Audience :: System Administrators",
|
"Intended Audience :: System Administrators",
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||||
"Programming Language :: Python :: 2",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.5",
|
"Programming Language :: Python :: 3.5",
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
|
@ -18,7 +18,6 @@ from pony.orm import ObjectNotFound
|
|||||||
from pony.orm import commit
|
from pony.orm import commit
|
||||||
|
|
||||||
from ..managers.user import UserManager
|
from ..managers.user import UserManager
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from .exceptions import Unauthorized
|
from .exceptions import Unauthorized
|
||||||
from .formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
from .formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
||||||
|
@ -24,7 +24,6 @@ from ..db import (
|
|||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
from ..db import now
|
from ..db import now
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
from .exceptions import GenericError, NotFound
|
from .exceptions import GenericError, NotFound
|
||||||
|
@ -19,7 +19,6 @@ from ..db import Track, Album, Artist, Folder, User
|
|||||||
from ..db import StarredTrack, StarredAlbum, StarredArtist, StarredFolder
|
from ..db import StarredTrack, StarredAlbum, StarredArtist, StarredFolder
|
||||||
from ..db import RatingTrack, RatingFolder
|
from ..db import RatingTrack, RatingFolder
|
||||||
from ..lastfm import LastFm
|
from ..lastfm import LastFm
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api, get_entity, get_entity_id
|
from . import api, get_entity, get_entity_id
|
||||||
from .exceptions import AggregateException, GenericError, MissingParameter, NotFound
|
from .exceptions import AggregateException, GenericError, MissingParameter, NotFound
|
||||||
@ -150,7 +149,9 @@ def rate():
|
|||||||
if rating == 0:
|
if rating == 0:
|
||||||
if tid is not None:
|
if tid is not None:
|
||||||
delete(
|
delete(
|
||||||
r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == tid
|
r
|
||||||
|
for r in RatingTrack
|
||||||
|
if r.user.id == request.user.id and r.rated.id == tid
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
delete(
|
delete(
|
||||||
|
@ -14,7 +14,6 @@ from flask import request
|
|||||||
from pony.orm import ObjectNotFound, select
|
from pony.orm import ObjectNotFound, select
|
||||||
|
|
||||||
from ..db import Folder, Artist, Album, Track
|
from ..db import Folder, Artist, Album, Track
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api, get_entity, get_entity_id
|
from . import api, get_entity, get_entity_id
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from ..db import ChatMessage, User
|
from ..db import ChatMessage, User
|
||||||
from ..py23 import dict
|
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
from flask import json, jsonify, make_response
|
from flask import json, jsonify, make_response
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
from ..py23 import dict, strtype
|
|
||||||
from . import API_VERSION
|
from . import API_VERSION
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +102,7 @@ class XMLFormatter(BaseFormatter):
|
|||||||
"""
|
"""
|
||||||
if not isinstance(dictionary, dict):
|
if not isinstance(dictionary, dict):
|
||||||
raise TypeError("Expecting a dict")
|
raise TypeError("Expecting a dict")
|
||||||
if not all(map(lambda x: isinstance(x, strtype), dictionary)):
|
if not all(map(lambda x: isinstance(x, str), dictionary)):
|
||||||
raise TypeError("Dictionary keys must be strings")
|
raise TypeError("Dictionary keys must be strings")
|
||||||
|
|
||||||
for name, value in dictionary.items():
|
for name, value in dictionary.items():
|
||||||
@ -125,7 +124,7 @@ class XMLFormatter(BaseFormatter):
|
|||||||
def __value_tostring(self, value):
|
def __value_tostring(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
if isinstance(value, strtype):
|
if isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return str(value).lower()
|
return str(value).lower()
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#
|
#
|
||||||
# Distributed under terms of the GNU AGPLv3 license.
|
# Distributed under terms of the GNU AGPLv3 license.
|
||||||
|
|
||||||
import codecs
|
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os.path
|
import os.path
|
||||||
@ -33,7 +32,6 @@ from .. import scanner
|
|||||||
from ..cache import CacheMiss
|
from ..cache import CacheMiss
|
||||||
from ..covers import get_embedded_cover
|
from ..covers import get_embedded_cover
|
||||||
from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now
|
from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api, get_entity, get_entity_id
|
from . import api, get_entity, get_entity_id
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
@ -137,9 +135,7 @@ def stream_media():
|
|||||||
raise GenericError(message)
|
raise GenericError(message)
|
||||||
|
|
||||||
transcoder, decoder, encoder = [
|
transcoder, decoder, encoder = [
|
||||||
prepare_transcoding_cmdline(
|
prepare_transcoding_cmdline(x, res, src_suffix, dst_suffix, dst_bitrate)
|
||||||
x, res, src_suffix, dst_suffix, dst_bitrate
|
|
||||||
)
|
|
||||||
for x in (transcoder, decoder, encoder)
|
for x in (transcoder, decoder, encoder)
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
@ -304,7 +300,8 @@ def lyrics():
|
|||||||
logger.debug("Found lyrics file: " + lyrics_path)
|
logger.debug("Found lyrics file: " + lyrics_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lyrics = read_file_as_unicode(lyrics_path)
|
with open(lyrics_path, "rt") as f:
|
||||||
|
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
|
||||||
# return no lyrics. Log it anyway.
|
# return no lyrics. Log it anyway.
|
||||||
@ -350,22 +347,3 @@ def lyrics():
|
|||||||
logger.warning("Error while requesting the ChartLyrics API: " + str(e))
|
logger.warning("Error while requesting the ChartLyrics API: " + str(e))
|
||||||
|
|
||||||
return request.formatter("lyrics", lyrics)
|
return request.formatter("lyrics", lyrics)
|
||||||
|
|
||||||
|
|
||||||
def read_file_as_unicode(path):
|
|
||||||
""" Opens a file trying with different encodings and returns the contents as a unicode string """
|
|
||||||
|
|
||||||
encodings = ["utf-8", "latin1"] # Should be extended to support more encodings
|
|
||||||
|
|
||||||
for enc in encodings:
|
|
||||||
try:
|
|
||||||
contents = codecs.open(path, "r", encoding=enc).read()
|
|
||||||
logger.debug("Read file {} with {} encoding".format(path, enc))
|
|
||||||
# Maybe save the encoding somewhere to prevent going through this loop each time for the same file
|
|
||||||
return contents
|
|
||||||
except UnicodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Fallback to ASCII
|
|
||||||
logger.debug("Reading file {} with ascii encoding".format(path))
|
|
||||||
return unicode(open(path, "r").read())
|
|
||||||
|
@ -12,7 +12,6 @@ import uuid
|
|||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from ..db import Playlist, User, Track
|
from ..db import Playlist, User, Track
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api, get_entity
|
from . import api, get_entity
|
||||||
from .exceptions import Forbidden, MissingParameter, NotFound
|
from .exceptions import Forbidden, MissingParameter, NotFound
|
||||||
|
@ -13,7 +13,6 @@ from flask import request
|
|||||||
from pony.orm import select
|
from pony.orm import select
|
||||||
|
|
||||||
from ..db import Folder, Track, Artist, Album
|
from ..db import Folder, Track, Artist, Album
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
from .exceptions import MissingParameter
|
from .exceptions import MissingParameter
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from ..py23 import dict
|
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ from functools import wraps
|
|||||||
|
|
||||||
from ..db import User
|
from ..db import User
|
||||||
from ..managers.user import UserManager
|
from ..managers.user import UserManager
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import api, decode_password
|
from . import api, decode_password
|
||||||
from .exceptions import Forbidden, GenericError, NotFound
|
from .exceptions import Forbidden, GenericError, NotFound
|
||||||
|
@ -18,8 +18,6 @@ import tempfile
|
|||||||
import threading
|
import threading
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from .py23 import scandir, osreplace
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ class Cache(object):
|
|||||||
for mtime, size, key in sorted(
|
for mtime, size, key in sorted(
|
||||||
[
|
[
|
||||||
(f.stat().st_mtime, f.stat().st_size, f.name)
|
(f.stat().st_mtime, f.stat().st_size, f.name)
|
||||||
for f in scandir(self._cache_dir)
|
for f in os.scandir(self._cache_dir)
|
||||||
if f.is_file()
|
if f.is_file()
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
@ -158,7 +156,7 @@ class Cache(object):
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
if self._auto_prune:
|
if self._auto_prune:
|
||||||
self._make_space(size, key=key)
|
self._make_space(size, key=key)
|
||||||
osreplace(f.name, self._filepath(key))
|
os.replace(f.name, self._filepath(key))
|
||||||
self._record_file(key, size)
|
self._record_file(key, size)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# Ignore error from trying to delete the renamed temp file
|
# Ignore error from trying to delete the renamed temp file
|
||||||
|
@ -8,14 +8,11 @@
|
|||||||
#
|
#
|
||||||
# Distributed under terms of the GNU AGPLv3 license.
|
# Distributed under terms of the GNU AGPLv3 license.
|
||||||
|
|
||||||
try:
|
|
||||||
from configparser import RawConfigParser
|
|
||||||
except ImportError:
|
|
||||||
from ConfigParser import RawConfigParser
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
from configparser import RawConfigParser
|
||||||
|
|
||||||
current_config = None
|
current_config = None
|
||||||
|
|
||||||
|
|
||||||
@ -92,11 +89,6 @@ class IniConfig(DefaultConfig):
|
|||||||
return True
|
return True
|
||||||
if lv in ("no", "false", "off"):
|
if lv in ("no", "false", "off"):
|
||||||
return False
|
return False
|
||||||
try:
|
|
||||||
if isinstance(value, unicode):
|
|
||||||
return str(value)
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -16,8 +16,8 @@ from mutagen.easyid3 import EasyID3
|
|||||||
from mutagen.flac import FLAC, Picture
|
from mutagen.flac import FLAC, Picture
|
||||||
from mutagen._vorbis import VCommentDict
|
from mutagen._vorbis import VCommentDict
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from os import scandir
|
||||||
|
|
||||||
from .py23 import scandir, strtype
|
|
||||||
|
|
||||||
EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
|
EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
|
||||||
NAMING_SCORE_RULES = (
|
NAMING_SCORE_RULES = (
|
||||||
@ -90,7 +90,7 @@ def find_cover_in_folder(path, album_name=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_embedded_cover(path):
|
def get_embedded_cover(path):
|
||||||
if not isinstance(path, strtype): # pragma: nocover
|
if not isinstance(path, str): # pragma: nocover
|
||||||
raise TypeError("Expecting string, got " + str(type(path)))
|
raise TypeError("Expecting string, got " + str(type(path)))
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
|
@ -11,7 +11,6 @@ from multiprocessing.connection import Client
|
|||||||
|
|
||||||
from .exceptions import DaemonUnavailableError
|
from .exceptions import DaemonUnavailableError
|
||||||
from ..config import get_current_config
|
from ..config import get_current_config
|
||||||
from ..py23 import strtype
|
|
||||||
from ..utils import get_secret_key
|
from ..utils import get_secret_key
|
||||||
|
|
||||||
__all__ = ["DaemonClient"]
|
__all__ = ["DaemonClient"]
|
||||||
@ -86,13 +85,13 @@ class DaemonClient(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def add_watched_folder(self, folder):
|
def add_watched_folder(self, folder):
|
||||||
if not isinstance(folder, strtype):
|
if not isinstance(folder, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(folder)))
|
raise TypeError("Expecting string, got " + str(type(folder)))
|
||||||
with self.__get_connection() as c:
|
with self.__get_connection() as c:
|
||||||
c.send(AddWatchedFolderCommand(folder))
|
c.send(AddWatchedFolderCommand(folder))
|
||||||
|
|
||||||
def remove_watched_folder(self, folder):
|
def remove_watched_folder(self, folder):
|
||||||
if not isinstance(folder, strtype):
|
if not isinstance(folder, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(folder)))
|
raise TypeError("Expecting string, got " + str(type(folder)))
|
||||||
with self.__get_connection() as c:
|
with self.__get_connection() as c:
|
||||||
c.send(RemoveWatchedFolder(folder))
|
c.send(RemoveWatchedFolder(folder))
|
||||||
|
@ -20,14 +20,8 @@ from pony.orm import ObjectNotFound, DatabaseError
|
|||||||
from pony.orm import buffer
|
from pony.orm import buffer
|
||||||
from pony.orm import min, max, avg, sum, exists
|
from pony.orm import min, max, avg, sum, exists
|
||||||
from pony.orm import db_session
|
from pony.orm import db_session
|
||||||
from uuid import UUID, uuid4
|
|
||||||
|
|
||||||
from .py23 import dict, strtype
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib.parse import urlparse, parse_qsl
|
from urllib.parse import urlparse, parse_qsl
|
||||||
except ImportError:
|
from uuid import UUID, uuid4
|
||||||
from urlparse import urlparse, parse_qsl
|
|
||||||
|
|
||||||
SCHEMA_VERSION = "20190915"
|
SCHEMA_VERSION = "20190915"
|
||||||
|
|
||||||
@ -538,7 +532,7 @@ class Playlist(db.Entity):
|
|||||||
tid = track
|
tid = track
|
||||||
elif isinstance(track, Track):
|
elif isinstance(track, Track):
|
||||||
tid = track.id
|
tid = track.id
|
||||||
elif isinstance(track, strtype):
|
elif isinstance(track, str):
|
||||||
tid = UUID(track)
|
tid = UUID(track)
|
||||||
|
|
||||||
if self.tracks and len(self.tracks) > 0:
|
if self.tracks and len(self.tracks) > 0:
|
||||||
@ -557,7 +551,7 @@ class Playlist(db.Entity):
|
|||||||
|
|
||||||
|
|
||||||
def parse_uri(database_uri):
|
def parse_uri(database_uri):
|
||||||
if not isinstance(database_uri, strtype):
|
if not isinstance(database_uri, str):
|
||||||
raise TypeError("Expecting a string")
|
raise TypeError("Expecting a string")
|
||||||
|
|
||||||
uri = urlparse(database_uri)
|
uri = urlparse(database_uri)
|
||||||
|
@ -17,7 +17,6 @@ from pony.orm import ObjectNotFound
|
|||||||
from ..db import User, ClientPrefs
|
from ..db import User, ClientPrefs
|
||||||
from ..lastfm import LastFm
|
from ..lastfm import LastFm
|
||||||
from ..managers.user import UserManager
|
from ..managers.user import UserManager
|
||||||
from ..py23 import dict
|
|
||||||
|
|
||||||
from . import admin_only, frontend
|
from . import admin_only, frontend
|
||||||
|
|
||||||
|
@ -11,8 +11,6 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .py23 import strtype
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +85,7 @@ class LastFm:
|
|||||||
sig_str = b""
|
sig_str = b""
|
||||||
for k, v in sorted(kwargs.items()):
|
for k, v in sorted(kwargs.items()):
|
||||||
k = k.encode("utf-8")
|
k = k.encode("utf-8")
|
||||||
v = v.encode("utf-8") if isinstance(v, strtype) else str(v).encode("utf-8")
|
v = v.encode("utf-8") if isinstance(v, str) else str(v).encode("utf-8")
|
||||||
sig_str += k + v
|
sig_str += k + v
|
||||||
sig = hashlib.md5(sig_str + self.__api_secret).hexdigest()
|
sig = hashlib.md5(sig_str + self.__api_secret).hexdigest()
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ from pony.orm import ObjectNotFound
|
|||||||
from ..daemon.client import DaemonClient
|
from ..daemon.client import DaemonClient
|
||||||
from ..daemon.exceptions import DaemonUnavailableError
|
from ..daemon.exceptions import DaemonUnavailableError
|
||||||
from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack
|
from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack
|
||||||
from ..py23 import strtype
|
|
||||||
|
|
||||||
|
|
||||||
class FolderManager:
|
class FolderManager:
|
||||||
|
@ -16,7 +16,6 @@ import uuid
|
|||||||
from pony.orm import ObjectNotFound
|
from pony.orm import ObjectNotFound
|
||||||
|
|
||||||
from ..db import User
|
from ..db import User
|
||||||
from ..py23 import strtype
|
|
||||||
|
|
||||||
|
|
||||||
class UserManager:
|
class UserManager:
|
||||||
@ -24,7 +23,7 @@ class UserManager:
|
|||||||
def get(uid):
|
def get(uid):
|
||||||
if isinstance(uid, uuid.UUID):
|
if isinstance(uid, uuid.UUID):
|
||||||
pass
|
pass
|
||||||
elif isinstance(uid, strtype):
|
elif isinstance(uid, str):
|
||||||
uid = uuid.UUID(uid)
|
uid = uuid.UUID(uid)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid user id")
|
raise ValueError("Invalid user id")
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
# This file is part of Supysonic.
|
|
||||||
# Supysonic is a Python implementation of the Subsonic server API.
|
|
||||||
#
|
|
||||||
# Copyright (C) 2018-2019 Alban 'spl0k' Féron
|
|
||||||
# 2018-2019 Carey 'pR0Ps' Metcalfe
|
|
||||||
#
|
|
||||||
# Distributed under terms of the GNU AGPLv3 license.
|
|
||||||
|
|
||||||
# Try built-in scandir, fall back to the package for Python 2.7
|
|
||||||
try:
|
|
||||||
from os import scandir
|
|
||||||
except ImportError:
|
|
||||||
from scandir import scandir
|
|
||||||
|
|
||||||
# os.replace was added in Python 3.3, provide a fallback for Python 2.7
|
|
||||||
try:
|
|
||||||
from os import replace as osreplace
|
|
||||||
except ImportError:
|
|
||||||
# os.rename is equivalent to os.replace except on Windows
|
|
||||||
# On Windows an existing file will not be overwritten
|
|
||||||
# This fallback just attempts to delete the dst file before using rename
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.platform != "win32":
|
|
||||||
from os import rename as osreplace
|
|
||||||
else:
|
|
||||||
import os
|
|
||||||
|
|
||||||
def osreplace(src, dst):
|
|
||||||
try:
|
|
||||||
os.remove(dst)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
os.rename(src, dst)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from queue import Queue, Empty as QueueEmpty
|
|
||||||
except ImportError:
|
|
||||||
from Queue import Queue, Empty as QueueEmpty
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Python 2
|
|
||||||
strtype = basestring
|
|
||||||
|
|
||||||
_builtin_dict = dict
|
|
||||||
|
|
||||||
class DictMeta(type):
|
|
||||||
def __instancecheck__(cls, instance):
|
|
||||||
return isinstance(instance, _builtin_dict)
|
|
||||||
|
|
||||||
class dict(dict):
|
|
||||||
__metaclass__ = DictMeta
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self.viewkeys()
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
return self.viewvalues()
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return self.viewitems()
|
|
||||||
|
|
||||||
|
|
||||||
except NameError:
|
|
||||||
# Python 3
|
|
||||||
strtype = str
|
|
||||||
dict = dict
|
|
@ -14,13 +14,13 @@ import time
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pony.orm import db_session
|
from pony.orm import db_session
|
||||||
|
from queue import Queue, Empty as QueueEmpty
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
|
||||||
from .covers import find_cover_in_folder, has_embedded_cover, CoverFile
|
from .covers import find_cover_in_folder, has_embedded_cover, CoverFile
|
||||||
from .db import Folder, Artist, Album, Track, User
|
from .db import Folder, Artist, Album, Track, User
|
||||||
from .db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack
|
from .db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack
|
||||||
from .db import RatingFolder, RatingTrack
|
from .db import RatingFolder, RatingTrack
|
||||||
from .py23 import scandir, strtype, Queue, QueueEmpty
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -93,7 +93,7 @@ class Scanner(Thread):
|
|||||||
self.__progress(folder_name, scanned)
|
self.__progress(folder_name, scanned)
|
||||||
|
|
||||||
def queue_folder(self, folder_name):
|
def queue_folder(self, folder_name):
|
||||||
if not isinstance(folder_name, strtype):
|
if not isinstance(folder_name, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(folder_name)))
|
raise TypeError("Expecting string, got " + str(type(folder_name)))
|
||||||
|
|
||||||
self.__queue.put(folder_name)
|
self.__queue.put(folder_name)
|
||||||
@ -131,7 +131,7 @@ class Scanner(Thread):
|
|||||||
scanned = 0
|
scanned = 0
|
||||||
while not self.__stopped.is_set() and to_scan:
|
while not self.__stopped.is_set() and to_scan:
|
||||||
path = to_scan.pop()
|
path = to_scan.pop()
|
||||||
for entry in scandir(path):
|
for entry in os.scandir(path):
|
||||||
if entry.name.startswith("."):
|
if entry.name.startswith("."):
|
||||||
continue
|
continue
|
||||||
if entry.is_symlink() and not self.__follow_symlinks:
|
if entry.is_symlink() and not self.__follow_symlinks:
|
||||||
@ -194,7 +194,7 @@ class Scanner(Thread):
|
|||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def scan_file(self, path_or_direntry):
|
def scan_file(self, path_or_direntry):
|
||||||
if isinstance(path_or_direntry, strtype):
|
if isinstance(path_or_direntry, str):
|
||||||
path = path_or_direntry
|
path = path_or_direntry
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
@ -286,7 +286,7 @@ class Scanner(Thread):
|
|||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def remove_file(self, path):
|
def remove_file(self, path):
|
||||||
if not isinstance(path, strtype):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(path)))
|
raise TypeError("Expecting string, got " + str(type(path)))
|
||||||
|
|
||||||
tr = Track.get(path=path)
|
tr = Track.get(path=path)
|
||||||
@ -298,9 +298,9 @@ class Scanner(Thread):
|
|||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def move_file(self, src_path, dst_path):
|
def move_file(self, src_path, dst_path):
|
||||||
if not isinstance(src_path, strtype):
|
if not isinstance(src_path, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(src_path)))
|
raise TypeError("Expecting string, got " + str(type(src_path)))
|
||||||
if not isinstance(dst_path, strtype):
|
if not isinstance(dst_path, str):
|
||||||
raise TypeError("Expecting string, got " + str(type(dst_path)))
|
raise TypeError("Expecting string, got " + str(type(dst_path)))
|
||||||
|
|
||||||
if src_path == dst_path:
|
if src_path == dst_path:
|
||||||
@ -326,7 +326,7 @@ class Scanner(Thread):
|
|||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def find_cover(self, dirpath):
|
def find_cover(self, dirpath):
|
||||||
if not isinstance(dirpath, strtype): # pragma: nocover
|
if not isinstance(dirpath, str): # pragma: nocover
|
||||||
raise TypeError("Expecting string, got " + str(type(dirpath)))
|
raise TypeError("Expecting string, got " + str(type(dirpath)))
|
||||||
|
|
||||||
if not os.path.exists(dirpath):
|
if not os.path.exists(dirpath):
|
||||||
@ -346,7 +346,7 @@ class Scanner(Thread):
|
|||||||
|
|
||||||
@db_session
|
@db_session
|
||||||
def add_cover(self, path):
|
def add_cover(self, path):
|
||||||
if not isinstance(path, strtype): # pragma: nocover
|
if not isinstance(path, str): # pragma: nocover
|
||||||
raise TypeError("Expecting string, got " + str(type(path)))
|
raise TypeError("Expecting string, got " + str(type(path)))
|
||||||
|
|
||||||
folder = Folder.get(path=os.path.dirname(path))
|
folder = Folder.get(path=os.path.dirname(path))
|
||||||
|
@ -18,7 +18,6 @@ from watchdog.events import PatternMatchingEventHandler
|
|||||||
|
|
||||||
from . import covers
|
from . import covers
|
||||||
from .db import Folder
|
from .db import Folder
|
||||||
from .py23 import dict, strtype
|
|
||||||
from .scanner import Scanner
|
from .scanner import Scanner
|
||||||
|
|
||||||
OP_SCAN = 1
|
OP_SCAN = 1
|
||||||
@ -267,7 +266,7 @@ class SupysonicWatcher(object):
|
|||||||
def add_folder(self, folder):
|
def add_folder(self, folder):
|
||||||
if isinstance(folder, Folder):
|
if isinstance(folder, Folder):
|
||||||
path = folder.path
|
path = folder.path
|
||||||
elif isinstance(folder, strtype):
|
elif isinstance(folder, str):
|
||||||
path = folder
|
path = folder
|
||||||
else:
|
else:
|
||||||
raise TypeError("Expecting string or Folder, got " + str(type(folder)))
|
raise TypeError("Expecting string or Folder, got " + str(type(folder)))
|
||||||
@ -279,7 +278,7 @@ class SupysonicWatcher(object):
|
|||||||
def remove_folder(self, folder):
|
def remove_folder(self, folder):
|
||||||
if isinstance(folder, Folder):
|
if isinstance(folder, Folder):
|
||||||
path = folder.path
|
path = folder.path
|
||||||
elif isinstance(folder, strtype):
|
elif isinstance(folder, str):
|
||||||
path = folder
|
path = folder
|
||||||
else:
|
else:
|
||||||
raise TypeError("Expecting string or Folder, got " + str(type(folder)))
|
raise TypeError("Expecting string or Folder, got " + str(type(folder)))
|
||||||
|
@ -11,7 +11,6 @@ import re
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from supysonic.managers.user import UserManager
|
from supysonic.managers.user import UserManager
|
||||||
from supysonic.py23 import strtype
|
|
||||||
|
|
||||||
from ..testbase import TestBase
|
from ..testbase import TestBase
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ class ApiTestBase(TestBase):
|
|||||||
|
|
||||||
if not isinstance(args, dict):
|
if not isinstance(args, dict):
|
||||||
raise TypeError("'args', expecting a dict, got " + type(args).__name__)
|
raise TypeError("'args', expecting a dict, got " + type(args).__name__)
|
||||||
if tag and not isinstance(tag, strtype):
|
if tag and not isinstance(tag, str):
|
||||||
raise TypeError("'tag', expecting a str, got " + type(tag).__name__)
|
raise TypeError("'tag', expecting a str, got " + type(tag).__name__)
|
||||||
|
|
||||||
args.update({"c": "tests", "v": "1.9.0"})
|
args.update({"c": "tests", "v": "1.9.0"})
|
||||||
|
@ -14,7 +14,6 @@ import flask.json
|
|||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
from supysonic.api.formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
from supysonic.api.formatters import JSONFormatter, JSONPFormatter, XMLFormatter
|
||||||
from supysonic.py23 import strtype
|
|
||||||
|
|
||||||
from ..testbase import TestBase
|
from ..testbase import TestBase
|
||||||
|
|
||||||
@ -98,7 +97,7 @@ class ResponseHelperJsonTestCase(TestBase, UnwrapperMixin.create_from(JSONFormat
|
|||||||
self.assertIn("list", d)
|
self.assertIn("list", d)
|
||||||
self.assertNotIn("emptyList", d)
|
self.assertNotIn("emptyList", d)
|
||||||
self.assertIn("subdict", d)
|
self.assertIn("subdict", d)
|
||||||
self.assertIsInstance(d["value"], strtype)
|
self.assertIsInstance(d["value"], str)
|
||||||
self.assertIsInstance(d["list"], list)
|
self.assertIsInstance(d["list"], list)
|
||||||
self.assertIsInstance(d["subdict"], dict)
|
self.assertIsInstance(d["subdict"], dict)
|
||||||
|
|
||||||
|
@ -15,12 +15,8 @@ import tempfile
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pony.orm import db_session
|
|
||||||
|
|
||||||
try: # Don't use io.StringIO on py2, it only accepts unicode and the CLI spits strs
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from pony.orm import db_session
|
||||||
|
|
||||||
from supysonic.db import Folder, User, init_database, release_database
|
from supysonic.db import Folder, User, init_database, release_database
|
||||||
from supysonic.cli import SupysonicCLI
|
from supysonic.cli import SupysonicCLI
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from supysonic.config import IniConfig
|
from supysonic.config import IniConfig
|
||||||
from supysonic.py23 import strtype
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigTestCase(unittest.TestCase):
|
class ConfigTestCase(unittest.TestCase):
|
||||||
@ -26,7 +25,7 @@ class ConfigTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertIsInstance(conf.TYPES["float"], float)
|
self.assertIsInstance(conf.TYPES["float"], float)
|
||||||
self.assertIsInstance(conf.TYPES["int"], int)
|
self.assertIsInstance(conf.TYPES["int"], int)
|
||||||
self.assertIsInstance(conf.TYPES["string"], strtype)
|
self.assertIsInstance(conf.TYPES["string"], str)
|
||||||
|
|
||||||
for t in ("bool", "switch", "yn"):
|
for t in ("bool", "switch", "yn"):
|
||||||
self.assertIsInstance(conf.BOOLEANS[t + "_false"], bool)
|
self.assertIsInstance(conf.BOOLEANS[t + "_false"], bool)
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
from supysonic import db
|
from supysonic import db
|
||||||
from supysonic.managers.user import UserManager
|
from supysonic.managers.user import UserManager
|
||||||
from supysonic.py23 import strtype
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import unittest
|
import unittest
|
||||||
|
Loading…
Reference in New Issue
Block a user