This replaces the existing FolderBasedContentDirectory with a new DispatchingContentDirectory. The new system then dispatches requests to subclasses of UpnpContentProcessor, which then handle the requests as appropriate. This version also implements a new searchByName method in SearchService, which is used to provide artist, album, and track search functionality for the upnp service.master
parent
bcd330e74d
commit
b3ae8faac6
@ -0,0 +1,59 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2016 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.domain; |
||||||
|
|
||||||
|
import org.libresonic.player.service.SearchService; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* The outcome of a search. |
||||||
|
* |
||||||
|
* @author Sindre Mehus |
||||||
|
* @see SearchService#search |
||||||
|
*/ |
||||||
|
public class ParamSearchResult<T> { |
||||||
|
|
||||||
|
private final List<T> items = new ArrayList<T>(); |
||||||
|
|
||||||
|
private int offset; |
||||||
|
private int totalHits; |
||||||
|
|
||||||
|
public List<T> getItems() { |
||||||
|
return items; |
||||||
|
} |
||||||
|
|
||||||
|
public int getOffset() { |
||||||
|
return offset; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOffset(int offset) { |
||||||
|
this.offset = offset; |
||||||
|
} |
||||||
|
|
||||||
|
public int getTotalHits() { |
||||||
|
return totalHits; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTotalHits(int totalHits) { |
||||||
|
this.totalHits = totalHits; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.PersonWithRole; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.MusicAlbum; |
||||||
|
import org.libresonic.player.dao.AlbumDao; |
||||||
|
import org.libresonic.player.dao.MediaFileDao; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.service.SearchService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.web.util.UriComponentsBuilder; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
import java.net.URISyntaxException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class AlbumUpnpProcessor extends UpnpContentProcessor <Album, MediaFile> { |
||||||
|
|
||||||
|
public static final String ALL_BY_ARTIST = "allByArtist"; |
||||||
|
public static final String ALL_RECENT = "allRecent"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
AlbumDao albumDao; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
SearchService searchService; |
||||||
|
|
||||||
|
public AlbumUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_ALBUM_PREFIX); |
||||||
|
setRootTitle("Albums"); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Album album) throws Exception { |
||||||
|
MusicAlbum container = new MusicAlbum(); |
||||||
|
|
||||||
|
if (album.getId() == -1) { |
||||||
|
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + album.getComment()); |
||||||
|
} else { |
||||||
|
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + album.getId()); |
||||||
|
container.setAlbumArtURIs(new URI[] { getAlbumArtURI(album.getId()) }); |
||||||
|
container.setDescription(album.getComment()); |
||||||
|
} |
||||||
|
container.setParentID(getRootId()); |
||||||
|
container.setTitle(album.getName()); |
||||||
|
// TODO: correct artist?
|
||||||
|
if (album.getArtist() != null) { |
||||||
|
container.setArtists(getAlbumArtists(album.getArtist())); |
||||||
|
} |
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Album> getAllItems() { |
||||||
|
List<MusicFolder> allFolders = getDispatchingContentDirectory().getSettingsService().getAllMusicFolders(); |
||||||
|
return getAlbumDao().getAlphabetialAlbums(0, 0, false, allFolders); |
||||||
|
} |
||||||
|
|
||||||
|
public Album getItemById(String id) throws Exception { |
||||||
|
Album returnValue = null; |
||||||
|
if (id.startsWith(ALL_BY_ARTIST) || id.equalsIgnoreCase(ALL_RECENT)) { |
||||||
|
returnValue = new Album(); |
||||||
|
returnValue.setId(-1); |
||||||
|
returnValue.setComment(id); |
||||||
|
} else { |
||||||
|
returnValue = getAlbumDao().getAlbum(Integer.parseInt(id)); |
||||||
|
} |
||||||
|
return returnValue; |
||||||
|
} |
||||||
|
|
||||||
|
public List<MediaFile> getChildren(Album album) throws Exception { |
||||||
|
List<MediaFile> allFiles = getMediaFileDao().getSongsForAlbum(album.getArtist(), album.getName()); |
||||||
|
if (album.getId() == -1) { |
||||||
|
List<Album> albumList = null; |
||||||
|
if (album.getComment().startsWith(ALL_BY_ARTIST)) { |
||||||
|
ArtistUpnpProcessor ap = getDispatcher().getArtistProcessor(); |
||||||
|
albumList = ap.getChildren(ap.getItemById(album.getComment().replaceAll(ALL_BY_ARTIST + "_", ""))); |
||||||
|
} else if (album.getComment().equalsIgnoreCase(ALL_RECENT)) { |
||||||
|
albumList = getDispatcher().getRecentAlbumProcessor().getAllItems(); |
||||||
|
} |
||||||
|
for (Album a: albumList) { |
||||||
|
if (a.getId() != -1) { |
||||||
|
allFiles.addAll(getMediaFileDao().getSongsForAlbum(a.getArtist(), a.getName())); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
allFiles = getMediaFileDao().getSongsForAlbum(album.getArtist(), album.getName()); |
||||||
|
} |
||||||
|
return allFiles; |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, MediaFile child) throws Exception { |
||||||
|
didl.addItem(getDispatcher().getMediaFileProcessor().createItem(child)); |
||||||
|
} |
||||||
|
|
||||||
|
public URI getAlbumArtURI(int albumId) throws URISyntaxException { |
||||||
|
return getDispatcher().getJwtSecurityService().addJWTToken(UriComponentsBuilder.fromUriString(getDispatcher().getBaseUrl() + "/ext/coverArt.view").queryParam("id", albumId).queryParam("size", CoverArtScheme.LARGE.getSize())).build().encode().toUri(); |
||||||
|
} |
||||||
|
|
||||||
|
public PersonWithRole[] getAlbumArtists(String artist) { |
||||||
|
return new PersonWithRole[] { new PersonWithRole(artist) }; |
||||||
|
} |
||||||
|
|
||||||
|
public AlbumDao getAlbumDao() { |
||||||
|
return albumDao; |
||||||
|
} |
||||||
|
public void setAlbumDao(AlbumDao albumDao) { |
||||||
|
this.albumDao = albumDao; |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFileDao getMediaFileDao() { |
||||||
|
return getDispatcher().getMediaFileProcessor().getMediaFileDao(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
|
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.MusicArtist; |
||||||
|
import org.libresonic.player.dao.ArtistDao; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.service.SearchService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class ArtistUpnpProcessor extends UpnpContentProcessor <Artist, Album> { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ArtistDao artistDao; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
SearchService searchService; |
||||||
|
|
||||||
|
public ArtistUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_ARTIST_PREFIX); |
||||||
|
setRootTitle("Artists"); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Artist artist) { |
||||||
|
MusicArtist container = new MusicArtist(); |
||||||
|
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + artist.getId()); |
||||||
|
container.setParentID(getRootId()); |
||||||
|
container.setTitle(artist.getName()); |
||||||
|
container.setChildCount(artist.getAlbumCount()); |
||||||
|
|
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Artist> getAllItems() { |
||||||
|
List<MusicFolder> allFolders = getDispatcher().getSettingsService().getAllMusicFolders(); |
||||||
|
List<Artist> allArtists = getArtistDao().getAlphabetialArtists(0, Integer.MAX_VALUE, allFolders); |
||||||
|
// alpha artists doesn't quite work :P
|
||||||
|
allArtists.sort((Artist o1, Artist o2)->o1.getName().replaceAll("\\W", "").compareToIgnoreCase(o2.getName().replaceAll("\\W", ""))); |
||||||
|
|
||||||
|
return allArtists; |
||||||
|
} |
||||||
|
|
||||||
|
public Artist getItemById(String id) throws Exception { |
||||||
|
return getArtistDao().getArtist(Integer.parseInt(id)); |
||||||
|
} |
||||||
|
|
||||||
|
public List<Album> getChildren(Artist artist) { |
||||||
|
List<MusicFolder> allFolders = getDispatcher().getSettingsService().getAllMusicFolders(); |
||||||
|
List<Album> allAlbums = getAlbumProcessor().getAlbumDao().getAlbumsForArtist(artist.getName(), allFolders); |
||||||
|
if (allAlbums.size() > 1) { |
||||||
|
Album viewAll = new Album(); |
||||||
|
viewAll.setName("- All Albums -"); |
||||||
|
viewAll.setId(-1); |
||||||
|
viewAll.setComment(AlbumUpnpProcessor.ALL_BY_ARTIST + "_" + artist.getId()); |
||||||
|
allAlbums.add(0, viewAll); |
||||||
|
} |
||||||
|
return allAlbums; |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, Album album) throws Exception { |
||||||
|
didl.addContainer(getAlbumProcessor().createContainer(album)); |
||||||
|
} |
||||||
|
|
||||||
|
public ArtistDao getArtistDao() { |
||||||
|
return artistDao; |
||||||
|
} |
||||||
|
public void setArtistDao(ArtistDao artistDao) { |
||||||
|
this.artistDao = artistDao; |
||||||
|
} |
||||||
|
|
||||||
|
public AlbumUpnpProcessor getAlbumProcessor() { |
||||||
|
return getDispatcher().getAlbumProcessor(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,292 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2016 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
|
||||||
|
import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode; |
||||||
|
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException; |
||||||
|
import org.fourthline.cling.support.model.BrowseFlag; |
||||||
|
import org.fourthline.cling.support.model.BrowseResult; |
||||||
|
import org.fourthline.cling.support.model.DIDLObject; |
||||||
|
import org.fourthline.cling.support.model.PersonWithRole; |
||||||
|
import org.fourthline.cling.support.model.SortCriterion; |
||||||
|
import org.fourthline.cling.support.model.item.Item; |
||||||
|
import org.fourthline.cling.support.model.item.MusicTrack; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.service.JWTSecurityService; |
||||||
|
import org.libresonic.player.service.MediaFileService; |
||||||
|
import org.libresonic.player.service.MusicIndexService; |
||||||
|
import org.libresonic.player.service.PlaylistService; |
||||||
|
import org.libresonic.player.service.SearchService; |
||||||
|
import org.libresonic.player.service.SettingsService; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.web.util.UriComponentsBuilder; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
import java.net.URISyntaxException; |
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @author Sindre Mehus |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class DispatchingContentDirectory extends LibresonicContentDirectory { |
||||||
|
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(FolderBasedContentDirectory.class); |
||||||
|
|
||||||
|
public static final String CONTAINER_ID_ROOT = "0"; |
||||||
|
public static final String CONTAINER_ID_PLAYLIST_PREFIX = "playlist"; |
||||||
|
public static final String CONTAINER_ID_FOLDER_PREFIX = "folder"; |
||||||
|
public static final String CONTAINER_ID_ALBUM_PREFIX = "album"; |
||||||
|
public static final String CONTAINER_ID_ARTIST_PREFIX = "artist"; |
||||||
|
public static final String CONTAINER_ID_ARTISTALBUM_PREFIX = "artistalbum"; |
||||||
|
public static final String CONTAINER_ID_GENRE_PREFIX = "genre"; |
||||||
|
public static final String CONTAINER_ID_RECENT_PREFIX = "recent"; |
||||||
|
|
||||||
|
protected static final String SEPARATOR = "-"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private PlaylistUpnpProcessor playlistProcessor; |
||||||
|
@Autowired |
||||||
|
private MediaFileUpnpProcessor mediaFileProcessor; |
||||||
|
//@Autowired can't autowire because of the subclassing :P
|
||||||
|
private AlbumUpnpProcessor albumProcessor; |
||||||
|
//@Autowired can't autowire because of the subclassing :P
|
||||||
|
private RecentAlbumUpnpProcessor recentAlbumProcessor; |
||||||
|
@Autowired |
||||||
|
private ArtistUpnpProcessor artistProcessor; |
||||||
|
@Autowired |
||||||
|
private GenreUpnpProcessor genreProcessor; |
||||||
|
@Autowired |
||||||
|
private RootUpnpProcessor rootProcessor; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MediaFileService mediaFileService; |
||||||
|
|
||||||
|
private PlaylistService playlistService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MusicIndexService musicIndexService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private SearchService searchService; |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public BrowseResult browse(String objectId, BrowseFlag browseFlag, |
||||||
|
String filter, long firstResult, |
||||||
|
long maxResults, SortCriterion[] orderBy) |
||||||
|
throws ContentDirectoryException { |
||||||
|
|
||||||
|
LOG.info("UPnP request - objectId: " + objectId + ", browseFlag: " + browseFlag + ", filter: " + filter + ", firstResult: " + firstResult + ", maxResults: " + maxResults); |
||||||
|
|
||||||
|
if (objectId == null) |
||||||
|
throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS, "objectId is null"); |
||||||
|
|
||||||
|
// maxResult == 0 means all.
|
||||||
|
if (maxResults == 0) { |
||||||
|
maxResults = Long.MAX_VALUE; |
||||||
|
} |
||||||
|
|
||||||
|
BrowseResult returnValue = null; |
||||||
|
try { |
||||||
|
String[] splitId = objectId.split(SEPARATOR); |
||||||
|
String browseRoot = splitId[0]; |
||||||
|
String itemId = splitId.length == 1 ? null : splitId[1]; |
||||||
|
|
||||||
|
UpnpContentProcessor processor = findProcessor(browseRoot); |
||||||
|
if (processor == null) { |
||||||
|
// if it's null then assume it's a file, and that the id
|
||||||
|
// is all that's there.
|
||||||
|
itemId = browseRoot; |
||||||
|
browseRoot = "folder"; |
||||||
|
processor = getMediaFileProcessor(); |
||||||
|
} |
||||||
|
|
||||||
|
if (itemId == null) { |
||||||
|
returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseRootMetadata() : processor.browseRoot(filter, firstResult, maxResults, orderBy); |
||||||
|
} else { |
||||||
|
returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseObjectMetadata(itemId) : processor.browseObject(itemId, filter, firstResult, maxResults, orderBy); |
||||||
|
} |
||||||
|
return returnValue; |
||||||
|
} catch (Throwable x) { |
||||||
|
LOG.error("UPnP error: " + x, x); |
||||||
|
throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS, x.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BrowseResult search(String containerId, |
||||||
|
String searchCriteria, String filter, |
||||||
|
long firstResult, long maxResults, |
||||||
|
SortCriterion[] orderBy) throws ContentDirectoryException { |
||||||
|
// i don't see a parser for upnp search criteria anywhere, so this will
|
||||||
|
// have to do
|
||||||
|
String upnpClass = searchCriteria.replaceAll("^.*upnp:class\\s+[\\S]+\\s+\"([\\S]*)\".*$", "$1"); |
||||||
|
String titleSearch = searchCriteria.replaceAll("^.*dc:title\\s+[\\S]+\\s+\"([\\S]*)\".*$", "$1"); |
||||||
|
BrowseResult returnValue = null; |
||||||
|
if ("object.container.person.musicArtist".equalsIgnoreCase(upnpClass)) { |
||||||
|
returnValue = getArtistProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy); |
||||||
|
} else if ("object.item.audioItem".equalsIgnoreCase(upnpClass)) { |
||||||
|
returnValue = getMediaFileProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy); |
||||||
|
} else if ("object.container.album.musicAlbum".equalsIgnoreCase(upnpClass)) { |
||||||
|
returnValue = getAlbumProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy); |
||||||
|
} |
||||||
|
|
||||||
|
return returnValue != null ? returnValue : super.search(containerId, searchCriteria, filter, firstResult, maxResults, orderBy); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private UpnpContentProcessor findProcessor(String type) { |
||||||
|
switch(type) { |
||||||
|
case CONTAINER_ID_ROOT: |
||||||
|
return getRootProcessor(); |
||||||
|
case CONTAINER_ID_PLAYLIST_PREFIX: |
||||||
|
return getPlaylistProcessor(); |
||||||
|
case CONTAINER_ID_FOLDER_PREFIX: |
||||||
|
return getMediaFileProcessor(); |
||||||
|
case CONTAINER_ID_ALBUM_PREFIX: |
||||||
|
return getAlbumProcessor(); |
||||||
|
case CONTAINER_ID_RECENT_PREFIX: |
||||||
|
return getRecentAlbumProcessor(); |
||||||
|
case CONTAINER_ID_ARTIST_PREFIX: |
||||||
|
return getArtistProcessor(); |
||||||
|
case CONTAINER_ID_GENRE_PREFIX: |
||||||
|
return getGenreProcessor(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Item createItem(MediaFile song) throws Exception { |
||||||
|
MediaFile parent = mediaFileService.getParentOf(song); |
||||||
|
MusicTrack item = new MusicTrack(); |
||||||
|
item.setId(String.valueOf(song.getId())); |
||||||
|
item.setParentID(String.valueOf(parent.getId())); |
||||||
|
item.setTitle(song.getTitle()); |
||||||
|
item.setAlbum(song.getAlbumName()); |
||||||
|
if (song.getArtist() != null) { |
||||||
|
item.setArtists(new PersonWithRole[]{new PersonWithRole(song.getArtist())}); |
||||||
|
} |
||||||
|
Integer year = song.getYear(); |
||||||
|
if (year != null) { |
||||||
|
item.setDate(year + "-01-01"); |
||||||
|
} |
||||||
|
item.setOriginalTrackNumber(song.getTrackNumber()); |
||||||
|
if (song.getGenre() != null) { |
||||||
|
item.setGenres(new String[]{song.getGenre()}); |
||||||
|
} |
||||||
|
item.setResources(Arrays.asList(createResourceForSong(song))); |
||||||
|
item.setDescription(song.getComment()); |
||||||
|
item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(getAlbumArtUrl(parent.getId()))); |
||||||
|
|
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
public URI getAlbumArtUrl(int id) throws URISyntaxException { |
||||||
|
return jwtSecurityService.addJWTToken(UriComponentsBuilder.fromUriString(getBaseUrl() + "/ext/coverArt.view").queryParam("id", id).queryParam("size", CoverArtScheme.LARGE.getSize())).build().encode().toUri(); |
||||||
|
} |
||||||
|
|
||||||
|
public PlaylistUpnpProcessor getPlaylistProcessor() { |
||||||
|
return playlistProcessor; |
||||||
|
} |
||||||
|
public void setPlaylistProcessor(PlaylistUpnpProcessor playlistProcessor) { |
||||||
|
this.playlistProcessor = playlistProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFileUpnpProcessor getMediaFileProcessor() { |
||||||
|
return mediaFileProcessor; |
||||||
|
} |
||||||
|
public void setMediaFileProcessor(MediaFileUpnpProcessor mediaFileProcessor) { |
||||||
|
this.mediaFileProcessor = mediaFileProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public AlbumUpnpProcessor getAlbumProcessor() { |
||||||
|
return albumProcessor; |
||||||
|
} |
||||||
|
public void setAlbumProcessor(AlbumUpnpProcessor albumProcessor) { |
||||||
|
this.albumProcessor = albumProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public RecentAlbumUpnpProcessor getRecentAlbumProcessor() { |
||||||
|
return recentAlbumProcessor; |
||||||
|
} |
||||||
|
public void setRecentAlbumProcessor(RecentAlbumUpnpProcessor recentAlbumProcessor) { |
||||||
|
this.recentAlbumProcessor = recentAlbumProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public ArtistUpnpProcessor getArtistProcessor() { |
||||||
|
return artistProcessor; |
||||||
|
} |
||||||
|
public void setArtistProcessor(ArtistUpnpProcessor artistProcessor) { |
||||||
|
this.artistProcessor = artistProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public GenreUpnpProcessor getGenreProcessor() { |
||||||
|
return genreProcessor; |
||||||
|
} |
||||||
|
public void setGenreProcessor(GenreUpnpProcessor genreProcessor) { |
||||||
|
this.genreProcessor = genreProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public RootUpnpProcessor getRootProcessor() { |
||||||
|
return rootProcessor; |
||||||
|
} |
||||||
|
public void setRootProcessor(RootUpnpProcessor rootProcessor) { |
||||||
|
this.rootProcessor = rootProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFileService getMediaFileService() { |
||||||
|
return mediaFileService; |
||||||
|
} |
||||||
|
public void setMediaFileService(MediaFileService mediaFileService) { |
||||||
|
this.mediaFileService = mediaFileService; |
||||||
|
} |
||||||
|
|
||||||
|
public SettingsService getSettingsService() { |
||||||
|
return settingsService; |
||||||
|
} |
||||||
|
|
||||||
|
public PlaylistService getPlaylistService() { |
||||||
|
return playlistService; |
||||||
|
} |
||||||
|
public void setPlaylistService(PlaylistService playlistService) { |
||||||
|
this.playlistService = playlistService; |
||||||
|
} |
||||||
|
|
||||||
|
public JWTSecurityService getJwtSecurityService() { |
||||||
|
return jwtSecurityService; |
||||||
|
} |
||||||
|
|
||||||
|
public MusicIndexService getMusicIndexService() { |
||||||
|
return this.musicIndexService; |
||||||
|
} |
||||||
|
public void setMusicIndexService(MusicIndexService musicIndexService) { |
||||||
|
this.musicIndexService = musicIndexService; |
||||||
|
} |
||||||
|
|
||||||
|
public SearchService getSearchService() { |
||||||
|
return this.searchService; |
||||||
|
} |
||||||
|
public void setSearchService(SearchService searchService) { |
||||||
|
this.searchService = searchService; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
import org.fourthline.cling.support.model.BrowseResult; |
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.SortCriterion; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.GenreContainer; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.util.Util; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class GenreUpnpProcessor extends UpnpContentProcessor <Genre, MediaFile> { |
||||||
|
|
||||||
|
public GenreUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_GENRE_PREFIX); |
||||||
|
setRootTitle("Genres"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Browses the top-level content of a type. |
||||||
|
*/ |
||||||
|
public BrowseResult browseRoot(String filter, long firstResult, long maxResults, SortCriterion[] orderBy) throws Exception { |
||||||
|
// we have to override this to do an index-based id.
|
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
List<Genre> allItems = getAllItems(); |
||||||
|
if (filter != null) { |
||||||
|
// filter items
|
||||||
|
} |
||||||
|
if (orderBy != null) { |
||||||
|
// sort items
|
||||||
|
} |
||||||
|
List<Genre> selectedItems = Util.subList(allItems, firstResult, maxResults); |
||||||
|
for (int i=0; i < selectedItems.size(); i++) { |
||||||
|
Genre item = selectedItems.get(i); |
||||||
|
didl.addContainer(createContainer(item, (int) (i+firstResult))); |
||||||
|
} |
||||||
|
return createBrowseResult(didl, (int) didl.getCount(), allItems.size()); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Genre item) { |
||||||
|
// genre uses index because we don't have a proper id
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Genre item, int index) { |
||||||
|
GenreContainer container = new GenreContainer(); |
||||||
|
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + index); |
||||||
|
container.setParentID(getRootId()); |
||||||
|
container.setTitle(item.getName()); |
||||||
|
container.setChildCount(item.getAlbumCount()); |
||||||
|
|
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Genre> getAllItems() { |
||||||
|
return getDispatcher().getMediaFileService().getGenres(false); |
||||||
|
} |
||||||
|
|
||||||
|
public Genre getItemById(String id) { |
||||||
|
int index = Integer.parseInt(id); |
||||||
|
List<Genre> allGenres = getAllItems(); |
||||||
|
if (allGenres.size() > index) { |
||||||
|
return allGenres.get(index); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public List<MediaFile> getChildren(Genre item) { |
||||||
|
List<MusicFolder> allFolders = getDispatcher().getSettingsService().getAllMusicFolders(); |
||||||
|
return getDispatcher().getMediaFileProcessor().getMediaFileDao().getSongsByGenre(item.getName(), 0, Integer.MAX_VALUE, allFolders); |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, MediaFile child) throws Exception { |
||||||
|
didl.addItem(getDispatcher().getMediaFileProcessor().createItem(child)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,165 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
|
||||||
|
import org.fourthline.cling.support.model.BrowseResult; |
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.DIDLObject; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.MusicAlbum; |
||||||
|
import org.fourthline.cling.support.model.item.Item; |
||||||
|
import org.fourthline.cling.support.model.item.MusicTrack; |
||||||
|
import org.libresonic.player.dao.MediaFileDao; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.service.MediaFileService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class MediaFileUpnpProcessor extends UpnpContentProcessor <MediaFile, MediaFile> { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
MediaFileDao mediaFileDao; |
||||||
|
|
||||||
|
public MediaFileUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX); |
||||||
|
setRootTitle("Folders"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
// overriding for the case of browsing a file
|
||||||
|
public BrowseResult browseObjectMetadata(String id) throws Exception { |
||||||
|
MediaFile item = getItemById(id); |
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
addChild(didl, item); |
||||||
|
return createBrowseResult(didl, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(MediaFile item) throws Exception { |
||||||
|
MusicAlbum container = new MusicAlbum(); |
||||||
|
if (item.isAlbum()) { |
||||||
|
container.setAlbumArtURIs(new URI[] { getDispatcher().getAlbumProcessor().getAlbumArtURI(item.getId())}); |
||||||
|
|
||||||
|
if (item.getArtist() != null) { |
||||||
|
container.setArtists(getDispatcher().getAlbumProcessor().getAlbumArtists(item.getArtist())); |
||||||
|
} |
||||||
|
container.setDescription(item.getComment()); |
||||||
|
} |
||||||
|
container.setId(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX + DispatchingContentDirectory.SEPARATOR + item.getId()); |
||||||
|
container.setTitle(item.getName()); |
||||||
|
List<MediaFile> children = getChildren(item); |
||||||
|
container.setChildCount(children.size()); |
||||||
|
|
||||||
|
if (! getMediaFileService().isRoot(item)) { |
||||||
|
MediaFile parent = getMediaFileService().getParentOf(item); |
||||||
|
if (parent != null) { |
||||||
|
container.setParentID(String.valueOf(parent.getId())); |
||||||
|
} |
||||||
|
} else { |
||||||
|
container.setParentID(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX); |
||||||
|
} |
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
public List<MediaFile> getAllItems() throws Exception { |
||||||
|
List<MusicFolder> allFolders = getDispatcher().getSettingsService().getAllMusicFolders(); |
||||||
|
List<MediaFile> returnValue = new ArrayList<MediaFile>(); |
||||||
|
if (allFolders.size() == 1) { |
||||||
|
// if there's only one root folder just return it
|
||||||
|
return getChildren(getMediaFileService().getMediaFile(allFolders.get(0).getPath())); |
||||||
|
} else { |
||||||
|
for (MusicFolder folder : allFolders) { |
||||||
|
returnValue.add(getMediaFileService().getMediaFile(folder.getPath())); |
||||||
|
} |
||||||
|
} |
||||||
|
return returnValue; |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFile getItemById(String id) throws Exception { |
||||||
|
return getMediaFileService().getMediaFile(Integer.parseInt(id)); |
||||||
|
} |
||||||
|
|
||||||
|
public List<MediaFile> getChildren(MediaFile item) { |
||||||
|
List<MediaFile> children = getMediaFileService().getChildrenOf(item, true, true, true); |
||||||
|
children.sort((MediaFile o1, MediaFile o2)->o1.getPath().replaceAll("\\W", "").compareToIgnoreCase(o2.getPath().replaceAll("\\W", ""))); |
||||||
|
return children; |
||||||
|
} |
||||||
|
|
||||||
|
public void addItem(DIDLContent didl, MediaFile item) throws Exception { |
||||||
|
if (item.isFile()) { |
||||||
|
didl.addItem(createItem(item)); |
||||||
|
} else { |
||||||
|
didl.addContainer(createContainer(item)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, MediaFile child) throws Exception { |
||||||
|
if (child.isFile()) { |
||||||
|
didl.addItem(createItem(child)); |
||||||
|
} else { |
||||||
|
didl.addContainer(createContainer(child)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Item createItem(MediaFile song) throws Exception { |
||||||
|
MediaFile parent = getMediaFileService().getParentOf(song); |
||||||
|
MusicTrack item = new MusicTrack(); |
||||||
|
item.setId(String.valueOf(song.getId())); |
||||||
|
item.setParentID(String.valueOf(parent.getId())); |
||||||
|
item.setTitle(song.getTitle()); |
||||||
|
item.setAlbum(song.getAlbumName()); |
||||||
|
if (song.getArtist() != null) { |
||||||
|
item.setArtists(getDispatcher().getAlbumProcessor().getAlbumArtists(song.getArtist())); |
||||||
|
} |
||||||
|
Integer year = song.getYear(); |
||||||
|
if (year != null) { |
||||||
|
item.setDate(year + "-01-01"); |
||||||
|
} |
||||||
|
item.setOriginalTrackNumber(song.getTrackNumber()); |
||||||
|
if (song.getGenre() != null) { |
||||||
|
item.setGenres(new String[]{song.getGenre()}); |
||||||
|
} |
||||||
|
item.setResources(Arrays.asList(getDispatcher().createResourceForSong(song))); |
||||||
|
item.setDescription(song.getComment()); |
||||||
|
item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(getDispatcher().getAlbumProcessor().getAlbumArtURI(parent.getId()))); |
||||||
|
|
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFileService getMediaFileService() { |
||||||
|
return getDispatchingContentDirectory().getMediaFileService(); |
||||||
|
} |
||||||
|
|
||||||
|
public MediaFileDao getMediaFileDao() { |
||||||
|
return mediaFileDao; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMediaFileDao(MediaFileDao mediaFileDao) { |
||||||
|
this.mediaFileDao = mediaFileDao; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
|
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.PlaylistContainer; |
||||||
|
import org.libresonic.player.domain.*; |
||||||
|
import org.libresonic.player.service.PlaylistService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class PlaylistUpnpProcessor extends UpnpContentProcessor <Playlist, MediaFile> { |
||||||
|
@Autowired |
||||||
|
private PlaylistService playlistService; |
||||||
|
|
||||||
|
public PlaylistUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_PLAYLIST_PREFIX); |
||||||
|
setRootTitle("Playlists"); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Playlist item) { |
||||||
|
PlaylistContainer container = new PlaylistContainer(); |
||||||
|
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + item.getId()); |
||||||
|
container.setParentID(getRootId()); |
||||||
|
container.setTitle(item.getName()); |
||||||
|
container.setDescription(item.getComment()); |
||||||
|
container.setChildCount(getPlaylistService().getFilesInPlaylist(item.getId()).size()); |
||||||
|
|
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Playlist> getAllItems() { |
||||||
|
List<Playlist> playlists = getPlaylistService().getAllPlaylists(); |
||||||
|
return playlists; |
||||||
|
} |
||||||
|
|
||||||
|
public Playlist getItemById(String id) throws Exception { |
||||||
|
return getDispatcher().getPlaylistService().getPlaylist(Integer.parseInt(id)); |
||||||
|
} |
||||||
|
|
||||||
|
public List<MediaFile> getChildren(Playlist item) { |
||||||
|
return getPlaylistService().getFilesInPlaylist(item.getId()); |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, MediaFile child) throws Exception { |
||||||
|
didl.addItem(getDispatchingContentDirectory().createItem(child)); |
||||||
|
} |
||||||
|
|
||||||
|
public PlaylistService getPlaylistService() { |
||||||
|
return this.playlistService; |
||||||
|
} |
||||||
|
public void setPlaylistService(PlaylistService playlistService) { |
||||||
|
this.playlistService = playlistService; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
import org.libresonic.player.domain.Album; |
||||||
|
import org.libresonic.player.domain.MusicFolder; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public class RecentAlbumUpnpProcessor extends AlbumUpnpProcessor { |
||||||
|
private final static int RECENT_COUNT = 50; |
||||||
|
|
||||||
|
public RecentAlbumUpnpProcessor() { |
||||||
|
setRootId(DispatchingContentDirectory.CONTAINER_ID_RECENT_PREFIX); |
||||||
|
setRootTitle("RecentAlbums"); |
||||||
|
} |
||||||
|
|
||||||
|
public List<Album> getAllItems() { |
||||||
|
List<MusicFolder> allFolders = getDispatchingContentDirectory().getSettingsService().getAllMusicFolders(); |
||||||
|
List<Album> recentAlbums = getAlbumDao().getNewestAlbums(0, RECENT_COUNT, allFolders); |
||||||
|
if (recentAlbums.size() > 1) { |
||||||
|
Album viewAll = new Album(); |
||||||
|
viewAll.setName("- All Albums -"); |
||||||
|
viewAll.setId(-1); |
||||||
|
viewAll.setComment(AlbumUpnpProcessor.ALL_RECENT); |
||||||
|
recentAlbums.add(0, viewAll); |
||||||
|
} |
||||||
|
return recentAlbums; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.WriteStatus; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.StorageFolder; |
||||||
|
import org.libresonic.player.domain.MediaLibraryStatistics; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class RootUpnpProcessor extends UpnpContentProcessor <Container, Container> { |
||||||
|
public Container createRootContainer() { |
||||||
|
StorageFolder root = new StorageFolder(); |
||||||
|
root.setId(DispatchingContentDirectory.CONTAINER_ID_ROOT); |
||||||
|
root.setParentID("-1"); |
||||||
|
|
||||||
|
MediaLibraryStatistics statistics = getDispatchingContentDirectory().getSettingsService().getMediaLibraryStatistics(); |
||||||
|
// returning large storageUsed values doesn't play nicely with
|
||||||
|
// some upnp clients
|
||||||
|
//root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
||||||
|
root.setStorageUsed(-1L); |
||||||
|
root.setTitle("Libresonic Media"); |
||||||
|
root.setRestricted(true); |
||||||
|
root.setSearchable(true); |
||||||
|
root.setWriteStatus(WriteStatus.NOT_WRITABLE); |
||||||
|
|
||||||
|
root.setChildCount(6); |
||||||
|
return root; |
||||||
|
} |
||||||
|
|
||||||
|
public Container createContainer(Container item) { |
||||||
|
// the items are the containers in this case.
|
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Container> getAllItems() throws Exception { |
||||||
|
ArrayList<Container> allItems = new ArrayList<Container>(); |
||||||
|
allItems.add(getDispatchingContentDirectory().getAlbumProcessor().createRootContainer()); |
||||||
|
allItems.add(getDispatchingContentDirectory().getArtistProcessor().createRootContainer()); |
||||||
|
allItems.add(getDispatchingContentDirectory().getMediaFileProcessor().createRootContainer()); |
||||||
|
allItems.add(getDispatchingContentDirectory().getGenreProcessor().createRootContainer()); |
||||||
|
allItems.add(getDispatchingContentDirectory().getPlaylistProcessor().createRootContainer()); |
||||||
|
allItems.add(getDispatchingContentDirectory().getRecentAlbumProcessor().createRootContainer()); |
||||||
|
return allItems; |
||||||
|
} |
||||||
|
|
||||||
|
public Container getItemById(String id) { |
||||||
|
return createRootContainer(); |
||||||
|
} |
||||||
|
|
||||||
|
public List<Container> getChildren(Container item) throws Exception { |
||||||
|
return getAllItems(); |
||||||
|
} |
||||||
|
|
||||||
|
public void addChild(DIDLContent didl, Container child) { |
||||||
|
// special case; root doesn't have object instances
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,188 @@ |
|||||||
|
/* |
||||||
|
This file is part of Libresonic. |
||||||
|
|
||||||
|
Libresonic 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. |
||||||
|
|
||||||
|
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2017 (C) Libresonic Authors |
||||||
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||||
|
*/ |
||||||
|
package org.libresonic.player.service.upnp; |
||||||
|
|
||||||
|
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException; |
||||||
|
import org.fourthline.cling.support.contentdirectory.DIDLParser; |
||||||
|
import org.fourthline.cling.support.model.BrowseResult; |
||||||
|
import org.fourthline.cling.support.model.DIDLContent; |
||||||
|
import org.fourthline.cling.support.model.SortCriterion; |
||||||
|
import org.fourthline.cling.support.model.container.Container; |
||||||
|
import org.fourthline.cling.support.model.container.StorageFolder; |
||||||
|
import org.libresonic.player.domain.MusicFolder; |
||||||
|
import org.libresonic.player.domain.ParamSearchResult; |
||||||
|
import org.libresonic.player.util.Util; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Allen Petersen |
||||||
|
* @version $Id$ |
||||||
|
*/ |
||||||
|
public abstract class UpnpContentProcessor<T extends Object, U extends Object> { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private DispatchingContentDirectory dispatchingContentDirectory; |
||||||
|
|
||||||
|
protected String rootTitle; |
||||||
|
protected String rootId; |
||||||
|
|
||||||
|
/** |
||||||
|
* Browses the root metadata for a type. |
||||||
|
*/ |
||||||
|
public BrowseResult browseRootMetadata() throws Exception { |
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
didl.addContainer(createRootContainer()); |
||||||
|
return createBrowseResult(didl, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
public Container createRootContainer() throws Exception { |
||||||
|
Container container = new StorageFolder(); |
||||||
|
container.setId(getRootId()); |
||||||
|
container.setTitle(getRootTitle()); |
||||||
|
|
||||||
|
int childCount = getAllItemsSize(); |
||||||
|
container.setChildCount(childCount); |
||||||
|
container.setParentID(DispatchingContentDirectory.CONTAINER_ID_ROOT); |
||||||
|
return container; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Browses the top-level content of a type. |
||||||
|
*/ |
||||||
|
public BrowseResult browseRoot(String filter, long firstResult, long maxResults, SortCriterion[] orderBy) throws Exception { |
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
List<T> allItems = getAllItems(); |
||||||
|
if (filter != null) { |
||||||
|
// filter items (not implemented yet)
|
||||||
|
} |
||||||
|
if (orderBy != null) { |
||||||
|
// sort items (not implemented yet)
|
||||||
|
} |
||||||
|
List<T> selectedItems = Util.subList(allItems, firstResult, maxResults); |
||||||
|
for (T item : selectedItems) { |
||||||
|
addItem(didl, item); |
||||||
|
} |
||||||
|
|
||||||
|
return createBrowseResult(didl, (int) didl.getCount(), allItems.size()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Browses metadata for a child. |
||||||
|
*/ |
||||||
|
public BrowseResult browseObjectMetadata(String id) throws Exception { |
||||||
|
T item = getItemById(id); |
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
addItem(didl, item); |
||||||
|
return createBrowseResult(didl, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Browses a child of the container. |
||||||
|
*/ |
||||||
|
public BrowseResult browseObject(String id, String filter, long firstResult, long maxResults, SortCriterion[] orderBy) throws Exception { |
||||||
|
T item = getItemById(id); |
||||||
|
List<U> allChildren = getChildren(item); |
||||||
|
if (filter != null) { |
||||||
|
// filter items (not implemented yet)
|
||||||
|
} |
||||||
|
if (orderBy != null) { |
||||||
|
// sort items (not implemented yet)
|
||||||
|
} |
||||||
|
List<U> selectedChildren = Util.subList(allChildren, firstResult, maxResults); |
||||||
|
|
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
for (U child : selectedChildren) { |
||||||
|
addChild(didl, child); |
||||||
|
} |
||||||
|
return createBrowseResult(didl, selectedChildren.size(), allChildren.size()); |
||||||
|
} |
||||||
|
|
||||||
|
protected BrowseResult createBrowseResult(DIDLContent didl, int count, int totalMatches) throws Exception { |
||||||
|
return new BrowseResult(new DIDLParser().generate(didl), count, totalMatches); |
||||||
|
} |
||||||
|
|
||||||
|
public BrowseResult searchByName(String name, |
||||||
|
long firstResult, long maxResults, |
||||||
|
SortCriterion[] orderBy) |
||||||
|
throws ContentDirectoryException { |
||||||
|
DIDLContent didl = new DIDLContent(); |
||||||
|
|
||||||
|
Class clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; |
||||||
|
|
||||||
|
try { |
||||||
|
List<MusicFolder> allFolders = getDispatchingContentDirectory().getSettingsService().getAllMusicFolders(); |
||||||
|
ParamSearchResult<T> result = getDispatcher().getSearchService().searchByName(name, (int) firstResult, (int) maxResults, allFolders, clazz); |
||||||
|
List<T> selectedItems = result.getItems(); |
||||||
|
for (T item : selectedItems) { |
||||||
|
addItem(didl, item); |
||||||
|
} |
||||||
|
|
||||||
|
return createBrowseResult(didl, (int) didl.getCount(), result.getTotalHits()); |
||||||
|
} catch (Exception e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public DispatchingContentDirectory getDispatchingContentDirectory() { |
||||||
|
return dispatchingContentDirectory; |
||||||
|
} |
||||||
|
public void setDispatchingContentDirectory(DispatchingContentDirectory dispatchingContentDirectory) { |
||||||
|
this.dispatchingContentDirectory = dispatchingContentDirectory; |
||||||
|
} |
||||||
|
public DispatchingContentDirectory getDispatcher() { |
||||||
|
return getDispatchingContentDirectory(); |
||||||
|
} |
||||||
|
|
||||||
|
public void addItem(DIDLContent didl, T item) throws Exception { |
||||||
|
didl.addContainer(createContainer(item)); |
||||||
|
} |
||||||
|
|
||||||
|
// this can probably be optimized in some cases
|
||||||
|
public int getAllItemsSize() throws Exception { |
||||||
|
return getAllItems().size(); |
||||||
|
} |
||||||
|
|
||||||
|
public abstract Container createContainer(T item) throws Exception; |
||||||
|
|
||||||
|
public abstract List<T> getAllItems() throws Exception; |
||||||
|
|
||||||
|
public abstract T getItemById(String id) throws Exception; |
||||||
|
|
||||||
|
public abstract List<U> getChildren(T item) throws Exception; |
||||||
|
|
||||||
|
public abstract void addChild(DIDLContent didl, U child) throws Exception; |
||||||
|
|
||||||
|
public String getRootTitle() { |
||||||
|
return rootTitle; |
||||||
|
} |
||||||
|
public void setRootTitle(String rootTitle) { |
||||||
|
this.rootTitle = rootTitle; |
||||||
|
} |
||||||
|
public String getRootId() { |
||||||
|
return rootId; |
||||||
|
} |
||||||
|
public void setRootId(String rootId) { |
||||||
|
this.rootId = rootId; |
||||||
|
} |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue