diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 7662166..a1e8bc1 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -9,7 +9,7 @@ API_VERSION = "1.10.2" import binascii import uuid - +import functools from flask import request from flask import Blueprint from pony.orm import ObjectNotFound @@ -24,6 +24,18 @@ from .formatters import JSONFormatter, JSONPFormatter, XMLFormatter api = Blueprint("api", __name__) +def api_routing(endpoint): + def decorator(func): + viewendpoint="{}.view".format(endpoint) + @api.route(endpoint, methods=["GET", "POST"]) + @api.route(viewendpoint, methods=["GET", "POST"]) + @functools.wraps(func) + def wrapper(*args, **kwargs): + return func(*args,**kwargs) + return wrapper + return decorator + + @api.before_request def set_formatter(): """Return a function to create the response.""" diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py index 72ddae1..6fa2f96 100644 --- a/supysonic/api/albums_songs.py +++ b/supysonic/api/albums_songs.py @@ -21,11 +21,11 @@ from ..db import ( ) from ..db import now -from . import api +from . import api, api_routing from .exceptions import GenericError, NotFound -@api.route("/getRandomSongs.view", methods=["GET", "POST"]) +@api_routing("/getRandomSongs") def rand_songs(): size = request.values.get("size", "10") genre, fromYear, toYear, musicFolderId = map( @@ -66,7 +66,7 @@ def rand_songs(): ) -@api.route("/getAlbumList.view", methods=["GET", "POST"]) +@api_routing("/getAlbumList") def album_list(): ltype = request.values["type"] @@ -129,7 +129,7 @@ def album_list(): ) -@api.route("/getAlbumList2.view", methods=["GET", "POST"]) +@api_routing("/getAlbumList2") def album_list_id3(): ltype = request.values["type"] @@ -183,7 +183,7 @@ def album_list_id3(): ) -@api.route("/getSongsByGenre.view", methods=["GET", "POST"]) +@api_routing("/getSongsByGenre") def songs_by_genre(): genre = request.values["genre"] @@ -198,7 +198,7 @@ def songs_by_genre(): ) -@api.route("/getNowPlaying.view", methods=["GET", "POST"]) +@api_routing("/getNowPlaying") def now_playing(): query = User.select( lambda u: u.last_play is not None @@ -221,7 +221,7 @@ def now_playing(): ) -@api.route("/getStarred.view", methods=["GET", "POST"]) +@api_routing("/getStarred") def get_starred(): folders = select(s.starred for s in StarredFolder if s.user.id == request.user.id) @@ -246,7 +246,7 @@ def get_starred(): ) -@api.route("/getStarred2.view", methods=["GET", "POST"]) +@api_routing("/getStarred2") def get_starred_id3(): return request.formatter( "starred2", diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 3d44876..529aaae 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -16,7 +16,7 @@ from ..db import StarredTrack, StarredAlbum, StarredArtist, StarredFolder from ..db import RatingTrack, RatingFolder from ..lastfm import LastFm -from . import api, get_entity, get_entity_id +from . import api, get_entity, get_entity_id, api_routing from .exceptions import AggregateException, GenericError, MissingParameter, NotFound @@ -108,17 +108,17 @@ def handle_star_request(func): return request.formatter.empty -@api.route("/star.view", methods=["GET", "POST"]) +@api_routing("/star") def star(): return handle_star_request(star_single) -@api.route("/unstar.view", methods=["GET", "POST"]) +@api_routing("/unstar") def unstar(): return handle_star_request(unstar_single) -@api.route("/setRating.view", methods=["GET", "POST"]) +@api_routing("/setRating") def rate(): id = request.values["id"] rating = request.values["rating"] @@ -172,7 +172,7 @@ def rate(): return request.formatter.empty -@api.route("/scrobble.view", methods=["GET", "POST"]) +@api_routing("/scrobble") def scrobble(): res = get_entity(Track) t, submission = map(request.values.get, ["time", "submission"]) diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index 728ea9b..a3a649d 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -13,10 +13,10 @@ from pony.orm import ObjectNotFound, select, count from ..db import Folder, Artist, Album, Track -from . import api, get_entity, get_entity_id +from . import api, get_entity, get_entity_id, api_routing -@api.route("/getMusicFolders.view", methods=["GET", "POST"]) +@api_routing("/getMusicFolders") def list_folders(): return request.formatter( "musicFolders", @@ -49,7 +49,7 @@ def ignored_articles_str(): return " ".join(articles.split()) -@api.route("/getIndexes.view", methods=["GET", "POST"]) +@api_routing("/getIndexes") def list_indexes(): musicFolderId = request.values.get("musicFolderId") ifModifiedSince = request.values.get("ifModifiedSince") @@ -122,7 +122,7 @@ def list_indexes(): ) -@api.route("/getMusicDirectory.view", methods=["GET", "POST"]) +@api_routing("/getMusicDirectory") def show_directory(): res = get_entity(Folder) return request.formatter( @@ -130,7 +130,7 @@ def show_directory(): ) -@api.route("/getGenres.view", methods=["GET", "POST"]) +@api_routing("/getGenres") def list_genres(): return request.formatter( "genres", @@ -145,7 +145,7 @@ def list_genres(): ) -@api.route("/getArtists.view", methods=["GET", "POST"]) +@api_routing("/getArtists") def list_artists(): # According to the API page, there are no parameters? indexes = dict() @@ -183,7 +183,7 @@ def list_artists(): ) -@api.route("/getArtist.view", methods=["GET", "POST"]) +@api_routing("/getArtist") def artist_info(): res = get_entity(Artist) info = res.as_subsonic_artist(request.user) @@ -197,7 +197,7 @@ def artist_info(): return request.formatter("artist", info) -@api.route("/getAlbum.view", methods=["GET", "POST"]) +@api_routing("/getAlbum") def album_info(): res = get_entity(Album) info = res.as_subsonic_album(request.user) @@ -209,7 +209,7 @@ def album_info(): return request.formatter("album", info) -@api.route("/getSong.view", methods=["GET", "POST"]) +@api_routing("/getSong") def track_info(): res = get_entity(Track) return request.formatter( diff --git a/supysonic/api/chat.py b/supysonic/api/chat.py index 486ed8c..1f519d7 100644 --- a/supysonic/api/chat.py +++ b/supysonic/api/chat.py @@ -8,10 +8,10 @@ from flask import request from ..db import ChatMessage -from . import api +from . import api, api_routing -@api.route("/getChatMessages.view", methods=["GET", "POST"]) +@api_routing("/getChatMessages") def get_chat(): since = request.values.get("since") since = int(since) / 1000 if since else None @@ -25,7 +25,7 @@ def get_chat(): ) -@api.route("/addChatMessage.view", methods=["GET", "POST"]) +@api_routing("/addChatMessage") def add_chat_message(): msg = request.values["message"] ChatMessage(user=request.user, message=msg) diff --git a/supysonic/api/jukebox.py b/supysonic/api/jukebox.py index 63276d6..9269b2f 100644 --- a/supysonic/api/jukebox.py +++ b/supysonic/api/jukebox.py @@ -14,11 +14,11 @@ from ..daemon import DaemonClient from ..daemon.exceptions import DaemonUnavailableError from ..db import Track -from . import api +from . import api, api_routing from .exceptions import GenericError, MissingParameter, Forbidden -@api.route("/jukeboxControl.view", methods=["GET", "POST"]) +@api_routing("/jukeboxControl") def jukebox_control(): if not request.user.jukebox and not request.user.admin: raise Forbidden() diff --git a/supysonic/api/media.py b/supysonic/api/media.py index 3271d31..32c6263 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -28,7 +28,7 @@ from zipstream import ZipFile from ..cache import CacheMiss from ..db import Track, Album, Folder, now -from . import api, get_entity, get_entity_id +from . import api, get_entity, get_entity_id, api_routing from .exceptions import ( GenericError, NotFound, @@ -63,7 +63,7 @@ def prepare_transcoding_cmdline( return ret -@api.route("/stream.view", methods=["GET", "POST"]) +@api_routing("/stream") def stream_media(): res = get_entity(Track) @@ -218,7 +218,7 @@ def stream_media(): return response -@api.route("/download.view", methods=["GET", "POST"]) +@api_routing("/download") def download_media(): id = request.values["id"] @@ -257,7 +257,7 @@ def download_media(): return resp -@api.route("/getCoverArt.view", methods=["GET", "POST"]) +@api_routing("/getCoverArt") def cover_art(): cache = current_app.cache @@ -316,7 +316,7 @@ def cover_art(): return send_file(cache.get(cache_key), mimetype=mimetype) -@api.route("/getLyrics.view", methods=["GET", "POST"]) +@api_routing("/getLyrics") def lyrics(): artist = request.values["artist"] title = request.values["title"] diff --git a/supysonic/api/playlists.py b/supysonic/api/playlists.py index bb0b60f..46643c7 100644 --- a/supysonic/api/playlists.py +++ b/supysonic/api/playlists.py @@ -11,11 +11,11 @@ from flask import request from ..db import Playlist, User, Track -from . import api, get_entity +from . import api, get_entity, api_routing from .exceptions import Forbidden, MissingParameter, NotFound -@api.route("/getPlaylists.view", methods=["GET", "POST"]) +@api_routing("/getPlaylists") def list_playlists(): query = Playlist.select( lambda p: p.user.id == request.user.id or p.public @@ -40,7 +40,7 @@ def list_playlists(): ) -@api.route("/getPlaylist.view", methods=["GET", "POST"]) +@api_routing("/getPlaylist") def show_playlist(): res = get_entity(Playlist) if res.user.id != request.user.id and not res.public and not request.user.admin: @@ -53,7 +53,7 @@ def show_playlist(): return request.formatter("playlist", info) -@api.route("/createPlaylist.view", methods=["GET", "POST"]) +@api_routing("/createPlaylist") def create_playlist(): playlist_id, name = map(request.values.get, ["playlistId", "name"]) # songId actually doesn't seem to be required @@ -82,7 +82,7 @@ def create_playlist(): return request.formatter.empty -@api.route("/deletePlaylist.view", methods=["GET", "POST"]) +@api_routing("/deletePlaylist") def delete_playlist(): res = get_entity(Playlist) if res.user.id != request.user.id and not request.user.admin: @@ -92,7 +92,7 @@ def delete_playlist(): return request.formatter.empty -@api.route("/updatePlaylist.view", methods=["GET", "POST"]) +@api_routing("/updatePlaylist") def update_playlist(): res = get_entity(Playlist, "playlistId") if res.user.id != request.user.id and not request.user.admin: diff --git a/supysonic/api/radio.py b/supysonic/api/radio.py index 4bb58d8..c4647c5 100644 --- a/supysonic/api/radio.py +++ b/supysonic/api/radio.py @@ -9,11 +9,11 @@ from flask import request from ..db import RadioStation -from . import api, get_entity +from . import api, get_entity, api_routing from .exceptions import Forbidden, MissingParameter -@api.route("/getInternetRadioStations.view", methods=["GET", "POST"]) +@api_routing("/getInternetRadioStations") def get_radio_stations(): query = RadioStation.select().sort_by(RadioStation.name) return request.formatter( @@ -22,7 +22,7 @@ def get_radio_stations(): ) -@api.route("/createInternetRadioStation.view", methods=["GET", "POST"]) +@api_routing("/createInternetRadioStation") def create_radio_station(): if not request.user.admin: raise Forbidden() @@ -39,7 +39,7 @@ def create_radio_station(): return request.formatter.empty -@api.route("/updateInternetRadioStation.view", methods=["GET", "POST"]) +@api_routing("/updateInternetRadioStation") def update_radio_station(): if not request.user.admin: raise Forbidden() @@ -61,7 +61,7 @@ def update_radio_station(): return request.formatter.empty -@api.route("/deleteInternetRadioStation.view", methods=["GET", "POST"]) +@api_routing("/deleteInternetRadioStation") def delete_radio_station(): if not request.user.admin: raise Forbidden() diff --git a/supysonic/api/scan.py b/supysonic/api/scan.py index 7537269..d0c704c 100644 --- a/supysonic/api/scan.py +++ b/supysonic/api/scan.py @@ -12,12 +12,12 @@ from flask import current_app from ..daemon.client import DaemonClient from ..daemon.exceptions import DaemonUnavailableError -from . import api +from . import api, api_routing from .user import admin_only from .exceptions import ServerError -@api.route("/startScan.view", methods=["GET", "POST"]) +@api_routing("/startScan") @admin_only def startScan(): try: @@ -35,7 +35,7 @@ def startScan(): ) -@api.route("/getScanStatus.view", methods=["GET", "POST"]) +@api_routing("/getScanStatus") @admin_only def getScanStatus(): try: diff --git a/supysonic/api/search.py b/supysonic/api/search.py index a15a6b6..d289d8a 100644 --- a/supysonic/api/search.py +++ b/supysonic/api/search.py @@ -12,11 +12,11 @@ from pony.orm import select from ..db import Folder, Track, Artist, Album -from . import api +from . import api, api_routing from .exceptions import MissingParameter -@api.route("/search.view", methods=["GET", "POST"]) +@api_routing("/search") def old_search(): artist, album, title, anyf, count, offset, newer_than = map( request.values.get, @@ -83,7 +83,7 @@ def old_search(): ) -@api.route("/search2.view", methods=["GET", "POST"]) +@api_routing("/search2") def new_search(): query = request.values["query"] ( @@ -135,7 +135,7 @@ def new_search(): ) -@api.route("/search3.view", methods=["GET", "POST"]) +@api_routing("/search3") def search_id3(): query = request.values["query"] ( diff --git a/supysonic/api/system.py b/supysonic/api/system.py index b8c724a..0a0b440 100644 --- a/supysonic/api/system.py +++ b/supysonic/api/system.py @@ -8,14 +8,14 @@ from flask import request -from . import api +from . import api, api_routing -@api.route("/ping.view", methods=["GET", "POST"]) +@api_routing("/ping") def ping(): return request.formatter.empty -@api.route("/getLicense.view", methods=["GET", "POST"]) +@api_routing("/getLicense") def license(): return request.formatter("license", dict(valid=True)) diff --git a/supysonic/api/user.py b/supysonic/api/user.py index 19546d1..18a402c 100644 --- a/supysonic/api/user.py +++ b/supysonic/api/user.py @@ -11,7 +11,7 @@ from functools import wraps from ..db import User from ..managers.user import UserManager -from . import api, decode_password +from . import api, decode_password, api_routing from .exceptions import Forbidden, NotFound @@ -25,7 +25,7 @@ def admin_only(f): return decorated -@api.route("/getUser.view", methods=["GET", "POST"]) +@api_routing("/getUser") def user_info(): username = request.values["username"] @@ -39,7 +39,7 @@ def user_info(): return request.formatter("user", user.as_subsonic_user()) -@api.route("/getUsers.view", methods=["GET", "POST"]) +@api_routing("/getUsers") @admin_only def users_info(): return request.formatter( @@ -57,7 +57,7 @@ def get_roles_dict(): return roles -@api.route("/createUser.view", methods=["GET", "POST"]) +@api_routing("/createUser") @admin_only def user_add(): username = request.values["username"] @@ -71,7 +71,7 @@ def user_add(): return request.formatter.empty -@api.route("/deleteUser.view", methods=["GET", "POST"]) +@api_routing("/deleteUser") @admin_only def user_del(): username = request.values["username"] @@ -80,7 +80,7 @@ def user_del(): return request.formatter.empty -@api.route("/changePassword.view", methods=["GET", "POST"]) +@api_routing("/changePassword") def user_changepass(): username = request.values["username"] password = request.values["password"] @@ -94,7 +94,7 @@ def user_changepass(): return request.formatter.empty -@api.route("/updateUser.view", methods=["GET", "POST"]) +@api_routing("/updateUser") @admin_only def user_edit(): username = request.values["username"]