1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-10 04:02:17 +00:00

API as blueprint

Ref #76
This commit is contained in:
spl0k 2018-01-28 22:50:21 +01:00
parent aedda4f642
commit 0de87e64b0
11 changed files with 79 additions and 82 deletions

View File

@ -24,7 +24,7 @@ import binascii
import uuid
from flask import request
from flask import current_app as app
from flask import Blueprint
from pony.orm import db_session, ObjectNotFound
from ..managers.user import UserManager
@ -33,11 +33,10 @@ from ..py23 import dict
from .formatters import make_json_response, make_jsonp_response, make_xml_response
from .formatters import make_error_response_func
@app.before_request
def set_formatter():
if not request.path.startswith('/rest/'):
return
api = Blueprint('api', __name__)
@api.before_request
def set_formatter():
"""Return a function to create the response."""
f, callback = map(request.values.get, ['f', 'callback'])
if f == 'jsonp':
@ -58,11 +57,8 @@ def decode_password(password):
except:
return password
@app.before_request
@api.before_request
def authorize():
if not request.path.startswith('/rest/'):
return
error = request.error_formatter(40, 'Unauthorized'), 401
if request.authorization:
@ -84,11 +80,8 @@ def authorize():
request.username = username
request.user = user
@app.before_request
@api.before_request
def get_client_prefs():
if not request.path.startswith('/rest/'):
return
if 'c' not in request.values:
return request.error_formatter(10, 'Missing required parameter')
@ -101,11 +94,9 @@ def get_client_prefs():
request.client = client
@app.errorhandler(404)
def not_found(error):
if not request.path.startswith('/rest/'):
return error
#@api.errorhandler(404)
@api.route('/<path:invalid>', methods = [ 'GET', 'POST' ]) # blueprint 404 workaround
def not_found(*args, **kwargs):
return request.error_formatter(0, 'Not implemented'), 501
def get_entity(req, cls, param = 'id'):

View File

@ -22,14 +22,15 @@ import random
import uuid
from datetime import timedelta
from flask import request, current_app as app
from flask import request
from pony.orm import db_session, select, desc, avg, max, min, count
from ..db import Folder, Artist, Album, Track, RatingFolder, StarredFolder, StarredArtist, StarredAlbum, StarredTrack, User
from ..db import now
from ..py23 import dict
from . import api
@app.route('/rest/getRandomSongs.view', methods = [ 'GET', 'POST' ])
@api.route('/getRandomSongs.view', methods = [ 'GET', 'POST' ])
def rand_songs():
size = request.values.get('size', '10')
genre, fromYear, toYear, musicFolderId = map(request.values.get, [ 'genre', 'fromYear', 'toYear', 'musicFolderId' ])
@ -63,7 +64,7 @@ def rand_songs():
)
))
@app.route('/rest/getAlbumList.view', methods = [ 'GET', 'POST' ])
@api.route('/getAlbumList.view', methods = [ 'GET', 'POST' ])
def album_list():
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
if not ltype:
@ -106,7 +107,7 @@ def album_list():
)
))
@app.route('/rest/getAlbumList2.view', methods = [ 'GET', 'POST' ])
@api.route('/getAlbumList2.view', methods = [ 'GET', 'POST' ])
def album_list_id3():
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
if not ltype:
@ -147,7 +148,7 @@ def album_list_id3():
)
))
@app.route('/rest/getNowPlaying.view', methods = [ 'GET', 'POST' ])
@api.route('/getNowPlaying.view', methods = [ 'GET', 'POST' ])
@db_session
def now_playing():
query = User.select(lambda u: u.last_play is not None and u.last_play_date + timedelta(minutes = 3) > now())
@ -161,7 +162,7 @@ def now_playing():
)
))
@app.route('/rest/getStarred.view', methods = [ 'GET', 'POST' ])
@api.route('/getStarred.view', methods = [ 'GET', 'POST' ])
@db_session
def get_starred():
folders = select(s.starred for s in StarredFolder if s.user.id == request.user.id)
@ -174,7 +175,7 @@ def get_starred():
)
))
@app.route('/rest/getStarred2.view', methods = [ 'GET', 'POST' ])
@api.route('/getStarred2.view', methods = [ 'GET', 'POST' ])
@db_session
def get_starred_id3():
return request.formatter(dict(

View File

@ -21,7 +21,7 @@
import time
import uuid
from flask import request, current_app as app
from flask import current_app, request
from pony.orm import db_session, delete
from pony.orm import ObjectNotFound
@ -31,7 +31,7 @@ from ..db import RatingTrack, RatingFolder
from ..lastfm import LastFm
from ..py23 import dict
from . import get_entity
from . import api, get_entity
@db_session
def try_star(cls, starred_cls, eid):
@ -88,7 +88,7 @@ def merge_errors(errors):
return error
@app.route('/rest/star.view', methods = [ 'GET', 'POST' ])
@api.route('/star.view', methods = [ 'GET', 'POST' ])
def star():
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
@ -111,7 +111,7 @@ def star():
error = merge_errors(errors)
return request.formatter(dict(error = error), error = True) if error else request.formatter(dict())
@app.route('/rest/unstar.view', methods = [ 'GET', 'POST' ])
@api.route('/unstar.view', methods = [ 'GET', 'POST' ])
def unstar():
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
@ -134,7 +134,7 @@ def unstar():
error = merge_errors(errors)
return request.formatter(dict(error = error), error = True) if error else request.formatter(dict())
@app.route('/rest/setRating.view', methods = [ 'GET', 'POST' ])
@api.route('/setRating.view', methods = [ 'GET', 'POST' ])
def rate():
id, rating = map(request.values.get, [ 'id', 'rating' ])
if not id or not rating:
@ -172,7 +172,7 @@ def rate():
return request.formatter(dict())
@app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ])
@api.route('/scrobble.view', methods = [ 'GET', 'POST' ])
@db_session
def scrobble():
status, res = get_entity(request, Track)
@ -189,7 +189,7 @@ def scrobble():
else:
t = int(time.time())
lfm = LastFm(app.config['LASTFM'], User[request.user.id], app.logger)
lfm = LastFm(current_app.config['LASTFM'], User[request.user.id], current_app.logger)
if submission in (None, '', True, 'true', 'True', 1, '1'):
lfm.scrobble(res, t)

View File

@ -21,16 +21,16 @@
import string
import uuid
from flask import request, current_app as app
from flask import request
from pony.orm import db_session
from pony.orm import ObjectNotFound
from ..db import Folder, Artist, Album, Track
from ..py23 import dict
from . import get_entity
from . import api, get_entity
@app.route('/rest/getMusicFolders.view', methods = [ 'GET', 'POST' ])
@api.route('/getMusicFolders.view', methods = [ 'GET', 'POST' ])
@db_session
def list_folders():
return request.formatter(dict(
@ -42,7 +42,7 @@ def list_folders():
)
))
@app.route('/rest/getIndexes.view', methods = [ 'GET', 'POST' ])
@api.route('/getIndexes.view', methods = [ 'GET', 'POST' ])
@db_session
def list_indexes():
musicFolderId = request.values.get('musicFolderId')
@ -105,7 +105,7 @@ def list_indexes():
)
))
@app.route('/rest/getMusicDirectory.view', methods = [ 'GET', 'POST' ])
@api.route('/getMusicDirectory.view', methods = [ 'GET', 'POST' ])
@db_session
def show_directory():
status, res = get_entity(request, Folder)
@ -122,7 +122,7 @@ def show_directory():
return request.formatter(dict(directory = directory))
@app.route('/rest/getArtists.view', methods = [ 'GET', 'POST' ])
@api.route('/getArtists.view', methods = [ 'GET', 'POST' ])
@db_session
def list_artists():
# According to the API page, there are no parameters?
@ -148,7 +148,7 @@ def list_artists():
)
))
@app.route('/rest/getArtist.view', methods = [ 'GET', 'POST' ])
@api.route('/getArtist.view', methods = [ 'GET', 'POST' ])
@db_session
def artist_info():
status, res = get_entity(request, Artist)
@ -162,7 +162,7 @@ def artist_info():
return request.formatter(dict(artist = info))
@app.route('/rest/getAlbum.view', methods = [ 'GET', 'POST' ])
@api.route('/getAlbum.view', methods = [ 'GET', 'POST' ])
@db_session
def album_info():
status, res = get_entity(request, Album)
@ -174,7 +174,7 @@ def album_info():
return request.formatter(dict(album = info))
@app.route('/rest/getSong.view', methods = [ 'GET', 'POST' ])
@api.route('/getSong.view', methods = [ 'GET', 'POST' ])
@db_session
def track_info():
status, res = get_entity(request, Track)
@ -183,7 +183,7 @@ def track_info():
return request.formatter(dict(song = res.as_subsonic_child(request.user, request.client)))
@app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ])
@api.route('/getVideos.view', methods = [ 'GET', 'POST' ])
def list_videos():
return request.error_formatter(0, 'Video streaming not supported')

View File

@ -18,13 +18,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask import request, current_app as app
from flask import request
from pony.orm import db_session
from ..db import ChatMessage, User
from ..py23 import dict
from . import api
@app.route('/rest/getChatMessages.view', methods = [ 'GET', 'POST' ])
@api.route('/getChatMessages.view', methods = [ 'GET', 'POST' ])
def get_chat():
since = request.values.get('since')
try:
@ -39,7 +40,7 @@ def get_chat():
return request.formatter(dict(chatMessages = dict(chatMessage = [ msg.responsize() for msg in query ] )))
@app.route('/rest/addChatMessage.view', methods = [ 'GET', 'POST' ])
@api.route('/addChatMessage.view', methods = [ 'GET', 'POST' ])
def add_chat_message():
msg = request.values.get('message')
if not msg:

View File

@ -24,7 +24,8 @@ import os.path
import requests
import subprocess
from flask import request, send_file, Response, current_app as app
from flask import request, Response, send_file
from flask import current_app
from PIL import Image
from pony.orm import db_session
from xml.etree import ElementTree
@ -33,7 +34,7 @@ from .. import scanner
from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now
from ..py23 import dict
from . import get_entity
from . import api, get_entity
def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate):
if not base_cmdline:
@ -45,7 +46,7 @@ def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_f
]
return ret
@app.route('/rest/stream.view', methods = [ 'GET', 'POST' ])
@api.route('/stream.view', methods = [ 'GET', 'POST' ])
@db_session
def stream_media():
status, res = get_entity(request, Track)
@ -81,7 +82,7 @@ def stream_media():
dst_mimetype = mimetypes.guess_type('dummyname.' + dst_suffix, False)[0] or 'application/octet-stream'
if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate):
config = app.config['TRANSCODING']
config = current_app.config['TRANSCODING']
transcoder = config.get('transcoder_{}_{}'.format(src_suffix, dst_suffix))
decoder = config.get('decoder_' + src_suffix) or config.get('decoder')
encoder = config.get('encoder_' + dst_suffix) or config.get('encoder')
@ -89,7 +90,7 @@ def stream_media():
transcoder = config.get('transcoder')
if not transcoder:
message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix)
app.logger.info(message)
current_app.logger.info(message)
return request.error_formatter(0, message)
transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ])
@ -119,7 +120,7 @@ def stream_media():
dec_proc.wait()
proc.wait()
app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate))
current_app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate))
response = Response(transcode(), mimetype = dst_mimetype)
else:
response = send_file(res.path, mimetype = dst_mimetype, conditional=True)
@ -132,7 +133,7 @@ def stream_media():
return response
@app.route('/rest/download.view', methods = [ 'GET', 'POST' ])
@api.route('/download.view', methods = [ 'GET', 'POST' ])
def download_media():
with db_session:
status, res = get_entity(request, Track)
@ -141,7 +142,7 @@ def download_media():
return send_file(res.path, mimetype = res.content_type, conditional=True)
@app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ])
@api.route('/getCoverArt.view', methods = [ 'GET', 'POST' ])
def cover_art():
with db_session:
status, res = get_entity(request, Folder)
@ -164,7 +165,7 @@ def cover_art():
if size > im.size[0] and size > im.size[1]:
return send_file(os.path.join(res.path, 'cover.jpg'))
size_path = os.path.join(app.config['WEBAPP']['cache_dir'], str(size))
size_path = os.path.join(current_app.config['WEBAPP']['cache_dir'], str(size))
path = os.path.abspath(os.path.join(size_path, str(res.id)))
if os.path.exists(path):
return send_file(path, mimetype = 'image/jpeg')
@ -175,7 +176,7 @@ def cover_art():
im.save(path, 'JPEG')
return send_file(path, mimetype = 'image/jpeg')
@app.route('/rest/getLyrics.view', methods = [ 'GET', 'POST' ])
@api.route('/getLyrics.view', methods = [ 'GET', 'POST' ])
def lyrics():
artist, title = map(request.values.get, [ 'artist', 'title' ])
if not artist:
@ -188,14 +189,14 @@ def lyrics():
for track in query:
lyrics_path = os.path.splitext(track.path)[0] + '.txt'
if os.path.exists(lyrics_path):
app.logger.debug('Found lyrics file: ' + lyrics_path)
current_app.logger.debug('Found lyrics file: ' + lyrics_path)
try:
lyrics = read_file_as_unicode(lyrics_path)
except UnicodeError:
# Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or
# return no lyrics. Log it anyway.
app.logger.warning('Unsupported encoding for lyrics file ' + lyrics_path)
current_app.logger.warning('Unsupported encoding for lyrics file ' + lyrics_path)
continue
return request.formatter(dict(lyrics = dict(
@ -216,7 +217,7 @@ def lyrics():
_value_ = root.find('cl:Lyric', namespaces = ns).text
)))
except requests.exceptions.RequestException as e:
app.logger.warning('Error while requesting the ChartLyrics API: ' + str(e))
current_app.logger.warning('Error while requesting the ChartLyrics API: ' + str(e))
return request.formatter(dict(lyrics = dict()))
@ -228,13 +229,13 @@ def read_file_as_unicode(path):
for enc in encodings:
try:
contents = codecs.open(path, 'r', encoding = enc).read()
app.logger.debug('Read file {} with {} encoding'.format(path, enc))
current_app.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
app.logger.debug('Reading file {} with ascii encoding'.format(path))
current_app.logger.debug('Reading file {} with ascii encoding'.format(path))
return unicode(open(path, 'r').read())

View File

@ -20,16 +20,16 @@
import uuid
from flask import request, current_app as app
from flask import request
from pony.orm import db_session, rollback
from pony.orm import ObjectNotFound
from ..db import Playlist, User, Track
from ..py23 import dict
from . import get_entity
from . import api, get_entity
@app.route('/rest/getPlaylists.view', methods = [ 'GET', 'POST' ])
@api.route('/getPlaylists.view', methods = [ 'GET', 'POST' ])
def list_playlists():
query = Playlist.select(lambda p: p.user.id == request.user.id or p.public).order_by(Playlist.name)
@ -48,7 +48,7 @@ def list_playlists():
with db_session:
return request.formatter(dict(playlists = dict(playlist = [ p.as_subsonic_playlist(request.user) for p in query ] )))
@app.route('/rest/getPlaylist.view', methods = [ 'GET', 'POST' ])
@api.route('/getPlaylist.view', methods = [ 'GET', 'POST' ])
@db_session
def show_playlist():
status, res = get_entity(request, Playlist)
@ -62,7 +62,7 @@ def show_playlist():
info['entry'] = [ t.as_subsonic_child(request.user, request.client) for t in res.get_tracks() ]
return request.formatter(dict(playlist = info))
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
@api.route('/createPlaylist.view', methods = [ 'GET', 'POST' ])
@db_session
def create_playlist():
playlist_id, name = map(request.values.get, [ 'playlistId', 'name' ])
@ -104,7 +104,7 @@ def create_playlist():
return request.formatter(dict())
@app.route('/rest/deletePlaylist.view', methods = [ 'GET', 'POST' ])
@api.route('/deletePlaylist.view', methods = [ 'GET', 'POST' ])
@db_session
def delete_playlist():
status, res = get_entity(request, Playlist)
@ -117,7 +117,7 @@ def delete_playlist():
res.delete()
return request.formatter(dict())
@app.route('/rest/updatePlaylist.view', methods = [ 'GET', 'POST' ])
@api.route('/updatePlaylist.view', methods = [ 'GET', 'POST' ])
@db_session
def update_playlist():
status, res = get_entity(request, Playlist, 'playlistId')

View File

@ -20,13 +20,14 @@
from collections import OrderedDict
from datetime import datetime
from flask import request, current_app as app
from flask import request
from pony.orm import db_session, select
from ..db import Folder, Track, Artist, Album
from ..py23 import dict
from . import api
@app.route('/rest/search.view', methods = [ 'GET', 'POST' ])
@api.route('/search.view', methods = [ 'GET', 'POST' ])
def old_search():
artist, album, title, anyf, count, offset, newer_than = map(request.values.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ])
try:
@ -70,7 +71,7 @@ def old_search():
match = [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.client) for r in query[offset : offset + count] ]
)))
@app.route('/rest/search2.view', methods = [ 'GET', 'POST' ])
@api.route('/search2.view', methods = [ 'GET', 'POST' ])
def new_search():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
@ -99,7 +100,7 @@ def new_search():
('song', [ t.as_subsonic_child(request.user, request.client) for t in songs ])
))))
@app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
@api.route('/search3.view', methods = [ 'GET', 'POST' ])
def search_id3():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])

View File

@ -18,15 +18,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask import request, current_app as app
from flask import request
from ..py23 import dict
from . import api
@app.route('/rest/ping.view', methods = [ 'GET', 'POST' ])
@api.route('/ping.view', methods = [ 'GET', 'POST' ])
def ping():
return request.formatter(dict())
@app.route('/rest/getLicense.view', methods = [ 'GET', 'POST' ])
@api.route('/getLicense.view', methods = [ 'GET', 'POST' ])
def license():
return request.formatter(dict(license = dict(valid = True )))

View File

@ -18,16 +18,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask import request, current_app as app
from flask import request
from pony.orm import db_session
from ..db import User
from ..managers.user import UserManager
from ..py23 import dict
from . import decode_password
from . import api, decode_password
@app.route('/rest/getUser.view', methods = [ 'GET', 'POST' ])
@api.route('/getUser.view', methods = [ 'GET', 'POST' ])
def user_info():
username = request.values.get('username')
if username is None:
@ -43,7 +43,7 @@ def user_info():
return request.formatter(dict(user = user.as_subsonic_user()))
@app.route('/rest/getUsers.view', methods = [ 'GET', 'POST' ])
@api.route('/getUsers.view', methods = [ 'GET', 'POST' ])
def users_info():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
@ -51,7 +51,7 @@ def users_info():
with db_session:
return request.formatter(dict(users = dict(user = [ u.as_subsonic_user() for u in User.select() ] )))
@app.route('/rest/createUser.view', methods = [ 'GET', 'POST' ])
@api.route('/createUser.view', methods = [ 'GET', 'POST' ])
def user_add():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
@ -68,7 +68,7 @@ def user_add():
return request.formatter(dict())
@app.route('/rest/deleteUser.view', methods = [ 'GET', 'POST' ])
@api.route('/deleteUser.view', methods = [ 'GET', 'POST' ])
def user_del():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
@ -88,7 +88,7 @@ def user_del():
return request.formatter(dict())
@app.route('/rest/changePassword.view', methods = [ 'GET', 'POST' ])
@api.route('/changePassword.view', methods = [ 'GET', 'POST' ])
def user_changepass():
username, password = map(request.values.get, [ 'username', 'password' ])
if not username or not password:

View File

@ -65,7 +65,8 @@ def create_application(config = None):
if app.config['WEBAPP']['mount_webui']:
from . import frontend
if app.config['WEBAPP']['mount_api']:
from . import api
from .api import api
app.register_blueprint(api, url_prefix = '/rest')
return app