/* This file is part of Airsonic. Airsonic 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. Airsonic 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 Airsonic. If not, see . Copyright 2016 (C) Airsonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ package org.airsonic.player.service.upnp; import org.airsonic.player.domain.*; import org.airsonic.player.service.MediaFileService; import org.airsonic.player.service.PlaylistService; import org.airsonic.player.util.Util; import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode; import org.fourthline.cling.support.contentdirectory.ContentDirectoryException; import org.fourthline.cling.support.model.*; import org.fourthline.cling.support.model.container.Container; import org.fourthline.cling.support.model.container.MusicAlbum; import org.fourthline.cling.support.model.container.PlaylistContainer; import org.fourthline.cling.support.model.container.StorageFolder; import org.fourthline.cling.support.model.item.Item; import org.fourthline.cling.support.model.item.MusicTrack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.util.Arrays; import java.util.List; /** * @author Sindre Mehus * @version $Id$ */ @Service public class FolderBasedContentDirectory extends CustomContentDirectory { private static final Logger LOG = LoggerFactory.getLogger(FolderBasedContentDirectory.class); private static final String CONTAINER_ID_PLAYLIST_ROOT = "playlists"; private static final String CONTAINER_ID_PLAYLIST_PREFIX = "playlist-"; private static final String CONTAINER_ID_FOLDER_PREFIX = "folder-"; @Autowired private MediaFileService mediaFileService; @Autowired private PlaylistService playlistService; @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); // maxResult == 0 means all. if (maxResults == 0) { maxResults = Integer.MAX_VALUE; } try { if (CONTAINER_ID_ROOT.equals(objectId)) { return browseFlag == BrowseFlag.METADATA ? browseRootMetadata() : browseRoot(firstResult, maxResults); } if (CONTAINER_ID_PLAYLIST_ROOT.equals(objectId)) { return browseFlag == BrowseFlag.METADATA ? browsePlaylistRootMetadata() : browsePlaylistRoot(firstResult, maxResults); } if (objectId.startsWith(CONTAINER_ID_PLAYLIST_PREFIX)) { int playlistId = Integer.parseInt(objectId.replace(CONTAINER_ID_PLAYLIST_PREFIX, "")); Playlist playlist = playlistService.getPlaylist(playlistId); return browseFlag == BrowseFlag.METADATA ? browsePlaylistMetadata(playlist) : browsePlaylist(playlist, firstResult, maxResults); } int mediaFileId = Integer.parseInt(objectId.replace(CONTAINER_ID_FOLDER_PREFIX, "")); MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId); return browseFlag == BrowseFlag.METADATA ? browseMediaFileMetadata(mediaFile) : browseMediaFile(mediaFile, firstResult, maxResults); } catch (Throwable x) { LOG.error("UPnP error: " + x, x); throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS, x.toString()); } } private BrowseResult browseRootMetadata() throws Exception { StorageFolder root = new StorageFolder(); root.setId(CONTAINER_ID_ROOT); root.setParentID("-1"); MediaLibraryStatistics statistics = settingsService.getMediaLibraryStatistics(); root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes()); root.setTitle("Airsonic Media"); root.setRestricted(true); root.setSearchable(false); root.setWriteStatus(WriteStatus.NOT_WRITABLE); List musicFolders = settingsService.getAllMusicFolders(); root.setChildCount(musicFolders.size() + 1); // +1 for playlists DIDLContent didl = new DIDLContent(); didl.addContainer(root); return createBrowseResult(didl, 1, 1); } private BrowseResult browsePlaylistRootMetadata() throws Exception { DIDLContent didl = new DIDLContent(); didl.addContainer(createPlaylistRootContainer()); return createBrowseResult(didl, 1, 1); } private BrowseResult browsePlaylistRoot(long firstResult, long maxResults) throws Exception { DIDLContent didl = new DIDLContent(); List allPlaylists = playlistService.getAllPlaylists(); List selectedPlaylists = Util.subList(allPlaylists, firstResult, maxResults); for (Playlist playlist : selectedPlaylists) { didl.addContainer(createPlaylistContainer(playlist)); } return createBrowseResult(didl, selectedPlaylists.size(), allPlaylists.size()); } private BrowseResult browsePlaylistMetadata(Playlist playlist) throws Exception { DIDLContent didl = new DIDLContent(); didl.addContainer(createPlaylistContainer(playlist)); return createBrowseResult(didl, 1, 1); } private BrowseResult browsePlaylist(Playlist playlist, long firstResult, long maxResults) throws Exception { List allChildren = playlistService.getFilesInPlaylist(playlist.getId()); List selectedChildren = Util.subList(allChildren, firstResult, maxResults); DIDLContent didl = new DIDLContent(); for (MediaFile child : selectedChildren) { addContainerOrItem(didl, child); } return createBrowseResult(didl, selectedChildren.size(), allChildren.size()); } private BrowseResult browseRoot(long firstResult, long maxResults) throws Exception { DIDLContent didl = new DIDLContent(); List allFolders = settingsService.getAllMusicFolders(); List selectedFolders = Util.subList(allFolders, firstResult, maxResults); for (MusicFolder folder : selectedFolders) { MediaFile mediaFile = mediaFileService.getMediaFile(folder.getPath()); addContainerOrItem(didl, mediaFile); } if (maxResults > selectedFolders.size()) { didl.addContainer(createPlaylistRootContainer()); } return createBrowseResult(didl, (int) didl.getCount(), allFolders.size() + 1); } private BrowseResult browseMediaFileMetadata(MediaFile mediaFile) throws Exception { DIDLContent didl = new DIDLContent(); didl.addContainer(createContainer(mediaFile)); return createBrowseResult(didl, 1, 1); } private BrowseResult browseMediaFile(MediaFile mediaFile, long firstResult, long maxResults) throws Exception { List allChildren = mediaFileService.getChildrenOf(mediaFile, true, true, true); List selectedChildren = Util.subList(allChildren, firstResult, maxResults); DIDLContent didl = new DIDLContent(); for (MediaFile child : selectedChildren) { addContainerOrItem(didl, child); } return createBrowseResult(didl, selectedChildren.size(), allChildren.size()); } private void addContainerOrItem(DIDLContent didl, MediaFile mediaFile) { if (mediaFile.isFile()) { didl.addItem(createItem(mediaFile)); } else { didl.addContainer(createContainer(mediaFile)); } } private Item createItem(MediaFile song) { 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))); return item; } private Container createContainer(MediaFile mediaFile) { Container container = mediaFile.isAlbum() ? createAlbumContainer(mediaFile) : new MusicAlbum(); container.setId(CONTAINER_ID_FOLDER_PREFIX + mediaFile.getId()); container.setTitle(mediaFile.getName()); List children = mediaFileService.getChildrenOf(mediaFile, true, true, false); container.setChildCount(children.size()); container.setParentID(CONTAINER_ID_ROOT); if (!mediaFileService.isRoot(mediaFile)) { MediaFile parent = mediaFileService.getParentOf(mediaFile); if (parent != null) { container.setParentID(String.valueOf(parent.getId())); } } return container; } private Container createAlbumContainer(MediaFile album) { MusicAlbum container = new MusicAlbum(); container.setAlbumArtURIs(new URI[]{getAlbumArtUrl(album)}); // TODO: correct artist? if (album.getArtist() != null) { container.setArtists(new PersonWithRole[]{new PersonWithRole(album.getArtist())}); } container.setDescription(album.getComment()); return container; } private Container createPlaylistRootContainer() { Container container = new StorageFolder(); container.setId(CONTAINER_ID_PLAYLIST_ROOT); container.setTitle("Playlists"); List playlists = playlistService.getAllPlaylists(); container.setChildCount(playlists.size()); container.setParentID(CONTAINER_ID_ROOT); return container; } private Container createPlaylistContainer(Playlist playlist) { PlaylistContainer container = new PlaylistContainer(); container.setId(CONTAINER_ID_PLAYLIST_PREFIX + playlist.getId()); container.setParentID(CONTAINER_ID_PLAYLIST_ROOT); container.setTitle(playlist.getName()); container.setDescription(playlist.getComment()); container.setChildCount(playlistService.getFilesInPlaylist(playlist.getId()).size()); return container; } private URI getAlbumArtUrl(MediaFile album) { return jwtSecurityService.addJWTToken(UriComponentsBuilder.fromUriString(getBaseUrl() + "/ext/coverArt.view") .queryParam("id", album.getId()) .queryParam("size", CoverArtScheme.LARGE.getSize())) .build() .encode() .toUri(); } public void setMediaFileService(MediaFileService mediaFileService) { this.mediaFileService = mediaFileService; } public void setPlaylistService(PlaylistService playlistService) { this.playlistService = playlistService; } }