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.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+136
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -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();
|
||||
}
|
||||
}
|
||||
+292
@@ -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;
|
||||
}
|
||||
}
|
||||
+99
@@ -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));
|
||||
}
|
||||
}
|
||||
+165
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
}
|
||||
+83
@@ -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
|
||||
}
|
||||
}
|
||||
+188
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user