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
Allen Petersen 7 years ago
parent bcd330e74d
commit b3ae8faac6
  1. 59
      libresonic-main/src/main/java/org/libresonic/player/domain/ParamSearchResult.java
  2. 65
      libresonic-main/src/main/java/org/libresonic/player/service/SearchService.java
  3. 16
      libresonic-main/src/main/java/org/libresonic/player/service/UPnPService.java
  4. 136
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/AlbumUpnpProcessor.java
  5. 99
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/ArtistUpnpProcessor.java
  6. 292
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/DispatchingContentDirectory.java
  7. 99
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/GenreUpnpProcessor.java
  8. 165
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/MediaFileUpnpProcessor.java
  9. 81
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/PlaylistUpnpProcessor.java
  10. 50
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/RecentAlbumUpnpProcessor.java
  11. 83
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/RootUpnpProcessor.java
  12. 188
      libresonic-main/src/main/java/org/libresonic/player/service/upnp/UpnpContentProcessor.java
  13. 21
      libresonic-main/src/main/resources/applicationContext-service.xml

@ -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;
}
}

@ -32,6 +32,7 @@ import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.spans.SpanOrQuery;
import org.apache.lucene.search.spans.SpanQuery;
@ -372,6 +373,70 @@ public class SearchService {
return result;
}
public <T> ParamSearchResult<T> searchByName(String name, int offset, int count, List<MusicFolder> folderList, Class<T> clazz) {
IndexType indexType = null;
String field = null;
if (clazz.isAssignableFrom(Album.class)) {
indexType = IndexType.ALBUM_ID3;
field = FIELD_ALBUM;
} else if (clazz.isAssignableFrom(Artist.class)) {
indexType = IndexType.ARTIST_ID3;
field = FIELD_ARTIST;
} else if (clazz.isAssignableFrom(MediaFile.class)) {
indexType = IndexType.SONG;
field = FIELD_TITLE;
}
ParamSearchResult<T> result = new ParamSearchResult<T>();
// we only support album, artist, and song for now
if (indexType == null || field == null) {
return result;
}
result.setOffset(offset);
IndexReader reader = null;
try {
reader = createIndexReader(indexType);
Searcher searcher = new IndexSearcher(reader);
Analyzer analyzer = new LibresonicAnalyzer();
QueryParser queryParser = new QueryParser(LUCENE_VERSION, field, analyzer);
Query q = queryParser.parse(name + "*");
Sort sort = new Sort(new SortField(field, SortField.STRING));
TopDocs topDocs = searcher.search(q, null, offset + count, sort);
result.setTotalHits(topDocs.totalHits);
int start = Math.min(offset, topDocs.totalHits);
int end = Math.min(start + count, topDocs.totalHits);
for (int i = start; i < end; i++) {
Document doc = searcher.doc(topDocs.scoreDocs[i].doc);
switch (indexType) {
case SONG:
MediaFile mediaFile = mediaFileService.getMediaFile(Integer.valueOf(doc.get(FIELD_ID)));
addIfNotNull(clazz.cast(mediaFile), result.getItems());
break;
case ARTIST_ID3:
Artist artist = artistDao.getArtist(Integer.valueOf(doc.get(FIELD_ID)));
addIfNotNull(clazz.cast(artist), result.getItems());
break;
case ALBUM_ID3:
Album album = albumDao.getAlbum(Integer.valueOf(doc.get(FIELD_ID)));
addIfNotNull(clazz.cast(album), result.getItems());
break;
default:
break;
}
}
} catch (Throwable x) {
LOG.error("Failed to execute Lucene search.", x);
} finally {
FileUtil.closeQuietly(reader);
}
return result;
}
private <T> void addIfNotNull(T value, List<T> list) {
if (value != null) {
list.add(value);

@ -33,7 +33,7 @@ import org.fourthline.cling.support.model.ProtocolInfos;
import org.fourthline.cling.support.model.dlna.DLNAProfiles;
import org.fourthline.cling.support.model.dlna.DLNAProtocolInfo;
import org.libresonic.player.service.upnp.ApacheUpnpServiceConfiguration;
import org.libresonic.player.service.upnp.FolderBasedContentDirectory;
import org.libresonic.player.service.upnp.LibresonicContentDirectory;
import org.libresonic.player.service.upnp.MSMediaReceiverRegistrarService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,7 +53,7 @@ public class UPnPService {
private SettingsService settingsService;
private UpnpService upnpService;
private FolderBasedContentDirectory folderBasedContentDirectory;
private LibresonicContentDirectory libresonicContentDirectory;
private AtomicReference<Boolean> running = new AtomicReference<>(false);
public void init() {
@ -147,12 +147,12 @@ public class UPnPService {
Icon icon = new Icon("image/png", 512, 512, 32, "logo-512", getClass().getResourceAsStream("logo-512.png"));
LocalService<FolderBasedContentDirectory> contentDirectoryservice = new AnnotationLocalServiceBinder().read(FolderBasedContentDirectory.class);
contentDirectoryservice.setManager(new DefaultServiceManager<FolderBasedContentDirectory>(contentDirectoryservice) {
LocalService<LibresonicContentDirectory> contentDirectoryservice = new AnnotationLocalServiceBinder().read(LibresonicContentDirectory.class);
contentDirectoryservice.setManager(new DefaultServiceManager<LibresonicContentDirectory>(contentDirectoryservice) {
@Override
protected FolderBasedContentDirectory createServiceInstance() throws Exception {
return folderBasedContentDirectory;
protected LibresonicContentDirectory createServiceInstance() throws Exception {
return libresonicContentDirectory;
}
});
@ -205,7 +205,7 @@ public class UPnPService {
this.settingsService = settingsService;
}
public void setFolderBasedContentDirectory(FolderBasedContentDirectory folderBasedContentDirectory) {
this.folderBasedContentDirectory = folderBasedContentDirectory;
public void setLibresonicContentDirectory(LibresonicContentDirectory libresonicContentDirectory) {
this.libresonicContentDirectory = libresonicContentDirectory;
}
}

@ -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;
}
}

@ -194,9 +194,28 @@
<property name="jwtSecurityService" ref="jwtSecurityService" />
</bean>
<bean id="dispatchingContentDirectory" class="org.libresonic.player.service.upnp.DispatchingContentDirectory">
<property name="settingsService" ref="settingsService"/>
<property name="playlistService" ref="playlistService"/>
<property name="playerService" ref="playerService"/>
<property name="transcodingService" ref="transcodingService"/>
<property name="mediaFileService" ref="mediaFileService"/>
<property name="jwtSecurityService" ref="jwtSecurityService" />
<property name="albumProcessor" ref="albumUpnpProcessor" />
<property name="recentAlbumProcessor" ref="recentAlbumUpnpProcessor" />
</bean>
<bean id="mediaFileUpnpProcessor" class="org.libresonic.player.service.upnp.MediaFileUpnpProcessor"/>
<bean id="playlistUpnpProcessor" class="org.libresonic.player.service.upnp.PlaylistUpnpProcessor"/>
<bean id="rootUpnpProcessor" class="org.libresonic.player.service.upnp.RootUpnpProcessor"/>
<bean id="artistUpnpProcessor" class="org.libresonic.player.service.upnp.ArtistUpnpProcessor"/>
<bean id="albumUpnpProcessor" class="org.libresonic.player.service.upnp.AlbumUpnpProcessor"/>
<bean id="recentAlbumUpnpProcessor" class="org.libresonic.player.service.upnp.RecentAlbumUpnpProcessor"/>
<bean id="genreUpnpProcessor" class="org.libresonic.player.service.upnp.GenreUpnpProcessor"/>
<bean id="upnpService" class="org.libresonic.player.service.UPnPService" init-method="init">
<property name="settingsService" ref="settingsService"/>
<property name="folderBasedContentDirectory" ref="folderBasedContentDirectory"/>
<property name="libresonicContentDirectory" ref="dispatchingContentDirectory"/>
</bean>
<bean id="lastFmService" class="org.libresonic.player.service.LastFmService" init-method="init">

Loading…
Cancel
Save