net.sourceforge.subsonic.service.sonos.SonosHelper.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.subsonic.service.sonos.SonosHelper.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 2015 (C) Sindre Mehus
 */

package net.sourceforge.subsonic.service.sonos;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.sonos.services._1.AbstractMedia;
import com.sonos.services._1.AlbumArtUrl;
import com.sonos.services._1.ItemType;
import com.sonos.services._1.MediaCollection;
import com.sonos.services._1.MediaList;
import com.sonos.services._1.MediaMetadata;
import com.sonos.services._1.TrackMetadata;

import net.sourceforge.subsonic.controller.CoverArtController;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.domain.AlbumListType;
import net.sourceforge.subsonic.domain.ArtistBio;
import net.sourceforge.subsonic.domain.CoverArtScheme;
import net.sourceforge.subsonic.domain.Genre;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.MusicFolderContent;
import net.sourceforge.subsonic.domain.MusicIndex;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.PlayerTechnology;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.PodcastChannel;
import net.sourceforge.subsonic.domain.PodcastEpisode;
import net.sourceforge.subsonic.domain.PodcastStatus;
import net.sourceforge.subsonic.domain.SearchCriteria;
import net.sourceforge.subsonic.domain.SearchResult;
import net.sourceforge.subsonic.service.LastFmService;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.MusicIndexService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.service.RatingService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.SonosService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.util.StringUtil;
import net.sourceforge.subsonic.util.Util;

/**
 * @author Sindre Mehus
 * @version $Id$
 */
public class SonosHelper {

    public static final String SUBSONIC_CLIENT_ID = "sonos";

    private MediaFileService mediaFileService;
    private PlaylistService playlistService;
    private PlayerService playerService;
    private TranscodingService transcodingService;
    private SettingsService settingsService;
    private MusicIndexService musicIndexService;
    private SearchService searchService;
    private MediaFileDao mediaFileDao;
    private RatingService ratingService;
    private LastFmService lastFmService;
    private PodcastService podcastService;

    public List<AbstractMedia> forRoot() {
        MediaMetadata shuffle = new MediaMetadata();
        shuffle.setItemType(ItemType.PROGRAM);
        shuffle.setId(SonosService.ID_SHUFFLE);
        shuffle.setTitle("Shuffle Play");

        MediaCollection library = new MediaCollection();
        library.setItemType(ItemType.COLLECTION);
        library.setId(SonosService.ID_LIBRARY);
        library.setTitle("Browse Library");

        MediaCollection playlists = new MediaCollection();
        playlists.setItemType(ItemType.ALBUM_LIST);
        playlists.setId(SonosService.ID_PLAYLISTS);
        playlists.setTitle("Playlists");
        playlists.setUserContent(true);
        playlists.setReadOnly(false);

        MediaCollection starred = new MediaCollection();
        starred.setItemType(ItemType.FAVORITES);
        starred.setId(SonosService.ID_STARRED);
        starred.setTitle("Starred");

        MediaCollection albumlists = new MediaCollection();
        albumlists.setItemType(ItemType.COLLECTION);
        albumlists.setId(SonosService.ID_ALBUMLISTS);
        albumlists.setTitle("Album Lists");

        MediaCollection podcasts = new MediaCollection();
        podcasts.setItemType(ItemType.ALBUM_LIST);
        podcasts.setId(SonosService.ID_PODCASTS);
        podcasts.setTitle("Podcasts");

        return Arrays.asList(shuffle, library, playlists, starred, albumlists, podcasts);
    }

    public List<AbstractMedia> forShuffle(int count, String username, HttpServletRequest request) {
        return forShuffleMusicFolder(settingsService.getMusicFoldersForUser(username), count, username, request);
    }

    public List<AbstractMedia> forShuffleMusicFolder(int id, int count, String username,
            HttpServletRequest request) {
        return forShuffleMusicFolder(settingsService.getMusicFoldersForUser(username, id), count, username,
                request);
    }

    private List<AbstractMedia> forShuffleMusicFolder(List<MusicFolder> musicFolders, int count, String username,
            HttpServletRequest request) {
        List<MediaFile> albums = searchService.getRandomAlbums(40, musicFolders);
        List<MediaFile> songs = new ArrayList<MediaFile>();
        for (MediaFile album : albums) {
            for (MediaFile file : filterMusic(mediaFileService.getChildrenOf(album, true, false, false))) {
                songs.add(file);
            }
        }
        Collections.shuffle(songs);
        songs = songs.subList(0, Math.min(count, songs.size()));
        return forMediaFiles(songs, username, request);
    }

    public List<AbstractMedia> forShuffleArtist(int mediaFileId, int count, String username,
            HttpServletRequest request) {
        MediaFile artist = mediaFileService.getMediaFile(mediaFileId);
        List<MediaFile> songs = filterMusic(mediaFileService.getDescendantsOf(artist, false));
        Collections.shuffle(songs);
        songs = songs.subList(0, Math.min(count, songs.size()));
        return forMediaFiles(songs, username, request);
    }

    public List<AbstractMedia> forShuffleAlbumList(AlbumListType albumListType, int count, String username,
            HttpServletRequest request) {
        AlbumList albumList = createAlbumList(albumListType, 0, 40, username);

        List<MediaFile> songs = new ArrayList<MediaFile>();
        for (MediaFile album : albumList.getAlbums()) {
            songs.addAll(filterMusic(mediaFileService.getChildrenOf(album, true, false, false)));
        }
        Collections.shuffle(songs);
        songs = songs.subList(0, Math.min(count, songs.size()));
        return forMediaFiles(songs, username, request);
    }

    public List<AbstractMedia> forRadioArtist(int mediaFileId, int count, String username,
            HttpServletRequest request) {
        MediaFile artist = mediaFileService.getMediaFile(mediaFileId);
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaFile> songs = filterMusic(lastFmService.getSimilarSongs(artist, count, musicFolders));
        Collections.shuffle(songs);
        songs = songs.subList(0, Math.min(count, songs.size()));
        return forMediaFiles(songs, username, request);
    }

    public List<AbstractMedia> forTopSongs(int mediaFileId, String username, HttpServletRequest request) {
        MediaFile artist = mediaFileService.getMediaFile(mediaFileId);
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaFile> songs = filterMusic(lastFmService.getTopSongs(artist, 100, musicFolders));
        return forMediaFiles(songs, username, request);
    }

    public List<AbstractMedia> forLibrary(String username, HttpServletRequest request) {
        List<AbstractMedia> result = new ArrayList<AbstractMedia>();

        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        if (musicFolders.size() == 1) {
            return forMusicFolder(musicFolders.get(0), username, request);
        }

        for (MusicFolder musicFolder : musicFolders) {
            MediaCollection mediaCollection = new MediaCollection();
            mediaCollection.setItemType(ItemType.COLLECTION);
            mediaCollection.setId(SonosService.ID_MUSICFOLDER_PREFIX + musicFolder.getId());
            mediaCollection.setTitle(musicFolder.getName());
            result.add(mediaCollection);
        }
        return result;
    }

    public List<AbstractMedia> forMusicFolder(int musicFolderId, String username, HttpServletRequest request) {
        return forMusicFolder(settingsService.getMusicFolderById(musicFolderId), username, request);
    }

    public List<AbstractMedia> forMusicFolder(MusicFolder musicFolder, String username,
            HttpServletRequest request) {
        try {
            List<AbstractMedia> result = new ArrayList<AbstractMedia>();

            MediaMetadata shuffle = new MediaMetadata();
            shuffle.setItemType(ItemType.PROGRAM);
            shuffle.setId(SonosService.ID_SHUFFLE_MUSICFOLDER_PREFIX + musicFolder.getId());
            shuffle.setTitle("Shuffle Play");
            result.add(shuffle);

            for (MediaFile shortcut : musicIndexService.getShortcuts(Arrays.asList(musicFolder))) {
                result.add(forDirectory(shortcut, request, username));
            }

            MusicFolderContent musicFolderContent = musicIndexService
                    .getMusicFolderContent(Arrays.asList(musicFolder), false);
            for (List<MusicIndex.SortableArtistWithMediaFiles> artists : musicFolderContent.getIndexedArtists()
                    .values()) {
                for (MusicIndex.SortableArtistWithMediaFiles artist : artists) {
                    for (MediaFile artistMediaFile : artist.getMediaFiles()) {
                        result.add(forDirectory(artistMediaFile, request, username));
                    }
                }
            }
            for (MediaFile song : musicFolderContent.getSingleSongs()) {
                if (song.isAudio()) {
                    result.add(forSong(song, username, request));
                }
            }
            return result;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public List<AbstractMedia> forDirectoryContent(int mediaFileId, String username, HttpServletRequest request) {
        List<AbstractMedia> result = new ArrayList<AbstractMedia>();
        MediaFile dir = mediaFileService.getMediaFile(mediaFileId);
        List<MediaFile> children = dir.isFile() ? Arrays.asList(dir)
                : mediaFileService.getChildrenOf(dir, true, true, true);
        boolean isArtist = true;
        for (MediaFile child : children) {
            if (child.isDirectory()) {
                result.add(forDirectory(child, request, username));
                isArtist &= child.isAlbum();
            } else if (child.isAudio()) {
                isArtist = false;
                result.add(forSong(child, username, request));
            }
        }

        if (isArtist) {
            MediaMetadata shuffle = new MediaMetadata();
            shuffle.setItemType(ItemType.PROGRAM);
            shuffle.setId(SonosService.ID_SHUFFLE_ARTIST_PREFIX + mediaFileId);
            shuffle.setTitle(String.format("Shuffle Play  - %s", dir.getName()));
            result.add(0, shuffle);

            MediaMetadata radio = new MediaMetadata();
            radio.setItemType(ItemType.PROGRAM);
            radio.setId(SonosService.ID_RADIO_ARTIST_PREFIX + mediaFileId);
            radio.setTitle(String.format("Artist Radio - %s", dir.getName()));
            result.add(1, radio);

            MediaCollection topSongs = new MediaCollection();
            topSongs.setItemType(ItemType.TRACK);
            topSongs.setCanPlay(true);
            topSongs.setId(SonosService.ID_TOP_SONGS_PREFIX + mediaFileId);
            topSongs.setTitle(String.format("Top Songs - %s", dir.getName()));
            result.add(2, topSongs);
        }

        return result;
    }

    private MediaCollection forDirectory(MediaFile dir, HttpServletRequest request, String username) {
        mediaFileService.populateStarredDate(dir, username);
        MediaCollection mediaCollection = new MediaCollection();

        mediaCollection.setId(String.valueOf(dir.getId()));
        mediaCollection.setIsFavorite(dir.getStarredDate() != null);
        if (dir.isAlbum()) {
            mediaCollection.setItemType(ItemType.ALBUM);
            mediaCollection.setArtist(dir.getArtist());
            mediaCollection.setTitle(dir.getName());
            mediaCollection.setCanPlay(true);

            AlbumArtUrl albumArtUrl = new AlbumArtUrl();
            albumArtUrl.setValue(getCoverArtUrl(dir, request));
            mediaCollection.setAlbumArtURI(albumArtUrl);
        } else {
            mediaCollection.setItemType(ItemType.ARTIST);
            mediaCollection.setTitle(dir.getName());

            if (lastFmService.isArtistBioCached(dir.getName())) {
                ArtistBio artistBio = lastFmService.getArtistBio(dir);
                if (artistBio != null && artistBio.getSmallImageUrl() != null) {
                    AlbumArtUrl artistImageUrl = new AlbumArtUrl();
                    artistImageUrl.setValue(artistBio.getMediumImageUrl());
                    mediaCollection.setAlbumArtURI(artistImageUrl);
                }
            }
        }
        return mediaCollection;
    }

    public List<MediaCollection> forPlaylists(String username, HttpServletRequest request) {
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        for (Playlist playlist : playlistService.getReadablePlaylistsForUser(username)) {
            MediaCollection mediaCollection = new MediaCollection();
            AlbumArtUrl albumArtUrl = new AlbumArtUrl();
            albumArtUrl.setValue(
                    getCoverArtUrl(CoverArtController.PLAYLIST_COVERART_PREFIX + playlist.getId(), request));

            mediaCollection.setId(SonosService.ID_PLAYLIST_PREFIX + playlist.getId());
            mediaCollection.setCanPlay(true);
            mediaCollection.setReadOnly(!username.equals(playlist.getUsername()));
            mediaCollection.setRenameable(username.equals(playlist.getUsername()));
            mediaCollection.setUserContent(false);
            mediaCollection.setItemType(ItemType.PLAYLIST);
            mediaCollection.setArtist(playlist.getUsername());
            mediaCollection.setTitle(playlist.getName());
            mediaCollection.setAlbumArtURI(albumArtUrl);
            result.add(mediaCollection);
        }
        return result;
    }

    public List<MediaCollection> forAlbumLists() {
        List<MediaCollection> result = new ArrayList<MediaCollection>();

        for (AlbumListType albumListType : AlbumListType.values()) {
            MediaCollection mediaCollection = new MediaCollection();
            mediaCollection.setId(SonosService.ID_ALBUMLIST_PREFIX + albumListType.getId());
            mediaCollection.setItemType(ItemType.ALBUM_LIST);
            mediaCollection.setTitle(albumListType.getDescription());
            result.add(mediaCollection);
        }
        return result;
    }

    public List<MediaCollection> forPodcastChannels(HttpServletRequest request) {
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        for (PodcastChannel channel : podcastService.getAllChannels()) {
            AlbumArtUrl albumArtUri = new AlbumArtUrl();
            albumArtUri.setValue(
                    getCoverArtUrl(CoverArtController.PODCAST_COVERART_PREFIX + channel.getId(), request));

            MediaCollection mediaCollection = new MediaCollection();
            mediaCollection.setId(SonosService.ID_PODCAST_CHANNEL_PREFIX + channel.getId());
            mediaCollection.setTitle(channel.getTitle());
            mediaCollection.setItemType(ItemType.TRACK);
            mediaCollection.setAlbumArtURI(albumArtUri);
            result.add(mediaCollection);
        }
        return result;
    }

    public List<AbstractMedia> forPodcastChannel(int channelId, String username, HttpServletRequest request) {
        List<AbstractMedia> result = new ArrayList<AbstractMedia>();
        for (PodcastEpisode episode : podcastService.getEpisodes(channelId)) {
            if (episode.getStatus() == PodcastStatus.COMPLETED) {
                Integer mediaFileId = episode.getMediaFileId();
                MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
                if (mediaFile != null) {
                    result.add(forMediaFile(mediaFile, username, request));
                }
            }
        }
        return result;
    }

    public MediaList forAlbumList(AlbumListType albumListType, int offset, int count, String username,
            HttpServletRequest request) {
        if (albumListType == AlbumListType.DECADE) {
            return forDecades(offset, count);
        }
        if (albumListType == AlbumListType.GENRE) {
            return forGenres(offset, count);
        }

        MediaList mediaList = new MediaList();

        boolean includeShuffle = offset == 0;
        if (includeShuffle) {
            count--;
            MediaMetadata shuffle = new MediaMetadata();
            shuffle.setItemType(ItemType.PROGRAM);
            shuffle.setId(SonosService.ID_SHUFFLE_ALBUMLIST_PREFIX + albumListType.getId());
            shuffle.setTitle(String.format("Shuffle Play - %s", albumListType.getDescription()));
            mediaList.getMediaCollectionOrMediaMetadata().add(shuffle);
        }

        AlbumList albumList = createAlbumList(albumListType, offset - (includeShuffle ? 0 : 1), count, username);
        for (MediaFile album : albumList.getAlbums()) {
            mediaList.getMediaCollectionOrMediaMetadata().add(forDirectory(album, request, username));
        }

        mediaList.setIndex(offset);
        mediaList.setCount(mediaList.getMediaCollectionOrMediaMetadata().size());
        mediaList.setTotal(albumList.getTotal() + 1);
        return mediaList;
    }

    private AlbumList createAlbumList(AlbumListType albumListType, int offset, int count, String username) {
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaFile> albums = Collections.emptyList();
        int total = 0;
        switch (albumListType) {
        case RANDOM:
            albums = searchService.getRandomAlbums(count, musicFolders);
            total = mediaFileService.getAlbumCount(musicFolders);
            break;
        case NEWEST:
            albums = mediaFileService.getNewestAlbums(offset, count, musicFolders);
            total = mediaFileService.getAlbumCount(musicFolders);
            break;
        case STARRED:
            albums = mediaFileService.getStarredAlbums(offset, count, username, musicFolders);
            total = mediaFileService.getStarredAlbumCount(username, musicFolders);
            break;
        case HIGHEST:
            albums = ratingService.getHighestRatedAlbumsForUser(offset, count, username, musicFolders);
            total = ratingService.getRatedAlbumCount(username, musicFolders);
            break;
        case FREQUENT:
            albums = mediaFileService.getMostFrequentlyPlayedAlbums(offset, count, musicFolders);
            total = mediaFileService.getPlayedAlbumCount(musicFolders);
            break;
        case RECENT:
            albums = mediaFileService.getMostRecentlyPlayedAlbums(offset, count, musicFolders);
            total = mediaFileService.getPlayedAlbumCount(musicFolders);
            break;
        case ALPHABETICAL:
            albums = mediaFileService.getAlphabeticalAlbums(offset, count, true, musicFolders);
            total = mediaFileService.getAlbumCount(musicFolders);
            break;
        }
        return new AlbumList(albums, total);
    }

    private MediaList forDecades(int offset, int count) {
        List<MediaCollection> mediaCollections = new ArrayList<MediaCollection>();
        int currentDecade = Calendar.getInstance().get(Calendar.YEAR) / 10;
        for (int i = 0; i < 10; i++) {
            int decade = (currentDecade - i) * 10;
            MediaCollection mediaCollection = new MediaCollection();
            mediaCollection.setItemType(ItemType.ALBUM_LIST);
            mediaCollection.setId(SonosService.ID_DECADE_PREFIX + decade);
            mediaCollection.setTitle(String.valueOf(decade));
            mediaCollections.add(mediaCollection);
        }

        return createSubList(offset, count, mediaCollections);
    }

    private MediaList forGenres(int offset, int count) {
        List<MediaCollection> mediaCollections = new ArrayList<MediaCollection>();
        List<Genre> genres = mediaFileService.getGenres(true);
        for (int i = 0; i < genres.size(); i++) {
            Genre genre = genres.get(i);
            MediaCollection mediaCollection = new MediaCollection();
            mediaCollection.setItemType(ItemType.ALBUM_LIST);
            mediaCollection.setId(SonosService.ID_GENRE_PREFIX + i);
            mediaCollection.setTitle(genre.getName() + " (" + genre.getAlbumCount() + ")");
            mediaCollections.add(mediaCollection);
        }

        return createSubList(offset, count, mediaCollections);
    }

    public List<MediaCollection> forDecade(int decade, String username, HttpServletRequest request) {
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        for (MediaFile album : mediaFileService.getAlbumsByYear(0, Integer.MAX_VALUE, decade, decade + 9,
                musicFolders)) {
            result.add(forDirectory(album, request, username));
        }
        return result;
    }

    public List<MediaCollection> forGenre(int genreIndex, String username, HttpServletRequest request) {
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        Genre genre = mediaFileService.getGenres(true).get(genreIndex);
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        for (MediaFile album : mediaFileService.getAlbumsByGenre(0, Integer.MAX_VALUE, genre.getName(),
                musicFolders)) {
            result.add(forDirectory(album, request, username));
        }
        return result;
    }

    public List<MediaMetadata> forPlaylist(int playlistId, String username, HttpServletRequest request) {
        List<MediaMetadata> result = new ArrayList<MediaMetadata>();
        for (MediaFile song : playlistService.getFilesInPlaylist(playlistId)) {
            if (song.isAudio()) {
                result.add(forSong(song, username, request));
            }
        }
        return result;
    }

    public List<MediaCollection> forStarred() {
        MediaCollection artists = new MediaCollection();
        artists.setItemType(ItemType.ARTIST);
        artists.setId(SonosService.ID_STARRED_ARTISTS);
        artists.setTitle("Starred Artists");

        MediaCollection albums = new MediaCollection();
        albums.setItemType(ItemType.ALBUM_LIST);
        albums.setId(SonosService.ID_STARRED_ALBUMS);
        albums.setTitle("Starred Albums");

        MediaCollection songs = new MediaCollection();
        songs.setItemType(ItemType.TRACK_LIST);
        songs.setId(SonosService.ID_STARRED_SONGS);
        songs.setCanPlay(true);
        songs.setTitle("Starred Songs");

        return Arrays.asList(artists, albums, songs);
    }

    public List<MediaCollection> forStarredArtists(String username, HttpServletRequest request) {
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        for (MediaFile artist : mediaFileDao.getStarredDirectories(0, Integer.MAX_VALUE, username, musicFolders)) {
            MediaCollection mediaCollection = forDirectory(artist, request, username);
            mediaCollection.setItemType(ItemType.ARTIST);
            result.add(mediaCollection);
        }
        return result;
    }

    public List<MediaCollection> forStarredAlbums(String username, HttpServletRequest request) {
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaCollection> result = new ArrayList<MediaCollection>();
        for (MediaFile album : mediaFileDao.getStarredAlbums(0, Integer.MAX_VALUE, username, musicFolders)) {
            MediaCollection mediaCollection = forDirectory(album, request, username);
            mediaCollection.setItemType(ItemType.ALBUM);
            result.add(mediaCollection);
        }
        return result;
    }

    public List<MediaMetadata> forStarredSongs(String username, HttpServletRequest request) {
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaMetadata> result = new ArrayList<MediaMetadata>();
        for (MediaFile song : mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders)) {
            if (song.isAudio()) {
                result.add(forSong(song, username, request));
            }
        }
        return result;
    }

    public List<MediaCollection> forSearchCategories() {
        MediaCollection artists = new MediaCollection();
        artists.setItemType(ItemType.ARTIST);
        artists.setId(SonosService.ID_SEARCH_ARTISTS);
        artists.setTitle("Artists");

        MediaCollection albums = new MediaCollection();
        albums.setItemType(ItemType.ALBUM);
        albums.setId(SonosService.ID_SEARCH_ALBUMS);
        albums.setTitle("Albums");

        MediaCollection songs = new MediaCollection();
        songs.setItemType(ItemType.TRACK);
        songs.setId(SonosService.ID_SEARCH_SONGS);
        songs.setTitle("Songs");

        return Arrays.asList(artists, albums, songs);
    }

    public MediaList forSearch(String query, int offset, int count, SearchService.IndexType indexType,
            String username, HttpServletRequest request) {

        SearchCriteria searchCriteria = new SearchCriteria();
        searchCriteria.setCount(count);
        searchCriteria.setOffset(offset);
        searchCriteria.setQuery(query);
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);

        SearchResult searchResult = searchService.search(searchCriteria, musicFolders, indexType);

        MediaList result = new MediaList();
        result.setTotal(searchResult.getTotalHits());
        result.setIndex(offset);
        result.setCount(searchResult.getMediaFiles().size());
        for (MediaFile mediaFile : searchResult.getMediaFiles()) {
            result.getMediaCollectionOrMediaMetadata().add(forMediaFile(mediaFile, username, request));
        }

        return result;
    }

    public List<AbstractMedia> forSimilarArtists(int mediaFileId, String username, HttpServletRequest request) {
        MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
        List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
        List<MediaFile> similarArtists = lastFmService.getSimilarArtists(mediaFile, 100, false, musicFolders);
        return forMediaFiles(similarArtists, username, request);
    }

    private List<AbstractMedia> forMediaFiles(List<MediaFile> mediaFiles, String username,
            HttpServletRequest request) {
        List<AbstractMedia> result = new ArrayList<AbstractMedia>();
        for (MediaFile mediaFile : mediaFiles) {
            result.add(forMediaFile(mediaFile, username, request));
        }
        return result;
    }

    public AbstractMedia forMediaFile(MediaFile mediaFile, String username, HttpServletRequest request) {
        return mediaFile.isFile() ? forSong(mediaFile, username, request)
                : forDirectory(mediaFile, request, username);
    }

    public MediaMetadata forSong(MediaFile song, String username, HttpServletRequest request) {
        Player player = createPlayerIfNecessary(username);
        String suffix = transcodingService.getSuffix(player, song, null);
        mediaFileService.populateStarredDate(song, username);

        MediaMetadata result = new MediaMetadata();
        result.setId(String.valueOf(song.getId()));
        result.setItemType(ItemType.TRACK);
        result.setMimeType(StringUtil.getMimeType(suffix, true));
        result.setTitle(song.getTitle());
        result.setGenre(song.getGenre());
        result.setIsFavorite(song.getStarredDate() != null);
        //        result.setDynamic();// TODO: For starred songs

        AlbumArtUrl albumArtUrl = new AlbumArtUrl();
        albumArtUrl.setValue(getCoverArtUrl(song, request));

        TrackMetadata trackMetadata = new TrackMetadata();
        trackMetadata.setArtist(song.getArtist());
        trackMetadata.setAlbumArtist(song.getAlbumArtist());
        trackMetadata.setAlbum(song.getAlbumName());
        trackMetadata.setAlbumArtURI(albumArtUrl);
        trackMetadata.setDuration(song.getDurationSeconds());
        trackMetadata.setTrackNumber(song.getTrackNumber());

        MediaFile parent = mediaFileService.getParentOf(song);
        if (parent != null && parent.isAlbum()) {
            trackMetadata.setAlbumId(String.valueOf(parent.getId()));
        }
        result.setTrackMetadata(trackMetadata);

        return result;
    }

    public void star(int id, String username) {
        mediaFileDao.starMediaFile(id, username);
    }

    public void unstar(int id, String username) {
        mediaFileDao.unstarMediaFile(id, username);
    }

    private String getCoverArtUrl(String id, HttpServletRequest request) {
        return getBaseUrl(request) + "coverArt.view?id=" + id + "&size=" + CoverArtScheme.LARGE.getSize();
    }

    private String getCoverArtUrl(MediaFile file, HttpServletRequest request) {
        return getBaseUrl(request) + "coverArt.view?id=" + file.getId() + "&auth=" + file.getHash() + "&size="
                + CoverArtScheme.LARGE.getSize();
    }

    public static MediaList createSubList(int index, int count, List<? extends AbstractMedia> media) {
        MediaList result = new MediaList();
        List<? extends AbstractMedia> selectedMedia = Util.subList(media, index, count);

        result.setIndex(index);
        result.setCount(selectedMedia.size());
        result.setTotal(media.size());
        result.getMediaCollectionOrMediaMetadata().addAll(selectedMedia);

        return result;
    }

    private List<MediaFile> filterMusic(List<MediaFile> files) {
        return Lists.newArrayList(Iterables.filter(files, new Predicate<MediaFile>() {
            @Override
            public boolean apply(MediaFile input) {
                return input.getMediaType() == MediaFile.MediaType.MUSIC;
            }
        }));
    }

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

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

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

    public String getMediaURI(int mediaFileId, String username, HttpServletRequest request) {
        Player player = createPlayerIfNecessary(username);
        MediaFile song = mediaFileService.getMediaFile(mediaFileId);

        return getBaseUrl(request) + "stream?id=" + song.getId() + "&auth=" + song.getHash() + "&player="
                + player.getId();
    }

    private Player createPlayerIfNecessary(String username) {
        List<Player> players = playerService.getPlayersForUserAndClientId(username, SUBSONIC_CLIENT_ID);

        // If not found, create it.
        if (players.isEmpty()) {
            Player player = new Player();
            player.setUsername(username);
            player.setClientId(SUBSONIC_CLIENT_ID);
            player.setName("Sonos");
            player.setTechnology(PlayerTechnology.EXTERNAL_WITH_PLAYLIST);
            playerService.createPlayer(player);
            players = playerService.getPlayersForUserAndClientId(username, SUBSONIC_CLIENT_ID);
        }

        return players.get(0);
    }

    public String getBaseUrl(HttpServletRequest request) {
        int port = settingsService.getPort();
        String contextPath = settingsService.getUrlRedirectContextPath();

        // Note that the server IP can be overridden by the "ip" parameter. Used when Subsonic and Sonos are
        // on different networks.
        String ip = settingsService.getLocalIpAddress();
        if (request != null) {
            ip = ServletRequestUtils.getStringParameter(request, "ip", ip);
        }

        // Note: Serving media and cover art with http (as opposed to https) works when using jetty and SubsonicDeployer.
        StringBuilder url = new StringBuilder("http://").append(ip).append(":").append(port).append("/");

        if (StringUtils.isNotEmpty(contextPath)) {
            url.append(contextPath).append("/");
        }
        return url.toString();
    }

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

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

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

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

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

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

    public void setLastFmService(LastFmService lastFmService) {
        this.lastFmService = lastFmService;
    }

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