org.madsonic.controller.RESTController.java Source code

Java tutorial

Introduction

Here is the source code for org.madsonic.controller.RESTController.java

Source

/*
 This file is part of Subsonic.
    
 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 Subsonic is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
    
 Copyright 2009 (C) Sindre Mehus
 */
package org.madsonic.controller;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

import org.madsonic.Logger;
import org.madsonic.ajax.ChatService;
import org.madsonic.ajax.LyricsInfo;
import org.madsonic.ajax.LyricsService;
import org.madsonic.ajax.PlayQueueService;
import org.madsonic.command.UserSettingsCommand;
import org.madsonic.controller.ShareSettingsController.ShareInfo;
import org.madsonic.dao.AlbumDao;
import org.madsonic.dao.ArtistDao;
import org.madsonic.dao.BookmarkDao;
import org.madsonic.dao.MediaFileDao;
import org.madsonic.domain.Album;
import org.madsonic.domain.Artist;
import org.madsonic.domain.Bookmark;
import org.madsonic.domain.Genre;
import org.madsonic.domain.GenreSearchCriteria;
import org.madsonic.domain.InternetRadio;
import org.madsonic.domain.MediaFile;
import org.madsonic.domain.MoodsSearchCriteria;
import org.madsonic.domain.MultiSearchCriteria;
import org.madsonic.domain.MusicFolder;
import org.madsonic.domain.MusicIndex;
import org.madsonic.domain.PlayQueue;
import org.madsonic.domain.Player;
import org.madsonic.domain.PlayerTechnology;
import org.madsonic.domain.Playlist;
import org.madsonic.domain.PodcastChannel;
import org.madsonic.domain.PodcastEpisode;
import org.madsonic.domain.RandomSearchCriteria;
import org.madsonic.domain.SearchCriteria;
import org.madsonic.domain.SearchResult;
import org.madsonic.domain.Share;
import org.madsonic.domain.TranscodeScheme;
import org.madsonic.domain.TransferStatus;
import org.madsonic.domain.User;
import org.madsonic.domain.UserSettings;
import org.madsonic.service.AudioScrobblerService;
import org.madsonic.service.HotService;
import org.madsonic.service.JukeboxService;
import org.madsonic.service.LastFMService;
import org.madsonic.service.MediaFileService;
import org.madsonic.service.MediaScannerService;
import org.madsonic.service.MusicIndexService;
import org.madsonic.service.PlayerService;
import org.madsonic.service.PlaylistService;
import org.madsonic.service.PodcastService;
import org.madsonic.service.RatingService;
import org.madsonic.service.SearchService;
import org.madsonic.service.SecurityService;
import org.madsonic.service.SettingsService;
import org.madsonic.service.ShareService;
import org.madsonic.service.StatusService;
import org.madsonic.service.TranscodingService;
import org.madsonic.util.Pair;
import org.madsonic.util.StringUtil;
import org.madsonic.util.XMLBuilder;
import static org.madsonic.domain.MediaFile.MediaType.MUSIC;

import java.util.concurrent.ConcurrentHashMap;

import static org.madsonic.security.RESTRequestParameterProcessingFilter.decrypt;
import static org.madsonic.util.XMLBuilder.Attribute;
import static org.madsonic.util.XMLBuilder.AttributeSet;
import static org.springframework.web.bind.ServletRequestUtils.*;

/**
 * Multi-controller used for the REST API.
 * <p/>
 * For documentation, please refer to api.jsp.
 * <p/>
 * Note: Exceptions thrown from the methods are intercepted by RESTFilter.
 *
 * @author Sindre Mehus
 */
public class RESTController extends MultiActionController {

    private static final Logger LOG = Logger.getLogger(RESTController.class);

    private SettingsService settingsService;
    private SecurityService securityService;
    private PlayerService playerService;

    private MediaScannerService mediaScannerService;
    private MediaFileService mediaFileService;
    private MusicIndexService musicIndexService;
    private TranscodingService transcodingService;
    private DownloadController downloadController;
    private CoverArtController coverArtController;
    private AvatarController avatarController;
    private UserSettingsController userSettingsController;
    private LeftController leftController;
    private HomeController homeController;
    private StatusService statusService;
    private StreamController streamController;
    private HLSController hlsController;
    private ShareService shareService;
    private PlaylistService playlistService;
    private ChatService chatService;
    private LyricsService lyricsService;
    private PlayQueueService playQueueService;
    private JukeboxService jukeboxService;
    private AudioScrobblerService audioScrobblerService;
    private PodcastService podcastService;
    private RatingService ratingService;
    private SearchService searchService;
    private HotService hotService;
    private LastFMService lastFMService;
    private MediaFileDao mediaFileDao;
    private ArtistDao artistDao;
    private AlbumDao albumDao;
    private BookmarkDao bookmarkDao;

    private final Map<BookmarkKey, Bookmark> bookmarkCache = new ConcurrentHashMap<BookmarkKey, Bookmark>();

    public void init() {
        refreshBookmarkCache();
    }

    private void refreshBookmarkCache() {
        bookmarkCache.clear();
        for (Bookmark bookmark : bookmarkDao.getBookmarks()) {
            bookmarkCache.put(BookmarkKey.forBookmark(bookmark), bookmark);
        }
    }

    public void ping(HttpServletRequest request, HttpServletResponse response) throws Exception {
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void getLicense(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        String email = settingsService.getLicenseEmail();
        String key = settingsService.getLicenseCode();
        Date date = settingsService.getLicenseDate();
        boolean valid = settingsService.isLicenseValid();

        AttributeSet attributes = new AttributeSet();
        attributes.add("valid", valid);
        if (valid) {
            attributes.add("email", email);
            attributes.add("key", key);
            attributes.add("date", StringUtil.toISO8601(date));
        }

        builder.add("license", attributes, true);
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getMusicFolders(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("musicFolders", false);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        for (MusicFolder musicFolder : settingsService.getAllMusicFolders(userGroupId,
                settingsService.isSortMediaFileFolder())) {
            AttributeSet attributes = new AttributeSet();
            attributes.add("id", musicFolder.getId());
            attributes.add("name", musicFolder.getName());
            builder.add("musicFolder", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getIndexes(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        String username = securityService.getCurrentUser(request).getUsername();
        int userGroupId = securityService.getCurrentUserGroupId(request);

        long ifModifiedSince = getLongParameter(request, "ifModifiedSince", 0L);
        long lastModified = leftController.getLastModified(request);

        if (lastModified <= ifModifiedSince) {
            builder.endAll();
            response.getWriter().print(builder);
            return;
        }

        builder.add("indexes", false, new Attribute("lastModified", lastModified),
                new Attribute("ignoredArticles", settingsService.getIgnoredArticles()));
        List<MusicFolder> musicFolders = settingsService.getAllMusicFolders(userGroupId,
                settingsService.isSortMediaFileFolder());
        Integer musicFolderId = getIntParameter(request, "musicFolderId");
        if (musicFolderId != null) {
            for (MusicFolder musicFolder : musicFolders) {
                if (musicFolderId.equals(musicFolder.getId())) {
                    musicFolders = Arrays.asList(musicFolder);
                    break;
                }
            }
        }

        List<MediaFile> shortcuts = leftController.getShortcuts(musicFolders,
                settingsService.getShortcutsAsArray());
        for (MediaFile shortcut : shortcuts) {
            builder.add("shortcut", createAttributesForArtist(shortcut, username), true);
        }

        SortedMap<MusicIndex, SortedSet<MusicIndex.SortableArtistforGenre>> indexedArtists = leftController
                .getMusicFolderContent(musicFolders, null, "1", false).getIndexedArtists();

        for (Map.Entry<MusicIndex, SortedSet<MusicIndex.SortableArtistforGenre>> entry : indexedArtists
                .entrySet()) {
            builder.add("index", "name", entry.getKey().getIndex(), false);

            for (MusicIndex.SortableArtistforGenre artist : entry.getValue()) {
                for (MediaFile mediaFile : artist.getMediaFiles()) {
                    if (mediaFile.isDirectory()) {
                        Date starredDate = mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username);
                        builder.add("artist", true, new Attribute("name", artist.getName()),
                                new Attribute("id", mediaFile.getId()),
                                new Attribute("starred", StringUtil.toISO8601(starredDate)));
                    }
                }
            }
            builder.end();
        }

        // Add children
        Player player = playerService.getPlayer(request, response);
        List<MediaFile> singleSongs = leftController.getSingleSongs(musicFolders, false);

        for (MediaFile singleSong : singleSongs) {
            builder.add("child", createAttributesForMediaFile(player, singleSong, username), true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getGenres(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("genres", false);

        for (Genre genre : mediaFileDao.getGenres()) {

            String genreName = StringEscapeUtils.escapeXml(genre.getName().toString());

            AttributeSet attributes = new AttributeSet();
            attributes.add("songCount", genre.getSongCount());
            attributes.add("albumCount", genre.getAlbumCount());
            attributes.add("artistCount", genre.getArtistCount());

            builder.add("genre", (Iterable<Attribute>) attributes, genreName, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getArtistGenres(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("artistsgenres", false);

        for (String genre : mediaFileDao.getArtistGenres()) {
            genre = StringEscapeUtils.escapeXml(genre);
            builder.add("genre", (Iterable<Attribute>) null, genre, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getArtistsByGenre(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        UserSettings userSettings = settingsService.getUserSettings(username);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("artistsByGenre", false);

        String genre = getRequiredStringParameter(request, "genre");

        int offset = getIntParameter(request, "offset", 0);
        int count = getIntParameter(request, "count", 10);
        count = Math.max(0, Math.min(count, 500));

        for (MediaFile mediaFile : mediaFileDao.getArtistsByGenre(genre, offset, count, userGroupId)) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getSongsByGenre(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("songsByGenre", false);

        String genre = getRequiredStringParameter(request, "genre");
        int offset = getIntParameter(request, "offset", 0);
        int count = getIntParameter(request, "count", 10);
        count = Math.max(0, Math.min(count, 500));

        for (MediaFile mediaFile : mediaFileDao.getSongsByGenre(genre, offset, count)) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getArtists(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        String username = securityService.getCurrentUsername(request);

        builder.add("artists", "ignoredArticles", settingsService.getIgnoredArticles(), false);

        List<Artist> artists = artistDao.getAlphabetialArtists(0, Integer.MAX_VALUE);
        SortedMap<MusicIndex, SortedSet<MusicIndex.SortableArtistWithArtist>> indexedArtists = musicIndexService
                .getIndexedArtists(artists, 1); //TODO: fixed Default Index 
        for (Map.Entry<MusicIndex, SortedSet<MusicIndex.SortableArtistWithArtist>> entry : indexedArtists
                .entrySet()) {

            builder.add("index", "name", entry.getKey().getIndex(), false);
            for (MusicIndex.SortableArtistWithArtist sortableArtist : entry.getValue()) {
                AttributeSet attributes = createAttributesForArtist(sortableArtist.getArtist(), username);
                builder.add("artist", attributes, true);
            }
            builder.end();
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    private AttributeSet createAttributesForArtist(Artist artist, String username) {
        AttributeSet attributes = new AttributeSet();
        attributes.add("id", artist.getId());
        attributes.add("name", artist.getName());
        attributes.add("genre", artist.getGenre());
        if (artist.getCoverArtPath() != null) {
            attributes.add("coverArt", CoverArtController.ARTIST_COVERART_PREFIX + artist.getId());
        }
        attributes.add("albumCount", artist.getAlbumCount());
        attributes.add("starred", StringUtil.toISO8601(artistDao.getArtistStarredDate(artist.getId(), username)));
        return attributes;
    }

    private AttributeSet createAttributesForArtist(MediaFile artist, String username) {
        AttributeSet attributes = new AttributeSet();
        attributes.add("id", artist.getId());
        attributes.add("name", artist.getName());
        attributes.add("starred",
                StringUtil.toISO8601(mediaFileDao.getMediaFileStarredDate(artist.getId(), username)));
        return attributes;
    }

    public void getArtist(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        String username = securityService.getCurrentUsername(request);
        int id = getRequiredIntParameter(request, "id");
        Artist artist = artistDao.getArtist(id);
        if (artist == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Artist not found.");
            return;
        }

        builder.add("artist", createAttributesForArtist(artist, username), false);
        for (Album album : albumDao.getAlbumsForArtist(artist.getName())) {
            builder.add("album", createAttributesForAlbum(album, username), true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    private AttributeSet createAttributesForAlbum(Album album, String username) {
        AttributeSet attributes;
        attributes = new AttributeSet();
        attributes.add("id", album.getId());
        attributes.add("name", album.getName());
        attributes.add("artist", album.getArtist());
        if (album.getArtist() != null) {
            Artist artist = artistDao.getArtist(album.getArtist());
            if (artist != null) {
                attributes.add("artistId", artist.getId());
            }
        }
        if (album.getCoverArtPath() != null) {
            attributes.add("coverArt", CoverArtController.ALBUM_COVERART_PREFIX + album.getId());
        }
        attributes.add("songCount", album.getSongCount());
        attributes.add("duration", album.getDurationSeconds());
        attributes.add("created", StringUtil.toISO8601(album.getCreated()));
        attributes.add("starred", StringUtil.toISO8601(albumDao.getAlbumStarredDate(album.getId(), username)));
        attributes.add("year", album.getYear());
        attributes.add("genre", album.getGenre());

        return attributes;
    }

    private AttributeSet createAttributesForPlaylist(Playlist playlist) {
        AttributeSet attributes;
        attributes = new AttributeSet();
        attributes.add("id", playlist.getId());
        attributes.add("name", playlist.getName());
        attributes.add("comment", playlist.getComment());
        attributes.add("owner", playlist.getUsername());
        attributes.add("public", playlist.isPublic());
        attributes.add("songCount", playlist.getFileCount());
        attributes.add("duration", playlist.getDurationSeconds());
        attributes.add("created", StringUtil.toISO8601(playlist.getCreated()));
        return attributes;
    }

    public void getAlbum(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        int id = getRequiredIntParameter(request, "id");
        Album album = albumDao.getAlbum(id);
        if (album == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Album not found.");
            return;
        }

        builder.add("album", createAttributesForAlbum(album, username), false);

        //TODO:REST albumname
        for (MediaFile mediaFile : mediaFileDao.getSongsForAlbum(album.getArtist(), album.getNameid3())) {
            builder.add("song", createAttributesForMediaFile(player, mediaFile, username), true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getSong(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        int id = getRequiredIntParameter(request, "id");
        MediaFile song = mediaFileDao.getMediaFile(id);
        if (song == null || song.isDirectory()) {
            error(request, response, ErrorCode.NOT_FOUND, "Song not found.");
            return;
        }
        builder.add("song", createAttributesForMediaFile(player, song, username), true);

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getMusicDirectory(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        int id = getRequiredIntParameter(request, "id");
        MediaFile dir = mediaFileService.getMediaFile(id, userGroupId);
        if (dir == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Directory not found");
            return;
        }

        MediaFile parent = mediaFileService.getParentOf(dir);
        AttributeSet attributes = new AttributeSet();
        attributes.add("id", id);
        try {
            if (!mediaFileService.isRoot(parent)) {
                attributes.add("parent", parent.getId());
            }
        } catch (SecurityException x) {
            // Ignored.
        }
        attributes.add("name", dir.getName());
        attributes.add("starred", StringUtil.toISO8601(mediaFileDao.getMediaFileStarredDate(id, username)));

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("directory", attributes, false);

        for (MediaFile child : mediaFileService.getChildrenOf(dir, true, true, true)) {
            attributes = createAttributesForMediaFile(player, child, username);
            builder.add("child", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    @Deprecated
    public void search(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        String any = request.getParameter("any");
        String artist = request.getParameter("artist");
        String album = request.getParameter("album");
        String title = request.getParameter("title");

        StringBuilder query = new StringBuilder();
        if (any != null) {
            query.append(any).append(" ");
        }
        if (artist != null) {
            query.append(artist).append(" ");
        }
        if (album != null) {
            query.append(album).append(" ");
        }
        if (title != null) {
            query.append(title);
        }

        SearchCriteria criteria = new SearchCriteria();
        criteria.setQuery(query.toString().trim());
        criteria.setCount(getIntParameter(request, "count", 20));
        criteria.setOffset(getIntParameter(request, "offset", 0));

        SearchResult result = searchService.search(criteria, SearchService.IndexType.SONG, userGroupId);
        builder.add("searchResult", false, new Attribute("offset", result.getOffset()),
                new Attribute("totalHits", result.getTotalHits()));

        for (MediaFile mediaFile : result.getMediaFiles()) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("match", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void search2(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        builder.add("searchResult2", false);

        String query = request.getParameter("query");
        SearchCriteria criteria = new SearchCriteria();
        criteria.setQuery(StringUtils.trimToEmpty(query));
        criteria.setCount(getIntParameter(request, "artistCount", 20));
        criteria.setOffset(getIntParameter(request, "artistOffset", 0));
        SearchResult artists = searchService.search(criteria, SearchService.IndexType.ARTIST, userGroupId);
        for (MediaFile mediaFile : artists.getMediaFiles()) {
            builder.add("artist", createAttributesForArtist(mediaFile, username), true);
        }

        criteria.setCount(getIntParameter(request, "albumCount", 20));
        criteria.setOffset(getIntParameter(request, "albumOffset", 0));
        SearchResult albums = searchService.search(criteria, SearchService.IndexType.ALBUM, userGroupId);
        for (MediaFile mediaFile : albums.getMediaFiles()) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("album", attributes, true);
        }

        criteria.setCount(getIntParameter(request, "songCount", 20));
        criteria.setOffset(getIntParameter(request, "songOffset", 0));
        SearchResult songs = searchService.search(criteria, SearchService.IndexType.SONG, userGroupId);
        for (MediaFile mediaFile : songs.getMediaFiles()) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void search3(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        builder.add("searchResult3", false);

        String query = request.getParameter("query");
        SearchCriteria criteria = new SearchCriteria();
        criteria.setQuery(StringUtils.trimToEmpty(query));
        criteria.setCount(getIntParameter(request, "artistCount", 20));
        criteria.setOffset(getIntParameter(request, "artistOffset", 0));
        SearchResult searchResult = searchService.search(criteria, SearchService.IndexType.ARTIST_ID3, userGroupId);
        for (Artist artist : searchResult.getArtists()) {
            builder.add("artist", createAttributesForArtist(artist, username), true);
        }

        criteria.setCount(getIntParameter(request, "albumCount", 20));
        criteria.setOffset(getIntParameter(request, "albumOffset", 0));
        searchResult = searchService.search(criteria, SearchService.IndexType.ALBUM_ID3, userGroupId);
        for (Album album : searchResult.getAlbums()) {
            builder.add("album", createAttributesForAlbum(album, username), true);
        }

        criteria.setCount(getIntParameter(request, "songCount", 20));
        criteria.setOffset(getIntParameter(request, "songOffset", 0));
        searchResult = searchService.search(criteria, SearchService.IndexType.SONG, userGroupId);
        for (MediaFile song : searchResult.getMediaFiles()) {
            builder.add("song", createAttributesForMediaFile(player, song, username), true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getPlaylists(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        User user = securityService.getCurrentUser(request);

        String authenticatedUsername = user.getUsername();
        String requestedUsername = request.getParameter("username");

        if (requestedUsername == null) {
            requestedUsername = authenticatedUsername;
        } else if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    authenticatedUsername + " is not authorized to get playlists for " + requestedUsername);
            return;
        }

        builder.add("playlists", false);

        for (Playlist playlist : playlistService.getReadablePlaylistsForUser(requestedUsername, "name asc")) {
            List<String> sharedUsers = playlistService.getPlaylistUsers(playlist.getId());
            builder.add("playlist", createAttributesForPlaylist(playlist), sharedUsers.isEmpty());
            if (!sharedUsers.isEmpty()) {
                for (String username : sharedUsers) {
                    builder.add("allowedUser", (Iterable<Attribute>) null, username, true);
                }
                builder.end();
            }
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);

        int id = getRequiredIntParameter(request, "id");

        Playlist playlist = playlistService.getPlaylist(id);
        if (playlist == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
            return;
        }
        if (!playlistService.isReadAllowed(playlist, username)) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
            return;
        }
        builder.add("playlist", createAttributesForPlaylist(playlist), false);
        for (String allowedUser : playlistService.getPlaylistUsers(playlist.getId())) {
            builder.add("allowedUser", (Iterable<Attribute>) null, allowedUser, true);
        }
        for (MediaFile mediaFile : playlistService.getFilesInPlaylist(id)) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("entry", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void scanstatus(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);

        User user = securityService.getCurrentUser(request);
        if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to get Status.");
            return;
        }

        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("scan", null, false);
        int Counter = mediaScannerService.getScanCount();

        if (mediaScannerService.isScanning() == true) {
            builder.add("status", false, new Attribute("started", true), new Attribute("count", Counter));
        } else {
            builder.add("status", false, new Attribute("started", false));
        }

        builder.endAll();
        response.getWriter().print(builder);

    }

    public void startRescan(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);

        User user = securityService.getCurrentUser(request);
        if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to rescan.");
            return;
        }

        Date lastScan = settingsService.getLastScanned();

        mediaScannerService.scanLibrary();

        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("scan", null, false);

        if (mediaScannerService.isScanning() == true) {

            AttributeSet attributes = new AttributeSet();
            attributes.add("running", mediaScannerService.isScanning());
            attributes.add("count", mediaScannerService.getScanCount());
            builder.add("status", attributes, false);
            builder.end();

            AttributeSet attributesStatus = new AttributeSet();
            builder.add("Scan", attributesStatus, false);

            //FIXME: ScanDate
            //            attributes.add("lastScan", "xxxxx"); ///lastScan.toString());
            //            attributes.add("activeScan", mediaFileService.(mediaFileDao) "14.01.2013 12:30");

        } else {
            builder.add("status", false, new Attribute("started", false));
        }

        builder.endAll();
        response.getWriter().print(builder);

    }

    public void jukeboxControl(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);

        User user = securityService.getCurrentUser(request);
        if (!user.isJukeboxRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to use jukebox.");
            return;
        }

        boolean returnPlaylist = false;
        String action = getRequiredStringParameter(request, "action");
        if ("start".equals(action)) {
            playQueueService.doStart(request, response);
        } else if ("stop".equals(action)) {
            playQueueService.doStop(request, response);
        } else if ("skip".equals(action)) {
            int index = getRequiredIntParameter(request, "index");
            int offset = getIntParameter(request, "offset", 0);
            playQueueService.doSkip(request, response, index, offset);
        } else if ("add".equals(action)) {
            int[] ids = getIntParameters(request, "id");
            playQueueService.doAdd(request, response, ids, null);
        } else if ("set".equals(action)) {
            int[] ids = getIntParameters(request, "id");
            playQueueService.doSet(request, response, ids);
        } else if ("clear".equals(action)) {
            playQueueService.doClear(request, response);
        } else if ("remove".equals(action)) {
            int index = getRequiredIntParameter(request, "index");
            playQueueService.doRemove(request, response, index);
        } else if ("shuffle".equals(action)) {
            playQueueService.doShuffle(request, response);
        } else if ("setGain".equals(action)) {
            float gain = getRequiredFloatParameter(request, "gain");
            jukeboxService.setGain(gain);
        } else if ("get".equals(action)) {
            returnPlaylist = true;
        } else if ("status".equals(action)) {
            // No action necessary.
        } else {
            throw new Exception("Unknown jukebox action: '" + action + "'.");
        }

        XMLBuilder builder = createXMLBuilder(request, response, true);

        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        Player jukeboxPlayer = jukeboxService.getPlayer();
        boolean controlsJukebox = jukeboxPlayer != null && jukeboxPlayer.getId().equals(player.getId());
        PlayQueue playQueue = player.getPlayQueue();

        List<Attribute> attrs = new ArrayList<Attribute>(Arrays.asList(
                new Attribute("currentIndex", controlsJukebox && !playQueue.isEmpty() ? playQueue.getIndex() : -1),
                new Attribute("playing",
                        controlsJukebox && !playQueue.isEmpty()
                                && playQueue.getStatus() == PlayQueue.Status.PLAYING),
                new Attribute("gain", jukeboxService.getGain()), new Attribute("position",
                        controlsJukebox && !playQueue.isEmpty() ? jukeboxService.getPosition() : 0)));

        if (returnPlaylist) {
            builder.add("jukeboxPlaylist", attrs, false);
            List<MediaFile> result;
            synchronized (playQueue) {
                result = playQueue.getFiles();
            }
            for (MediaFile mediaFile : result) {
                AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
                builder.add("entry", attributes, true);
            }
        } else {
            builder.add("jukeboxStatus", attrs, false);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void createPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);
        String username = securityService.getCurrentUsername(request);

        Integer playlistId = getIntParameter(request, "playlistId");
        String name = request.getParameter("name");
        if (playlistId == null && name == null) {
            error(request, response, ErrorCode.MISSING_PARAMETER, "Playlist ID or name must be specified.");
            return;
        }

        Playlist playlist;
        if (playlistId != null) {
            playlist = playlistService.getPlaylist(playlistId);
            if (playlist == null) {
                error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + playlistId);
                return;
            }
            if (!playlistService.isWriteAllowed(playlist, username)) {
                error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + playlistId);
                return;
            }
        } else {
            playlist = new Playlist();
            playlist.setName(name);
            playlist.setCreated(new Date());
            playlist.setChanged(new Date());
            playlist.setPublic(false);
            playlist.setUsername(username);
            playlistService.createPlaylist(playlist);
        }

        List<MediaFile> songs = new ArrayList<MediaFile>();
        for (int id : getIntParameters(request, "songId")) {
            MediaFile song = mediaFileService.getMediaFile(id);
            if (song != null) {
                songs.add(song);
            }
        }
        playlistService.setFilesInPlaylist(playlist.getId(), songs);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void updatePlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);
        String username = securityService.getCurrentUsername(request);

        int id = getRequiredIntParameter(request, "playlistId");
        Playlist playlist = playlistService.getPlaylist(id);
        if (playlist == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
            return;
        }
        if (!playlistService.isWriteAllowed(playlist, username)) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
            return;
        }

        String name = request.getParameter("name");
        if (name != null) {
            playlist.setName(name);
        }
        String comment = request.getParameter("comment");
        if (comment != null) {
            playlist.setComment(comment);
        }
        Boolean isPublic = getBooleanParameter(request, "public");
        if (isPublic != null) {
            playlist.setPublic(isPublic);
        }
        playlistService.updatePlaylist(playlist);

        // TODO: Add later
        //            for (String usernameToAdd : ServletRequestUtils.getStringParameters(request, "usernameToAdd")) {
        //                if (securityService.getUserByName(usernameToAdd) != null) {
        //                    playlistService.addPlaylistUser(id, usernameToAdd);
        //                }
        //            }
        //            for (String usernameToRemove : ServletRequestUtils.getStringParameters(request, "usernameToRemove")) {
        //                if (securityService.getUserByName(usernameToRemove) != null) {
        //                    playlistService.deletePlaylistUser(id, usernameToRemove);
        //                }
        //            }
        List<MediaFile> songs = playlistService.getFilesInPlaylist(id);
        boolean songsChanged = false;

        SortedSet<Integer> tmp = new TreeSet<Integer>();
        for (int songIndexToRemove : getIntParameters(request, "songIndexToRemove")) {
            tmp.add(songIndexToRemove);
        }
        List<Integer> songIndexesToRemove = new ArrayList<Integer>(tmp);
        Collections.reverse(songIndexesToRemove);
        for (Integer songIndexToRemove : songIndexesToRemove) {
            songs.remove(songIndexToRemove.intValue());
            songsChanged = true;
        }
        for (int songToAdd : getIntParameters(request, "songIdToAdd")) {
            MediaFile song = mediaFileService.getMediaFile(songToAdd);
            if (song != null) {
                songs.add(song);
                songsChanged = true;
            }
        }
        if (songsChanged) {
            playlistService.setFilesInPlaylist(id, songs);
        }

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void deletePlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request, true);
        String username = securityService.getCurrentUsername(request);

        int id = getRequiredIntParameter(request, "id");
        Playlist playlist = playlistService.getPlaylist(id);
        if (playlist == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
            return;
        }
        if (!playlistService.isWriteAllowed(playlist, username)) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
            return;
        }
        playlistService.deletePlaylist(id);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getAlbumList(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("albumList", false);

        int size = getIntParameter(request, "size", 10);
        int offset = getIntParameter(request, "offset", 0);
        size = Math.max(0, Math.min(size, 1000));
        String type = getRequiredStringParameter(request, "type");

        List<MediaFile> albums = Collections.emptyList();
        //        List<org.madsonic.controller.HomeController.Album> HCalbums;

        if ("highest".equals(type)) {
            albums = ratingService.getHighestRated(offset, size, userGroupId);
        } else if ("frequent".equals(type)) {
            albums = mediaFileService.getMostFrequentlyPlayedAlbums(offset, size, userGroupId);
        } else if ("recent".equals(type)) {
            albums = mediaFileService.getMostRecentlyPlayedAlbums(offset, size, userGroupId);
        } else if ("newest".equals(type)) {
            albums = mediaFileService.getNewestAlbums(null, offset, size, userGroupId);
        } else if ("tip".equals(type)) {
            albums = hotService.getRandomHotRated(0, size, userGroupId);
        } else if ("hot".equals(type)) {
            albums = hotService.getHotRated(null, offset, size, userGroupId);
        } else if ("starred".equals(type)) {
            albums = mediaFileService.getStarredAlbums(offset, size, username);
        } else if ("alphabeticalByArtist".equals(type)) {
            albums = mediaFileService.getAlphabetialAlbums(offset, size, true, userGroupId);
        } else if ("alphabeticalByName".equals(type)) {
            albums = mediaFileService.getAlphabetialAlbums(offset, size, false, userGroupId);
        } else if ("byGenre".equals(type)) {
            albums = mediaFileService.getAlbumsByGenre(offset, size, getRequiredStringParameter(request, "genre"),
                    userGroupId);
        } else if ("byYear".equals(type)) {
            albums = mediaFileService.getAlbumsByYear(offset, size, getRequiredIntParameter(request, "fromYear"),
                    getRequiredIntParameter(request, "toYear"), userGroupId);
        } else if ("allArtist".equals(type)) {
            albums = mediaFileService.getArtists(offset, size, username, userGroupId);
        } else if ("starredArtist".equals(type)) {
            albums = mediaFileService.getStarredArtists(offset, size, username);
        } else if ("random".equals(type)) {
            albums = searchService.getRandomAlbums(null, size, userGroupId);
        } else {
            throw new Exception("Invalid list type: " + type);
        }

        for (MediaFile album : albums) {
            //   MediaFile mediaFile = mediaFileService.getMediaFile(album.getPath());
            AttributeSet attributes = createAttributesForMediaFile(player, album, username);
            builder.add("album", attributes, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getAlbumList2(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("albumList2", false);

        int size = getIntParameter(request, "size", 10);
        int offset = getIntParameter(request, "offset", 0);
        size = Math.max(0, Math.min(size, 500));
        String type = getRequiredStringParameter(request, "type");
        String username = securityService.getCurrentUsername(request);

        List<Album> albums;
        if ("frequent".equals(type)) {
            albums = albumDao.getMostFrequentlyPlayedAlbums(offset, size);
        } else if ("recent".equals(type)) {
            albums = albumDao.getMostRecentlyPlayedAlbums(offset, size);
        } else if ("newest".equals(type)) {
            albums = albumDao.getNewestAlbums(offset, size);
        } else if ("alphabeticalByArtist".equals(type)) {
            albums = albumDao.getAlphabetialAlbums(offset, size, true);
        } else if ("alphabeticalByName".equals(type)) {
            albums = albumDao.getAlphabetialAlbums(offset, size, false);
        } else if ("byGenre".equals(type)) {
            albums = albumDao.getAlbumsByGenre(offset, size, getRequiredStringParameter(request, "genre"));
        } else if ("byYear".equals(type)) {
            albums = albumDao.getAlbumsByYear(offset, size, getRequiredIntParameter(request, "fromYear"),
                    getRequiredIntParameter(request, "toYear"));
        } else if ("starred".equals(type)) {
            albums = albumDao.getStarredAlbums(offset, size, securityService.getCurrentUser(request).getUsername());
        } else if ("random".equals(type)) {
            albums = searchService.getRandomAlbumsId3(size);
        } else {
            throw new Exception("Invalid list type: " + type);
        }
        for (Album album : albums) {
            builder.add("album", createAttributesForAlbum(album, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getTopTrackSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {

        request = wrapRequest(request);

        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        //TODO: made secure
        int userGroupId = securityService.getCurrentUserGroupId(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("topTrackSongs", false);

        String artist = getStringParameter(request, "artist", "unknown");
        int id = getIntParameter(request, "id", 0);
        int size = getIntParameter(request, "size", 20);
        size = Math.max(0, Math.min(size, 500));

        List<MediaFile> resultList = new ArrayList<MediaFile>();

        if (id > 1) {
            MediaFile mediaFile = mediaFileService.getMediaFile(id);
            if (mediaFile != null) {
                artist = mediaFile.getArtist();
            }
        }
        if (artist != "unknown") {
            resultList = lastFMService.updateTopTrackEntries(artist, size, userGroupId);
        }

        for (MediaFile mediaFile : resultList) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    private List<MediaFile> getMediaFiles(HttpServletRequest request, int user_group_id) {
        List<MediaFile> mediaFiles = new ArrayList<MediaFile>();
        for (int id : ServletRequestUtils.getIntParameters(request, "id")) {
            MediaFile mediaFile = mediaFileService.getMediaFile(id, user_group_id);
            if (mediaFile != null) {
                mediaFiles.add(mediaFile);
            }
        }
        return mediaFiles;
    }

    public void getPandoraSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {

        request = wrapRequest(request);

        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        List<String> _artist = new ArrayList<String>();
        List<String> _albums = new ArrayList<String>();
        List<String> _genres = new ArrayList<String>();
        List<String> _mood = new ArrayList<String>();

        int userGroupId = securityService.getCurrentUserGroupId(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("pandoraSongs", false);

        List<MediaFile> resultList = new ArrayList<MediaFile>();
        List<MediaFile> mediaFiles = null;
        try {
            mediaFiles = getMediaFiles(request, userGroupId);
        } catch (SecurityException x) {
            // Ignored
        }

        for (MediaFile media : mediaFiles) {

            MediaFile mediaFile = mediaFileService.getMediaFile(media.getId());

            if (mediaFile != null) {
                _artist.add(media.getArtist());
                _albums.add(media.getAlbumName());
                _genres.add(media.getGenre());
                _mood.add(media.getMood());

            }
        }

        String[] artist = _artist.toArray(new String[_artist.size()]);
        String[] albums = _albums.toArray(new String[_albums.size()]);
        String[] genres = _genres.toArray(new String[_genres.size()]);
        String[] mood = _mood.toArray(new String[_mood.size()]);

        if (mediaFiles.size() > 0) {

            //RESULT: ALBUM
            MultiSearchCriteria criteria1 = new MultiSearchCriteria(settingsService.getPandoraResultAlbum(), artist,
                    albums, null, null, null, null, null, userGroupId);
            resultList.addAll(searchService.getRandomSongs(criteria1));
            if ("TEST".contains(SettingsService.getLogfileLevel())) {
                System.out.println("-=Pandora-R1=-");
                for (MediaFile m : searchService.getRandomSongs(criteria1)) {
                    System.out.println(m.getPath());
                }
            }

            //RESULT: ARTIST
            MultiSearchCriteria criteria2 = new MultiSearchCriteria(settingsService.getPandoraResultArtist(),
                    artist, null, null, null, null, null, null, userGroupId);
            resultList.addAll(searchService.getRandomSongs(criteria2));
            if ("TEST".contains(SettingsService.getLogfileLevel())) {
                System.out.println("-=Pandora-R2=-");
                for (MediaFile m : searchService.getRandomSongs(criteria2)) {
                    System.out.println(m.getPath());
                }
            }
            //RESULT: GENRE
            GenreSearchCriteria criteria3 = new GenreSearchCriteria(settingsService.getPandoraResultGenre(), null,
                    genres, null, null, null, userGroupId);
            resultList.addAll(searchService.getRandomSongs(criteria3));
            if ("TEST".contains(SettingsService.getLogfileLevel())) {
                System.out.println("-=Pandora-R3=-");
                for (MediaFile m : searchService.getRandomSongs(criteria3)) {
                    System.out.println(m.getPath());
                }
            }
            //RESULT: MOODS
            MoodsSearchCriteria criteria4 = new MoodsSearchCriteria(settingsService.getPandoraResultMood(), null,
                    mood, null, null, null, userGroupId);
            resultList.addAll(searchService.getRandomSongs(criteria4));
            if ("TEST".contains(SettingsService.getLogfileLevel())) {
                System.out.println("-=Pandora-R4=-");
                for (MediaFile m : searchService.getRandomSongs(criteria4)) {
                    System.out.println(m.getPath());
                }
            }

            //RESULT: OTHER
            List<String> allArtists = new ArrayList<String>();
            List<String> similar = new ArrayList<String>();

            for (String __artist : artist) {
                similar = lastFMService.getSimilarArtist(__artist);
                allArtists.addAll(similar);
                if (allArtists.size() > 10) {
                    break;
                }
            }
            String[] array = allArtists.toArray(new String[allArtists.size()]);

            //RESULT: RANDOM
            MultiSearchCriteria criteria5 = new MultiSearchCriteria(settingsService.getPandoraResultSimilar(),
                    array, null, null, null, null, null, null, userGroupId);
            resultList.addAll(searchService.getRandomSongs(criteria5));
            if ("TEST".contains(SettingsService.getLogfileLevel())) {
                System.out.println("-=Pandora-R5=-");
                for (MediaFile m : searchService.getRandomSongs(criteria5)) {
                    System.out.println(m.getPath());
                }
            }

            //RESULT: ARTIST TOPTRACKS
            List<MediaFile> resultTopTrack = new ArrayList<MediaFile>();

            if (mediaFileDao.getTopTracks(artist[0], userGroupId).size() == 0) {
                resultTopTrack = lastFMService.getTopTrack(artist[0], 20, userGroupId);
            }

            if (artist.length > 1) {
                for (String __artist : artist) {
                    // resultTopTrack = lastFMService.getTopTrack(_artist, 20, userGroupId);
                    resultTopTrack = mediaFileDao.getTopTracks(__artist, userGroupId);
                    if (resultTopTrack.size() > 0) {
                        for (int i = 0; i < settingsService.getPandoraResultArtistTopTrack(); i++) {
                            Random myRandomizer = new Random();
                            MediaFile randomTopTrack = resultTopTrack
                                    .get(myRandomizer.nextInt(resultTopTrack.size()));
                            resultList.add(randomTopTrack);
                        }
                        if ("TEST".contains(SettingsService.getLogfileLevel())) {
                            System.out.println("-=Pandora-R6=-");
                            for (MediaFile m : resultTopTrack) {
                                System.out.println(m.getPath());
                            }
                        }
                    }
                }
            }

            if (artist.length == 1) {
                // resultTopTrack = lastFMService.getTopTrack(artist[0], settingsService.getPandoraResultArtistTopTrack(), userGroupId);
                resultTopTrack = mediaFileDao.getTopTracks(artist[0], userGroupId);
                if (resultTopTrack.size() > 0) {
                    for (int i = 0; i < settingsService.getPandoraResultArtistTopTrack(); i++) {
                        Random myRandomizer = new Random();
                        MediaFile randomTopTrack = resultTopTrack.get(myRandomizer.nextInt(resultTopTrack.size()));
                        resultList.add(randomTopTrack);
                    }
                    if ("TEST".contains(SettingsService.getLogfileLevel())) {
                        System.out.println("-=Pandora-R7=-");
                        for (MediaFile m : resultTopTrack) {
                            System.out.println(m.getPath());
                        }
                    }
                }
            }

        }

        // Filter out duplicates
        HashSet<MediaFile> hs = new HashSet<MediaFile>();
        hs.addAll(resultList);
        resultList.clear();
        resultList.addAll(hs);

        for (MediaFile mediaFile : resultList) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getRandomSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("randomSongs", false);

        int size = getIntParameter(request, "size", 20);
        size = Math.max(0, Math.min(size, 500));
        String genre = getStringParameter(request, "genre");
        Integer fromYear = getIntParameter(request, "fromYear");
        Integer toYear = getIntParameter(request, "toYear");
        Integer musicFolderId = getIntParameter(request, "musicFolderId");

        if (musicFolderId == null) {
            List<MusicFolder> musicFolders = settingsService.getAllMusicFolders(userGroupId,
                    settingsService.isSortMediaFileFolder());
            Random generator = new Random();
            int index = generator.nextInt(musicFolders.size());
            if (index > -1) {
                musicFolderId = musicFolders.get(index).getId();
            }
        }

        RandomSearchCriteria criteria = new RandomSearchCriteria(size, genre, fromYear, toYear, musicFolderId);
        List<MediaFile> resultList;
        int loop = 0;

        do {
            resultList = searchService.getRandomSongs(criteria);
            loop++;
            List<MusicFolder> musicFolders = settingsService.getAllMusicFolders(userGroupId,
                    settingsService.isSortMediaFileFolder());
            Random generator = new Random();
            int index = generator.nextInt(musicFolders.size());
            if (index > -1) {
                musicFolderId = musicFolders.get(index).getId();
            }
            RandomSearchCriteria tmpCriteria = new RandomSearchCriteria(size, genre, fromYear, toYear,
                    musicFolderId);
            resultList = searchService.getRandomSongs(tmpCriteria);
            if (loop > 10) {
                //System.out.println("BREAK"); 
                break;
            }
        } while (resultList.size() < 1);

        for (MediaFile mediaFile : resultList) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("song", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getVideos(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("videos", false);
        int size = getIntParameter(request, "size", Integer.MAX_VALUE);
        int offset = getIntParameter(request, "offset", 0);

        for (MediaFile mediaFile : mediaFileDao.getVideos(size, offset)) {
            builder.add("video", createAttributesForMediaFile(player, mediaFile, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getNowPlaying(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("nowPlaying", false);

        for (TransferStatus status : statusService.getAllStreamStatuses()) {

            Player player = status.getPlayer();
            File file = status.getFile();
            if (player != null && player.getUsername() != null && file != null) {

                String username = player.getUsername();
                UserSettings userSettings = settingsService.getUserSettings(username);
                if (!userSettings.isNowPlayingAllowed()) {
                    continue;
                }

                MediaFile mediaFile = mediaFileService.getMediaFile(file);

                long minutesAgo = status.getMillisSinceLastUpdate() / 1000L / 60L;
                if (minutesAgo < 60) {
                    AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
                    attributes.add("username", username);
                    attributes.add("playerId", player.getId());
                    attributes.add("playerName", player.getName());
                    attributes.add("minutesAgo", minutesAgo);
                    builder.add("entry", attributes, true);
                }
            }
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    private AttributeSet createAttributesForMediaFile(Player player, MediaFile mediaFile, String username) {
        MediaFile parent = mediaFileService.getParentOf(mediaFile);
        AttributeSet attributes = new AttributeSet();
        attributes.add("id", mediaFile.getId());
        try {
            if (!mediaFileService.isRoot(parent)) {
                attributes.add("parent", parent.getId());
            }
        } catch (SecurityException x) {
            // Ignored.
        }

        if (mediaFile.isAlbum() || mediaFile.isAlbumSet()) {
            if (settingsService.isShowAlbumsYearApi()) {
                attributes.add("title", "[" + mediaFile.getYear() + "] " + mediaFile.getAlbumName());
                attributes.add("album", mediaFile.getAlbumSetName());
            } else {
                attributes.add("title", mediaFile.getAlbumName());
                attributes.add("album", mediaFile.getAlbumSetName());
                attributes.add("path", mediaFile.getPath().substring(mediaFile.getPath().lastIndexOf('\\') + 1));
            }
        } else {
            attributes.add("title", mediaFile.getName());
            attributes.add("album", parent.getAlbumSetName());
        }

        attributes.add("artist", mediaFile.getArtist());
        attributes.add("isDir", mediaFile.isDirectory());
        attributes.add("coverArt", findCoverArt(mediaFile, parent));
        attributes.add("year", mediaFile.getYear());
        attributes.add("genre", mediaFile.getGenre());
        attributes.add("created", StringUtil.toISO8601(mediaFile.getCreated()));
        attributes.add("starred",
                StringUtil.toISO8601(mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username)));
        attributes.add("userRating", ratingService.getRatingForUser(username, mediaFile));
        attributes.add("averageRating", ratingService.getAverageRating(mediaFile));

        if (mediaFile.isSingleArtist() || mediaFile.isMultiArtist()) {
            attributes.add("isArtist", true);
            attributes.add("title", mediaFile.getArtist());
            attributes.add("artist", mediaFile.getGenre());
        }

        if (mediaFile.isFile()) {
            attributes.add("duration", mediaFile.getDurationSeconds());
            attributes.add("bitRate", mediaFile.getBitRate());
            attributes.add("track", mediaFile.getTrackNumber());
            attributes.add("discNumber", mediaFile.getDiscNumber());
            attributes.add("size", mediaFile.getFileSize());
            attributes.add("rank", mediaFile.getRank());
            String suffix = mediaFile.getFormat();
            attributes.add("suffix", suffix);
            attributes.add("contentType", StringUtil.getMimeType(suffix));
            attributes.add("isVideo", mediaFile.isVideo());
            attributes.add("data", mediaFile.getData());

            attributes.add("path", getRelativePath(mediaFile));

            Bookmark bookmark = bookmarkCache.get(new BookmarkKey(username, mediaFile.getId()));
            if (bookmark != null) {
                attributes.add("bookmarkPosition", bookmark.getPositionMillis());
            }

            if (mediaFile.getAlbumArtist() != null && mediaFile.getAlbumName() != null) {
                Album album = albumDao.getAlbum(mediaFile.getAlbumArtist(), mediaFile.getAlbumName());
                if (album != null) {
                    attributes.add("albumId", album.getId());
                }
            }
            if (mediaFile.getArtist() != null) {
                Artist artist = artistDao.getArtist(mediaFile.getArtist());
                if (artist != null) {
                    attributes.add("artistId", artist.getId());
                }
            }
            switch (mediaFile.getMediaType()) {
            case MUSIC:
                attributes.add("type", "music");
                break;
            case PODCAST:
                attributes.add("type", "podcast");
                break;
            case AUDIOBOOK:
                attributes.add("type", "audiobook");
                break;
            default:
                break;
            }

            if (transcodingService.isTranscodingRequired(mediaFile, player)) {
                String transcodedSuffix = transcodingService.getSuffix(player, mediaFile, null);
                attributes.add("transcodedSuffix", transcodedSuffix);
                attributes.add("transcodedContentType", StringUtil.getMimeType(transcodedSuffix));
            }
        }
        return attributes;
    }

    private Integer findCoverArt(MediaFile mediaFile, MediaFile parent) {
        MediaFile dir = mediaFile.isDirectory() ? mediaFile : parent;
        if (dir != null && dir.getCoverArtPath() != null) {
            return dir.getId();
        }
        return null;
    }

    private String getRelativePath(MediaFile musicFile) {

        String filePath = musicFile.getPath();

        // Convert slashes.
        filePath = filePath.replace('\\', '/');

        String filePathLower = filePath.toLowerCase();

        List<MusicFolder> musicFolders = settingsService.getAllMusicFolders(false, true);
        for (MusicFolder musicFolder : musicFolders) {
            String folderPath = musicFolder.getPath().getPath();
            folderPath = folderPath.replace('\\', '/');
            String folderPathLower = folderPath.toLowerCase();
            if (!folderPathLower.endsWith("/")) {
                folderPathLower += "/";
            }

            if (filePathLower.startsWith(folderPathLower)) {
                String relativePath = filePath.substring(folderPath.length());
                return relativePath.startsWith("/") ? relativePath.substring(1) : relativePath;
            }
        }

        return null;
    }

    public ModelAndView download(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isDownloadRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to download files.");
            return null;
        }

        long ifModifiedSince = request.getDateHeader("If-Modified-Since");
        long lastModified = downloadController.getLastModified(request);

        if (ifModifiedSince != -1 && lastModified != -1 && lastModified <= ifModifiedSince) {
            response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
            return null;
        }

        if (lastModified != -1) {
            response.setDateHeader("Last-Modified", lastModified);
        }

        return downloadController.handleRequest(request, response);
    }

    public ModelAndView stream(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isStreamRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to play files.");
            return null;
        }

        streamController.handleRequest(request, response);
        return null;
    }

    public ModelAndView hls(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isStreamRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to play files.");
            return null;
        }
        hlsController.handleRequest(request, response);
        return null;
    }

    public void scrobble(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        Player player = playerService.getPlayer(request, response);

        if (!settingsService.getUserSettings(player.getUsername()).isLastFmEnabled()) {
            error(request, response, ErrorCode.GENERIC,
                    "Scrobbling is not enabled for " + player.getUsername() + ".");
            return;
        }

        boolean submission = getBooleanParameter(request, "submission", true);
        int[] ids = getRequiredIntParameters(request, "id");
        long[] times = getLongParameters(request, "time");
        if (times.length > 0 && times.length != ids.length) {
            error(request, response, ErrorCode.GENERIC, "Wrong number of timestamps: " + times.length);
            return;
        }

        for (int i = 0; i < ids.length; i++) {
            int id = ids[i];
            MediaFile file = mediaFileService.getMediaFile(id);
            if (file == null) {
                LOG.warn("File to scrobble not found: " + id);
                continue;
            }
            Date time = times.length == 0 ? null : new Date(times[i]);
            audioScrobblerService.register(file, player.getUsername(), submission, time);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void star(HttpServletRequest request, HttpServletResponse response) throws Exception {
        starOrUnstar(request, response, true);
    }

    public void unstar(HttpServletRequest request, HttpServletResponse response) throws Exception {
        starOrUnstar(request, response, false);
    }

    private void starOrUnstar(HttpServletRequest request, HttpServletResponse response, boolean star)
            throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        String username = securityService.getCurrentUser(request).getUsername();
        for (int id : getIntParameters(request, "id")) {
            MediaFile mediaFile = mediaFileDao.getMediaFile(id);
            if (mediaFile == null) {
                error(request, response, ErrorCode.NOT_FOUND, "Media file not found: " + id);
                return;
            }
            if (star) {
                mediaFileDao.starMediaFile(id, username);
            } else {
                mediaFileDao.unstarMediaFile(id, username);
            }
        }
        for (int albumId : getIntParameters(request, "albumId")) {
            Album album = albumDao.getAlbum(albumId);
            if (album == null) {
                error(request, response, ErrorCode.NOT_FOUND, "Album not found: " + albumId);
                return;
            }
            if (star) {
                albumDao.starAlbum(albumId, username);
            } else {
                albumDao.unstarAlbum(albumId, username);
            }
        }
        for (int artistId : getIntParameters(request, "artistId")) {
            Artist artist = artistDao.getArtist(artistId);
            if (artist == null) {
                error(request, response, ErrorCode.NOT_FOUND, "Artist not found: " + artistId);
                return;
            }
            if (star) {
                artistDao.starArtist(artistId, username);
            } else {
                artistDao.unstarArtist(artistId, username);
            }
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getNewaddedSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int userGroupId = securityService.getCurrentUserGroupId(request);
        int size = getIntParameter(request, "size", 50);
        int offset = getIntParameter(request, "offset", 0);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("newadded", false);

        for (MediaFile song : mediaFileDao.getHistory(offset, size, userGroupId, MUSIC.name())) {
            builder.add("song", createAttributesForMediaFile(player, song, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getLastplayedSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        int size = getIntParameter(request, "size", 50);
        int offset = getIntParameter(request, "offset", 0);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("lastplayed", false);

        for (MediaFile song : mediaFileDao.getLastPlayedCountForUser(offset, size, username)) {
            builder.add("song", createAttributesForMediaFile(player, song, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getStarred(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("starred", false);
        for (MediaFile artist : mediaFileDao.getStarredDirectories(0, Integer.MAX_VALUE, username)) {
            builder.add("artist", createAttributesForArtist(artist, username), true);
        }
        for (MediaFile album : mediaFileDao.getStarredAlbums(0, Integer.MAX_VALUE, username)) {
            builder.add("album", createAttributesForMediaFile(player, album, username), true);
        }
        for (MediaFile song : mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username)) {
            builder.add("song", createAttributesForMediaFile(player, song, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getStarred2(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("starred2", false);
        for (Artist artist : artistDao.getStarredArtists(0, Integer.MAX_VALUE, username)) {
            builder.add("artist", createAttributesForArtist(artist, username), true);
        }
        for (Album album : albumDao.getStarredAlbums(0, Integer.MAX_VALUE, username)) {
            builder.add("album", createAttributesForAlbum(album, username), true);
        }
        for (MediaFile song : mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username)) {
            builder.add("song", createAttributesForMediaFile(player, song, username), true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getPodcasts(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);
        boolean includeEpisodes = getBooleanParameter(request, "includeEpisodes", true);
        Integer channelId = getIntParameter(request, "id");

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("podcasts", false);

        for (PodcastChannel channel : podcastService.getAllChannels()) {
            if (channelId == null || channelId.equals(channel.getId())) {
                AttributeSet channelAttrs = new AttributeSet();
                channelAttrs.add("id", channel.getId());
                channelAttrs.add("url", channel.getUrl());
                channelAttrs.add("status", channel.getStatus().toString().toLowerCase());
                channelAttrs.add("title", channel.getTitle());
                channelAttrs.add("description", channel.getDescription());
                channelAttrs.add("errorMessage", channel.getErrorMessage());
                builder.add("channel", channelAttrs, false);

                if (includeEpisodes) {
                    List<PodcastEpisode> episodes = podcastService.getEpisodes(channel.getId(), false);
                    for (PodcastEpisode episode : episodes) {
                        AttributeSet episodeAttrs = new AttributeSet();

                        String path = episode.getPath();
                        if (path != null) {
                            MediaFile mediaFile = mediaFileService.getMediaFile(path);
                            episodeAttrs.addAll(createAttributesForMediaFile(player, mediaFile, username));
                            episodeAttrs.add("streamId", mediaFile.getId());
                        }

                        episodeAttrs.add("id", episode.getId()); // Overwrites the previous "id" attribute.
                        episodeAttrs.add("status", episode.getStatus().toString().toLowerCase());
                        episodeAttrs.add("title", episode.getTitle());
                        episodeAttrs.add("description", episode.getDescription());
                        episodeAttrs.add("publishDate", episode.getPublishDate());

                        builder.add("episode", episodeAttrs, true);
                    }
                }
                builder.end(); // <channel>
            }
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void refreshPodcasts(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isPodcastRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to administrate podcasts.");
            return;
        }
        podcastService.refreshAllChannels(true);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void createPodcastChannel(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isPodcastRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to administrate podcasts.");
            return;
        }

        String url = getRequiredStringParameter(request, "url");
        podcastService.createChannel(url);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void deletePodcastChannel(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isPodcastRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to administrate podcasts.");
            return;
        }

        int id = getRequiredIntParameter(request, "id");
        podcastService.deleteChannel(id);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void deletePodcastEpisode(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isPodcastRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to administrate podcasts.");
            return;
        }

        int id = getRequiredIntParameter(request, "id");
        podcastService.deleteEpisode(id, true);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void downloadPodcastEpisode(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isPodcastRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to administrate podcasts.");
            return;
        }

        int id = getRequiredIntParameter(request, "id");
        PodcastEpisode episode = podcastService.getEpisode(id, true);
        if (episode == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Podcast episode " + id + " not found.");
            return;
        }

        podcastService.downloadEpisode(episode);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void getInternetRadioStations(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("internetRadioStations", false);
        for (InternetRadio radio : settingsService.getAllInternetRadios()) {
            AttributeSet attrs = new AttributeSet();
            attrs.add("id", radio.getId());
            attrs.add("name", radio.getName());
            attrs.add("streamUrl", radio.getStreamUrl());
            attrs.add("homePageUrl", radio.getHomepageUrl());
            builder.add("internetRadioStation", attrs, true);
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getBookmarks(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("bookmarks", false);
        for (Bookmark bookmark : bookmarkDao.getBookmarks(username)) {
            builder.add("bookmark", createAttributesForBookmark(bookmark), false);
            MediaFile mediaFile = mediaFileService.getMediaFile(bookmark.getMediaFileId());
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("entry", attributes, true);
            builder.end();
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void createBookmark(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        String username = securityService.getCurrentUsername(request);
        int mediaFileId = getRequiredIntParameter(request, "id");
        long position = getRequiredLongParameter(request, "position");
        String comment = request.getParameter("comment");
        Date now = new Date();

        Bookmark bookmark = new Bookmark(0, mediaFileId, position, username, comment, now, now);
        bookmarkDao.createOrUpdateBookmark(bookmark);
        refreshBookmarkCache();
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void deleteBookmark(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        String username = securityService.getCurrentUsername(request);
        int mediaFileId = getRequiredIntParameter(request, "id");
        bookmarkDao.deleteBookmark(username, mediaFileId);
        refreshBookmarkCache();

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getShares(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        User user = securityService.getCurrentUser(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        builder.add("shares", false);
        for (Share share : shareService.getSharesForUser(user)) {
            builder.add("share", createAttributesForShare(share), false);

            for (MediaFile mediaFile : shareService.getSharedFiles(share.getId())) {
                AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
                builder.add("entry", attributes, true);
            }

            builder.end();
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getSharedFiles(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        String shareName = getRequiredStringParameter(request, "share");
        Share share = shareService.getShareByName(shareName);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("sharedFiles", false);

        for (MediaFile mediaFile : shareService.getSharedFiles(share.getId())) {

            if (mediaFile.isDirectory()) {
                for (MediaFile child : mediaFileService.getChildrenOf(mediaFile, true, false, false)) {
                    builder.add("song", createAttributesForMediaFile(player, child, username), true);
                }
                ;
            } else {
                builder.add("song", createAttributesForMediaFile(player, mediaFile, username), true);
            }

        }
        builder.endAll();
        response.getWriter().print(builder);

    }

    public void createShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Player player = playerService.getPlayer(request, response);
        String username = securityService.getCurrentUsername(request);

        User user = securityService.getCurrentUser(request);
        if (!user.isShareRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to share media.");
            return;
        }

        //        if (!settingsService.isUrlRedirectionEnabled()) {
        //            error(request, response, ErrorCode.GENERIC, "Sharing is only supported for *.subsonic.org domain names.");
        //            return;
        //        }

        XMLBuilder builder = createXMLBuilder(request, response, true);

        List<MediaFile> files = new ArrayList<MediaFile>();
        for (int id : getRequiredIntParameters(request, "id")) {
            files.add(mediaFileService.getMediaFile(id));
        }

        Share share = shareService.createShare(request, files);
        share.setDescription(request.getParameter("description"));
        long expires = getLongParameter(request, "expires", 0L);
        if (expires != 0) {
            share.setExpires(new Date(expires));
        }
        shareService.updateShare(share);

        builder.add("shares", false);
        builder.add("share", createAttributesForShare(share), false);

        for (MediaFile mediaFile : shareService.getSharedFiles(share.getId())) {
            AttributeSet attributes = createAttributesForMediaFile(player, mediaFile, username);
            builder.add("entry", attributes, true);
        }

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void deleteShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        int id = getRequiredIntParameter(request, "id");

        Share share = shareService.getShareById(id);
        if (share == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Shared media not found.");
            return;
        }
        if (!user.isAdminRole() && !share.getUsername().equals(user.getUsername())) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Not authorized to delete shared media.");
            return;
        }

        shareService.deleteShare(id);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void updateShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        int id = getRequiredIntParameter(request, "id");

        Share share = shareService.getShareById(id);
        if (share == null) {
            error(request, response, ErrorCode.NOT_FOUND, "Shared media not found.");
            return;
        }
        if (!user.isAdminRole() && !share.getUsername().equals(user.getUsername())) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Not authorized to modify shared media.");
            return;
        }

        share.setDescription(request.getParameter("description"));
        String expiresString = request.getParameter("expires");
        if (expiresString != null) {
            long expires = Long.parseLong(expiresString);
            share.setExpires(expires == 0L ? null : new Date(expires));
        }
        shareService.updateShare(share);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    private List<Attribute> createAttributesForShare(Share share) {
        List<Attribute> attributes = new ArrayList<Attribute>();

        attributes.add(new Attribute("id", share.getId()));
        attributes.add(new Attribute("url", shareService.getShareUrl(share)));
        attributes.add(new Attribute("username", share.getUsername()));
        attributes.add(new Attribute("created", StringUtil.toISO8601(share.getCreated())));
        attributes.add(new Attribute("visitCount", share.getVisitCount()));
        attributes.add(new Attribute("description", share.getDescription()));
        attributes.add(new Attribute("expires", StringUtil.toISO8601(share.getExpires())));
        attributes.add(new Attribute("lastVisited", StringUtil.toISO8601(share.getLastVisited())));

        return attributes;
    }

    private List<Attribute> createAttributesForBookmark(Bookmark bookmark) {
        List<Attribute> attributes = new ArrayList<Attribute>();
        attributes.add(new Attribute("position", bookmark.getPositionMillis()));
        attributes.add(new Attribute("username", bookmark.getUsername()));
        attributes.add(new Attribute("comment", bookmark.getComment()));
        attributes.add(new Attribute("created", StringUtil.toISO8601(bookmark.getCreated())));
        attributes.add(new Attribute("changed", StringUtil.toISO8601(bookmark.getChanged())));
        return attributes;
    }

    public ModelAndView videoPlayer(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        Map<String, Object> map = new HashMap<String, Object>();
        int id = getRequiredIntParameter(request, "id");
        MediaFile file = mediaFileService.getMediaFile(id);

        int timeOffset = getIntParameter(request, "timeOffset", 0);
        timeOffset = Math.max(0, timeOffset);
        Integer duration = file.getDurationSeconds();
        if (duration != null) {
            map.put("skipOffsets", VideoPlayerController.createSkipOffsets(duration));
            timeOffset = Math.min(duration, timeOffset);
            duration -= timeOffset;
        }

        map.put("id", request.getParameter("id"));
        map.put("u", request.getParameter("u"));
        map.put("p", request.getParameter("p"));
        map.put("c", request.getParameter("c"));
        map.put("v", request.getParameter("v"));
        map.put("video", file);
        map.put("maxBitRate", getIntParameter(request, "maxBitRate", VideoPlayerController.DEFAULT_BIT_RATE));
        map.put("duration", duration);
        map.put("timeOffset", timeOffset);
        map.put("bitRates", VideoPlayerController.BIT_RATES);
        map.put("autoplay", getBooleanParameter(request, "autoplay", true));

        ModelAndView result = new ModelAndView();

        if (SettingsService.getUsedVideoPlayer().equalsIgnoreCase("HTML5")) {

            result.setViewName("rest/videoPlayerH5");

        } else {
            result.setViewName("rest/videoPlayer");
        }

        result.addObject("model", map);
        return result;
    }

    public ModelAndView getCoverArt(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        return coverArtController.handleRequest(request, response);
    }

    public ModelAndView getAvatar(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        return avatarController.handleRequest(request, response);
    }

    public void changePassword(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        String username = getRequiredStringParameter(request, "username");
        String password = decrypt(getRequiredStringParameter(request, "password"));

        User authUser = securityService.getCurrentUser(request);

        boolean allowed = authUser.isAdminRole()
                || username.equals(authUser.getUsername()) && authUser.isSettingsRole();

        if (!allowed) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    authUser.getUsername() + " is not authorized to change password for " + username);
            return;
        }

        User user = securityService.getUserByName(username);
        user.setPassword(password);
        securityService.updateUser(user);

        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void getUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        String username = getRequiredStringParameter(request, "username");

        User currentUser = securityService.getCurrentUser(request);
        if (!username.equals(currentUser.getUsername()) && !currentUser.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    currentUser.getUsername() + " is not authorized to get details for other users.");
            return;
        }

        User requestedUser = securityService.getUserByName(username);
        if (requestedUser == null) {
            error(request, response, ErrorCode.NOT_FOUND, "No such user: " + username);
            return;
        }

        UserSettings userSettings = settingsService.getUserSettings(username);

        XMLBuilder builder = createXMLBuilder(request, response, true);
        List<Attribute> attributes = createAttributesForUser(requestedUser, userSettings);

        builder.add("user", attributes, true);
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void getUsers(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        User currentUser = securityService.getCurrentUser(request);
        if (!currentUser.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    currentUser.getUsername() + " is not authorized to get details for other users.");
            return;
        }

        XMLBuilder builder = createXMLBuilder(request, response, true);
        builder.add("users", false);
        for (User user : securityService.getAllUsers()) {
            UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
            List<Attribute> attributes = createAttributesForUser(user, userSettings);
            builder.add("user", attributes, true);

        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    private List<Attribute> createAttributesForUser(User user, UserSettings userSettings) {
        return Arrays.asList(new Attribute("username", user.getUsername()), new Attribute("email", user.getEmail()),
                new Attribute("scrobblingEnabled", userSettings.isLastFmEnabled()),
                new Attribute("adminRole", user.isAdminRole()),
                new Attribute("settingsRole", user.isSettingsRole()),
                new Attribute("downloadRole", user.isDownloadRole()),
                new Attribute("uploadRole", user.isUploadRole()), new Attribute("playlistRole", true), // Since 1.8.0
                new Attribute("coverArtRole", user.isCoverArtRole()),
                new Attribute("commentRole", user.isCommentRole()),
                new Attribute("podcastRole", user.isPodcastRole()),
                new Attribute("streamRole", user.isStreamRole()),
                new Attribute("jukeboxRole", user.isJukeboxRole()), new Attribute("shareRole", user.isShareRole()),
                new Attribute("lastFMRole", user.isLastFMRole()));
    }

    public void createUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to create new users.");
            return;
        }

        UserSettingsCommand command = new UserSettingsCommand();
        command.setUsername(getRequiredStringParameter(request, "username"));
        command.setPassword(decrypt(getRequiredStringParameter(request, "password")));
        command.setEmail(getRequiredStringParameter(request, "email"));
        command.setLdapAuthenticated(getBooleanParameter(request, "ldapAuthenticated", false));
        command.setAdminRole(getBooleanParameter(request, "adminRole", false));
        command.setCommentRole(getBooleanParameter(request, "commentRole", false));
        command.setCoverArtRole(getBooleanParameter(request, "coverArtRole", false));
        command.setDownloadRole(getBooleanParameter(request, "downloadRole", false));
        command.setStreamRole(getBooleanParameter(request, "streamRole", true));
        command.setUploadRole(getBooleanParameter(request, "uploadRole", false));
        command.setJukeboxRole(getBooleanParameter(request, "jukeboxRole", false));
        command.setPodcastRole(getBooleanParameter(request, "podcastRole", false));
        command.setSettingsRole(getBooleanParameter(request, "settingsRole", true));
        command.setShareRole(getBooleanParameter(request, "shareRole", false));
        command.setSearchRole(getBooleanParameter(request, "searchRole", false));
        command.setLastFMRole(getBooleanParameter(request, "lastFMRole", false));
        command.setTranscodeSchemeName(getStringParameter(request, "transcodeScheme", TranscodeScheme.OFF.name()));
        command.setGroupId(getIntParameter(request, "groupId", 0));

        userSettingsController.createUser(command);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void updateUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to update users.");
            return;
        }

        String username = getRequiredStringParameter(request, "username");
        User u = securityService.getUserByName(username);
        UserSettings s = settingsService.getUserSettings(username);

        if (u == null) {
            error(request, response, ErrorCode.NOT_FOUND, "No such user: " + username);
            return;
        } else if (User.USERNAME_ADMIN.equals(username)) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Not allowed to change admin user");
            return;
        }

        UserSettingsCommand command = new UserSettingsCommand();
        command.setUsername(username);
        command.setEmail(getStringParameter(request, "email", u.getEmail()));
        command.setLdapAuthenticated(getBooleanParameter(request, "ldapAuthenticated", u.isLdapAuthenticated()));
        command.setAdminRole(getBooleanParameter(request, "adminRole", u.isAdminRole()));
        command.setCommentRole(getBooleanParameter(request, "commentRole", u.isCommentRole()));
        command.setCoverArtRole(getBooleanParameter(request, "coverArtRole", u.isCoverArtRole()));
        command.setDownloadRole(getBooleanParameter(request, "downloadRole", u.isDownloadRole()));
        command.setStreamRole(getBooleanParameter(request, "streamRole", u.isDownloadRole()));
        command.setUploadRole(getBooleanParameter(request, "uploadRole", u.isUploadRole()));
        command.setJukeboxRole(getBooleanParameter(request, "jukeboxRole", u.isJukeboxRole()));
        command.setPodcastRole(getBooleanParameter(request, "podcastRole", u.isPodcastRole()));
        command.setSettingsRole(getBooleanParameter(request, "settingsRole", u.isSettingsRole()));
        command.setShareRole(getBooleanParameter(request, "shareRole", u.isShareRole()));
        command.setLastFMRole(getBooleanParameter(request, "lastFMRole", u.isLastFMRole()));
        command.setTranscodeSchemeName(s.getTranscodeScheme().name());

        if (hasParameter(request, "password")) {
            command.setPassword(decrypt(getRequiredStringParameter(request, "password")));
            command.setPasswordChange(true);
        }

        userSettingsController.updateUser(command);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    private boolean hasParameter(HttpServletRequest request, String name) {
        return request.getParameter(name) != null;
    }

    public void deleteUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        User user = securityService.getCurrentUser(request);
        if (!user.isAdminRole()) {
            error(request, response, ErrorCode.NOT_AUTHORIZED,
                    user.getUsername() + " is not authorized to delete users.");
            return;
        }

        String username = getRequiredStringParameter(request, "username");
        if (User.USERNAME_ADMIN.equals(username)) {
            error(request, response, ErrorCode.NOT_AUTHORIZED, "Not allowed to delete admin user");
            return;
        }

        securityService.deleteUser(username);

        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void getChatMessages(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        XMLBuilder builder = createXMLBuilder(request, response, true);

        long since = getLongParameter(request, "since", 0L);

        builder.add("chatMessages", false);

        for (ChatService.Message message : chatService.getMessages(0L).getMessages()) {
            long time = message.getDate().getTime();
            if (time > since) {
                builder.add("chatMessage", true, new Attribute("username", message.getUsername()),
                        new Attribute("time", time), new Attribute("message", message.getContent()));
            }
        }
        builder.endAll();
        response.getWriter().print(builder);
    }

    public void addChatMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        chatService.doAddMessage(getRequiredStringParameter(request, "message"), request);
        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    public void getLyrics(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);

        String artist = request.getParameter("artist");
        String title = request.getParameter("title");
        String id = request.getParameter("id");

        LyricsInfo lyrics = new LyricsInfo();
        if (id != null) {
            lyrics = lyricsService.getLyrics(id, artist, title);
        } else {
            lyrics = lyricsService.getLyrics(artist, title);
        }

        XMLBuilder builder = createXMLBuilder(request, response, true);
        AttributeSet attributes = new AttributeSet();
        attributes.add("artist", lyrics.getArtist());
        attributes.add("title", lyrics.getTitle());
        builder.add("lyrics", attributes, lyrics.getLyrics(), true);

        builder.endAll();
        response.getWriter().print(builder);
    }

    public void setRating(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request = wrapRequest(request);
        Integer rating = getRequiredIntParameter(request, "rating");
        if (rating == 0) {
            rating = null;
        }

        int id = getRequiredIntParameter(request, "id");
        MediaFile mediaFile = mediaFileService.getMediaFile(id);
        if (mediaFile == null) {
            error(request, response, ErrorCode.NOT_FOUND, "File not found: " + id);
            return;
        }

        String username = securityService.getCurrentUsername(request);
        ratingService.setRatingForUser(username, mediaFile, rating);

        XMLBuilder builder = createXMLBuilder(request, response, true).endAll();
        response.getWriter().print(builder);
    }

    private HttpServletRequest wrapRequest(HttpServletRequest request) {
        return wrapRequest(request, false);
    }

    private HttpServletRequest wrapRequest(final HttpServletRequest request, boolean jukebox) {
        final String playerId = createPlayerIfNecessary(request, jukebox);
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getParameter(String name) {
                // Returns the correct player to be used in PlayerService.getPlayer()
                if ("player".equals(name)) {
                    return playerId;
                }

                // Support old style ID parameters.
                if ("id".equals(name)) {
                    return mapId(request.getParameter("id"));
                }

                return super.getParameter(name);
            }
        };
    }

    private String mapId(String id) {
        if (id == null || id.startsWith(CoverArtController.ALBUM_COVERART_PREFIX)
                || id.startsWith(CoverArtController.ARTIST_COVERART_PREFIX) || StringUtils.isNumeric(id)) {
            return id;
        }

        try {
            String path = StringUtil.utf8HexDecode(id);
            MediaFile mediaFile = mediaFileService.getMediaFile(path);
            return String.valueOf(mediaFile.getId());
        } catch (Exception x) {
            return id;
        }
    }

    public static void error(HttpServletRequest request, HttpServletResponse response, ErrorCode code,
            String message) throws IOException {
        XMLBuilder builder = createXMLBuilder(request, response, false);
        builder.add("error", true, new XMLBuilder.Attribute("code", code.getCode()),
                new XMLBuilder.Attribute("message", message));
        builder.end();
        response.getWriter().print(builder);
    }

    private static XMLBuilder createXMLBuilder(HttpServletRequest request, HttpServletResponse response, boolean ok)
            throws IOException {
        String format = getStringParameter(request, "f", "xml");
        boolean json = "json".equals(format);
        boolean jsonp = "jsonp".equals(format);
        XMLBuilder builder;

        response.setCharacterEncoding(StringUtil.ENCODING_UTF8);

        if (json) {
            builder = XMLBuilder.createJSONBuilder();
            response.setContentType("application/json");
        } else if (jsonp) {
            builder = XMLBuilder.createJSONPBuilder(request.getParameter("callback"));
            response.setContentType("text/javascript");
        } else {
            builder = XMLBuilder.createXMLBuilder();
            response.setContentType("text/xml");
        }

        builder.preamble(StringUtil.ENCODING_UTF8);
        builder.add("subsonic-response", false, new Attribute("xmlns", "http://madsonic.org/restapi"),
                new Attribute("status", ok ? "ok" : "failed"), new Attribute("type", "madsonic"),
                new Attribute("version", StringUtil.getRESTProtocolVersion()));

        return builder;
    }

    private String createPlayerIfNecessary(HttpServletRequest request, boolean jukebox) {
        String username = request.getRemoteUser();
        String clientId = request.getParameter("c");
        if (jukebox) {
            clientId += "-jukebox";
        }

        List<Player> players = playerService.getPlayersForUserAndClientId(username, clientId);

        // If not found, create it.
        if (players.isEmpty()) {
            Player player = new Player();
            player.setIpAddress(request.getRemoteAddr());
            player.setUsername(username);
            player.setClientId(clientId);
            player.setName(clientId);
            player.setTechnology(jukebox ? PlayerTechnology.JUKEBOX : PlayerTechnology.EXTERNAL_WITH_PLAYLIST);
            playerService.createPlayer(player);
            players = playerService.getPlayersForUserAndClientId(username, clientId);
        }

        // Return the player ID.
        return !players.isEmpty() ? players.get(0).getId() : null;
    }

    public void setSettingsService(SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    public void setPlayerService(PlayerService playerService) {
        this.playerService = playerService;
    }

    public void setTranscodingService(TranscodingService transcodingService) {
        this.transcodingService = transcodingService;
    }

    public void setDownloadController(DownloadController downloadController) {
        this.downloadController = downloadController;
    }

    public void setCoverArtController(CoverArtController coverArtController) {
        this.coverArtController = coverArtController;
    }

    public void setUserSettingsController(UserSettingsController userSettingsController) {
        this.userSettingsController = userSettingsController;
    }

    public void setLeftController(LeftController leftController) {
        this.leftController = leftController;
    }

    public void setStatusService(StatusService statusService) {
        this.statusService = statusService;
    }

    public void setPlaylistService(PlaylistService playlistService) {
        this.playlistService = playlistService;
    }

    public void setStreamController(StreamController streamController) {
        this.streamController = streamController;
    }

    public void setHlsController(HLSController hlsController) {
        this.hlsController = hlsController;
    }

    public void setChatService(ChatService chatService) {
        this.chatService = chatService;
    }

    public void setHomeController(HomeController homeController) {
        this.homeController = homeController;
    }

    public void setLyricsService(LyricsService lyricsService) {
        this.lyricsService = lyricsService;
    }

    public void setPlayQueueService(PlayQueueService playQueueService) {
        this.playQueueService = playQueueService;
    }

    public void setJukeboxService(JukeboxService jukeboxService) {
        this.jukeboxService = jukeboxService;
    }

    public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) {
        this.audioScrobblerService = audioScrobblerService;
    }

    public void setPodcastService(PodcastService podcastService) {
        this.podcastService = podcastService;
    }

    public void setRatingService(RatingService ratingService) {
        this.ratingService = ratingService;
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setShareService(ShareService shareService) {
        this.shareService = shareService;
    }

    public void setMediaFileService(MediaFileService mediaFileService) {
        this.mediaFileService = mediaFileService;
    }

    public void setHotService(HotService hotService) {
        this.hotService = hotService;
    }

    public void setLastFMService(LastFMService lastFMService) {
        this.lastFMService = lastFMService;
    }

    public void setAvatarController(AvatarController avatarController) {
        this.avatarController = avatarController;
    }

    public void setArtistDao(ArtistDao artistDao) {
        this.artistDao = artistDao;
    }

    public void setAlbumDao(AlbumDao albumDao) {
        this.albumDao = albumDao;
    }

    public void setMediaFileDao(MediaFileDao mediaFileDao) {
        this.mediaFileDao = mediaFileDao;
    }

    public void setMusicIndexService(MusicIndexService musicIndexService) {
        this.musicIndexService = musicIndexService;
    }

    public void setMediaScannerService(MediaScannerService mediaScannerService) {
        this.mediaScannerService = mediaScannerService;
    }

    public void setBookmarkDao(BookmarkDao bookmarkDao) {
        this.bookmarkDao = bookmarkDao;
    }

    public static enum ErrorCode {

        GENERIC(0, "A generic error."), MISSING_PARAMETER(10,
                "Required parameter is missing."), PROTOCOL_MISMATCH_CLIENT_TOO_OLD(20,
                        "Incompatible Subsonic REST protocol version. Client must upgrade."), PROTOCOL_MISMATCH_SERVER_TOO_OLD(
                                30,
                                "Incompatible Subsonic REST protocol version. Server must upgrade."), NOT_AUTHENTICATED(
                                        40, "Wrong username or password."), NOT_AUTHORIZED(50,
                                                "User is not authorized for the given operation."), NOT_LICENSED(60,
                                                        "The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium. Visit subsonic.org for details."), NOT_FOUND(
                                                                70, "Requested data was not found.");

        private final int code;
        private final String message;

        ErrorCode(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

    private static class BookmarkKey extends Pair<String, Integer> {
        private BookmarkKey(String username, int mediaFileId) {
            super(username, mediaFileId);
        }

        static BookmarkKey forBookmark(Bookmark b) {
            return new BookmarkKey(b.getUsername(), b.getMediaFileId());
        }
    }
}