File System Renames (No content changes)

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
Andrew DeMaria
2017-07-13 21:34:19 -06:00
parent d8bd94b7ad
commit 260e04c8ea
1378 changed files with 0 additions and 0 deletions
@@ -0,0 +1,53 @@
/*
* 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 2014 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.domain.ArtistBio;
import java.util.List;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class ArtistInfo {
private final List<SimilarArtist> similarArtists;
private final ArtistBio artistBio;
private final List<TopSong> topSongs;
public ArtistInfo(List<SimilarArtist> similarArtists, ArtistBio artistBio, List<TopSong> topSongs) {
this.similarArtists = similarArtists;
this.artistBio = artistBio;
this.topSongs = topSongs;
}
public List<SimilarArtist> getSimilarArtists() {
return similarArtists;
}
public ArtistBio getArtistBio() {
return artistBio;
}
public List<TopSong> getTopSongs() {
return topSongs;
}
}
@@ -0,0 +1,44 @@
/*
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.ajax;
/**
* Contains info about cover art images for an album.
*
* @author Sindre Mehus
*/
public class CoverArtInfo {
private final String imagePreviewUrl;
private final String imageDownloadUrl;
public CoverArtInfo(String imagePreviewUrl, String imageDownloadUrl) {
this.imagePreviewUrl = imagePreviewUrl;
this.imageDownloadUrl = imageDownloadUrl;
}
public String getImagePreviewUrl() {
return imagePreviewUrl;
}
public String getImageDownloadUrl() {
return imageDownloadUrl;
}
}
@@ -0,0 +1,177 @@
/*
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.ajax;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.libresonic.player.domain.LastFmCoverArt;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
* Provides AJAX-enabled services for changing cover art images.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class CoverArtService {
private static final Logger LOG = LoggerFactory.getLogger(CoverArtService.class);
private SecurityService securityService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
public List<LastFmCoverArt> searchCoverArt(String artist, String album) {
return lastFmService.searchCoverArt(artist, album);
}
/**
* Downloads and saves the cover art at the given URL.
*
* @param albumId ID of the album in question.
* @param url The image URL.
* @return The error string if something goes wrong, <code>null</code> otherwise.
*/
public String setCoverArtImage(int albumId, String url) {
try {
MediaFile mediaFile = mediaFileService.getMediaFile(albumId);
saveCoverArt(mediaFile.getPath(), url);
return null;
} catch (Exception x) {
LOG.warn("Failed to save cover art for album " + albumId, x);
return x.toString();
}
}
private void saveCoverArt(String path, String url) throws Exception {
InputStream input = null;
OutputStream output = null;
try (CloseableHttpClient client = HttpClients.createDefault()) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(20 * 1000) // 20 seconds
.setSocketTimeout(20 * 1000) // 20 seconds
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
try (CloseableHttpResponse response = client.execute(method)) {
input = response.getEntity().getContent();
// Attempt to resolve proper suffix.
String suffix = "jpg";
if (url.toLowerCase().endsWith(".gif")) {
suffix = "gif";
} else if (url.toLowerCase().endsWith(".png")) {
suffix = "png";
}
// Check permissions.
File newCoverFile = new File(path, "cover." + suffix);
if (!securityService.isWriteAllowed(newCoverFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath()));
}
// If file exists, create a backup.
backup(newCoverFile, new File(path, "cover." + suffix + ".backup"));
// Write file.
output = new FileOutputStream(newCoverFile);
IOUtils.copy(input, output);
MediaFile dir = mediaFileService.getMediaFile(path);
// Refresh database.
mediaFileService.refreshMediaFile(dir);
dir = mediaFileService.getMediaFile(dir.getId());
// Rename existing cover files if new cover file is not the preferred.
try {
while (true) {
File coverFile = mediaFileService.getCoverArt(dir);
if (coverFile != null && !isMediaFile(coverFile) && !newCoverFile.equals(coverFile)) {
if (!coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old"))) {
LOG.warn("Unable to rename old image file " + coverFile);
break;
}
LOG.info("Renamed old image file " + coverFile);
// Must refresh again.
mediaFileService.refreshMediaFile(dir);
dir = mediaFileService.getMediaFile(dir.getId());
} else {
break;
}
}
} catch (Exception x) {
LOG.warn("Failed to rename existing cover file.", x);
}
}
} finally {
IOUtils.closeQuietly(input);
IOUtils.closeQuietly(output);
}
}
private boolean isMediaFile(File file) {
return !mediaFileService.filterMediaFiles(new File[]{file}).isEmpty();
}
private void backup(File newCoverFile, File backup) {
if (newCoverFile.exists()) {
if (backup.exists()) {
backup.delete();
}
if (newCoverFile.renameTo(backup)) {
LOG.info("Backed up old image file to " + backup);
} else {
LOG.warn("Failed to create image file backup " + backup);
}
}
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
}
@@ -0,0 +1,63 @@
/*
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.ajax;
/**
* Contains lyrics info for a song.
*
* @author Sindre Mehus
*/
public class LyricsInfo {
private final String lyrics;
private final String artist;
private final String title;
private boolean tryLater;
public LyricsInfo() {
this(null, null, null);
}
public LyricsInfo(String lyrics, String artist, String title) {
this.lyrics = lyrics;
this.artist = artist;
this.title = title;
}
public String getLyrics() {
return lyrics;
}
public String getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public void setTryLater(boolean tryLater) {
this.tryLater = tryLater;
}
public boolean isTryLater() {
return tryLater;
}
}
@@ -0,0 +1,106 @@
/*
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.ajax;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringReader;
import java.net.SocketException;
/**
* Provides AJAX-enabled services for retrieving song lyrics from chartlyrics.com.
* <p/>
* See http://www.chartlyrics.com/api.aspx for details.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class LyricsService {
private static final Logger LOG = LoggerFactory.getLogger(LyricsService.class);
/**
* Returns lyrics for the given song and artist.
*
* @param artist The artist.
* @param song The song.
* @return The lyrics, never <code>null</code> .
*/
public LyricsInfo getLyrics(String artist, String song) {
LyricsInfo lyrics = new LyricsInfo();
try {
artist = StringUtil.urlEncode(artist);
song = StringUtil.urlEncode(song);
String url = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect?artist=" + artist + "&song=" + song;
String xml = executeGetRequest(url);
lyrics = parseSearchResult(xml);
} catch (SocketException x) {
lyrics.setTryLater(true);
} catch (Exception x) {
LOG.warn("Failed to get lyrics for song '" + song + "'.", x);
}
return lyrics;
}
private LyricsInfo parseSearchResult(String xml) throws Exception {
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(new StringReader(xml));
Element root = document.getRootElement();
Namespace ns = root.getNamespace();
String lyric = StringUtils.trimToNull(root.getChildText("Lyric", ns));
String song = root.getChildText("LyricSong", ns);
String artist = root.getChildText("LyricArtist", ns);
return new LyricsInfo(lyric, artist, song);
}
private String executeGetRequest(String url) throws IOException {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000)
.setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
try (CloseableHttpClient client = HttpClients.createDefault()) {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(method, responseHandler);
}
}
}
@@ -0,0 +1,120 @@
/*
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.ajax;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.domain.ArtistBio;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* Provides miscellaneous AJAX-enabled services.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class MultiService {
private static final Logger LOG = LoggerFactory.getLogger(MultiService.class);
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SettingsService settingsService;
public ArtistInfo getArtistInfo(int mediaFileId, int maxSimilarArtists, int maxTopSongs) {
MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
List<SimilarArtist> similarArtists = getSimilarArtists(mediaFileId, maxSimilarArtists);
ArtistBio artistBio = lastFmService.getArtistBio(mediaFile);
List<TopSong> topSongs = getTopSongs(mediaFile, maxTopSongs);
return new ArtistInfo(similarArtists, artistBio, topSongs);
}
private List<TopSong> getTopSongs(MediaFile mediaFile, int limit) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<TopSong> result = new ArrayList<TopSong>();
List<MediaFile> files = lastFmService.getTopSongs(mediaFile, limit, musicFolders);
mediaFileService.populateStarredDate(files, username);
for (MediaFile file : files) {
result.add(new TopSong(file.getId(), file.getTitle(), file.getArtist(), file.getAlbumName(),
file.getDurationString(), file.getStarredDate() != null));
}
return result;
}
private List<SimilarArtist> getSimilarArtists(int mediaFileId, int limit) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
MediaFile artist = mediaFileService.getMediaFile(mediaFileId);
List<MediaFile> similarArtists = lastFmService.getSimilarArtists(artist, limit, false, musicFolders);
SimilarArtist[] result = new SimilarArtist[similarArtists.size()];
for (int i = 0; i < result.length; i++) {
MediaFile similarArtist = similarArtists.get(i);
result[i] = new SimilarArtist(similarArtist.getId(), similarArtist.getName());
}
return Arrays.asList(result);
}
public void setShowSideBar(boolean show) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
UserSettings userSettings = settingsService.getUserSettings(username);
userSettings.setShowSideBar(show);
userSettings.setChanged(new Date());
settingsService.updateUserSettings(userSettings);
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Details about what a user is currently listening to.
*
* @author Sindre Mehus
*/
public class NowPlayingInfo {
private final String playerId;
private final String username;
private final String artist;
private final String title;
private final String tooltip;
private final String streamUrl;
private final String albumUrl;
private final String lyricsUrl;
private final String coverArtUrl;
private final String avatarUrl;
private final int minutesAgo;
public NowPlayingInfo(String playerId, String user, String artist, String title, String tooltip, String streamUrl, String albumUrl,
String lyricsUrl, String coverArtUrl, String avatarUrl, int minutesAgo) {
this.playerId = playerId;
this.username = user;
this.artist = artist;
this.title = title;
this.tooltip = tooltip;
this.streamUrl = streamUrl;
this.albumUrl = albumUrl;
this.lyricsUrl = lyricsUrl;
this.coverArtUrl = coverArtUrl;
this.avatarUrl = avatarUrl;
this.minutesAgo = minutesAgo;
}
public String getPlayerId() {
return playerId;
}
public String getUsername() {
return username;
}
public String getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public String getTooltip() {
return tooltip;
}
public String getStreamUrl() {
return streamUrl;
}
public String getAlbumUrl() {
return albumUrl;
}
public String getLyricsUrl() {
return lyricsUrl;
}
public String getCoverArtUrl() {
return coverArtUrl;
}
public String getAvatarUrl() {
return avatarUrl;
}
public int getMinutesAgo() {
return minutesAgo;
}
}
@@ -0,0 +1,159 @@
/*
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.ajax;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Provides AJAX-enabled services for retrieving the currently playing file and directory.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class NowPlayingService {
private static final Logger LOG = LoggerFactory.getLogger(NowPlayingService.class);
private PlayerService playerService;
private StatusService statusService;
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
/**
* Returns details about what the current player is playing.
*
* @return Details about what the current player is playing, or <code>null</code> if not playing anything.
*/
public NowPlayingInfo getNowPlayingForCurrentPlayer() throws Exception {
WebContext webContext = WebContextFactory.get();
Player player = playerService.getPlayer(webContext.getHttpServletRequest(), webContext.getHttpServletResponse());
for (NowPlayingInfo info : getNowPlaying()) {
if (player.getId().equals(info.getPlayerId())) {
return info;
}
}
return null;
}
/**
* Returns details about what all users are currently playing.
*
* @return Details about what all users are currently playing.
*/
public List<NowPlayingInfo> getNowPlaying() throws Exception {
try {
return convert(statusService.getPlayStatuses());
} catch (Throwable x) {
LOG.error("Unexpected error in getNowPlaying: " + x, x);
return Collections.emptyList();
}
}
/**
* Returns media folder scanning status.
*/
public ScanInfo getScanningStatus() {
return new ScanInfo(mediaScannerService.isScanning(), mediaScannerService.getScanCount());
}
private List<NowPlayingInfo> convert(List<PlayStatus> playStatuses) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String url = NetworkService.getBaseUrl(request);
List<NowPlayingInfo> result = new ArrayList<NowPlayingInfo>();
for (PlayStatus status : playStatuses) {
Player player = status.getPlayer();
MediaFile mediaFile = status.getMediaFile();
String username = player.getUsername();
if (username == null) {
continue;
}
UserSettings userSettings = settingsService.getUserSettings(username);
if (!userSettings.isNowPlayingAllowed()) {
continue;
}
String artist = mediaFile.getArtist();
String title = mediaFile.getTitle();
String streamUrl = url + "/stream?player=" + player.getId() + "&id=" + mediaFile.getId();
String albumUrl = url + "/main.view?id=" + mediaFile.getId();
String lyricsUrl = null;
if (!mediaFile.isVideo()) {
lyricsUrl = url + "/lyrics.view?artistUtf8Hex=" + StringUtil.utf8HexEncode(artist) +
"&songUtf8Hex=" + StringUtil.utf8HexEncode(title);
}
String coverArtUrl = url + "/coverArt.view?size=60&id=" + mediaFile.getId();
String avatarUrl = null;
if (userSettings.getAvatarScheme() == AvatarScheme.SYSTEM) {
avatarUrl = url + "/avatar.view?id=" + userSettings.getSystemAvatarId();
} else if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM && settingsService.getCustomAvatar(username) != null) {
avatarUrl = url + "/avatar.view?usernameUtf8Hex=" + StringUtil.utf8HexEncode(username);
}
String tooltip = StringUtil.toHtml(artist) + " &ndash; " + StringUtil.toHtml(title);
if (StringUtils.isNotBlank(player.getName())) {
username += "@" + player.getName();
}
artist = StringUtil.toHtml(StringUtils.abbreviate(artist, 25));
title = StringUtil.toHtml(StringUtils.abbreviate(title, 25));
username = StringUtil.toHtml(StringUtils.abbreviate(username, 25));
long minutesAgo = status.getMinutesAgo();
if (minutesAgo < 60) {
result.add(new NowPlayingInfo(player.getId(),username, artist, title, tooltip, streamUrl, albumUrl, lyricsUrl,
coverArtUrl, avatarUrl, (int) minutesAgo));
}
}
return result;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
}
@@ -0,0 +1,224 @@
/*
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.ajax;
import org.libresonic.player.util.StringUtil;
import java.util.List;
/**
* The playlist of a player.
*
* @author Sindre Mehus
*/
public class PlayQueueInfo {
private final List<Entry> entries;
private final boolean stopEnabled;
private final boolean repeatEnabled;
private final boolean radioEnabled;
private final boolean sendM3U;
private final float gain;
private int startPlayerAt = -1;
private long startPlayerAtPosition; // millis
public PlayQueueInfo(List<Entry> entries, boolean stopEnabled, boolean repeatEnabled, boolean radioEnabled, boolean sendM3U, float gain) {
this.entries = entries;
this.stopEnabled = stopEnabled;
this.repeatEnabled = repeatEnabled;
this.radioEnabled = radioEnabled;
this.sendM3U = sendM3U;
this.gain = gain;
}
public List<Entry> getEntries() {
return entries;
}
public String getDurationAsString() {
int durationSeconds = 0;
for (Entry entry : entries) {
if (entry.getDuration() != null) {
durationSeconds += entry.getDuration();
}
}
return StringUtil.formatDuration(durationSeconds);
}
public boolean isStopEnabled() {
return stopEnabled;
}
public boolean isSendM3U() {
return sendM3U;
}
public boolean isRepeatEnabled() {
return repeatEnabled;
}
public boolean isRadioEnabled() {
return radioEnabled;
}
public float getGain() {
return gain;
}
public int getStartPlayerAt() {
return startPlayerAt;
}
public PlayQueueInfo setStartPlayerAt(int startPlayerAt) {
this.startPlayerAt = startPlayerAt;
return this;
}
public long getStartPlayerAtPosition() {
return startPlayerAtPosition;
}
public PlayQueueInfo setStartPlayerAtPosition(long startPlayerAtPosition) {
this.startPlayerAtPosition = startPlayerAtPosition;
return this;
}
public static class Entry {
private final int id;
private final Integer trackNumber;
private final String title;
private final String artist;
private final String album;
private final String genre;
private final Integer year;
private final String bitRate;
private final Integer duration;
private final String durationAsString;
private final String format;
private final String contentType;
private final String fileSize;
private final boolean starred;
private final String albumUrl;
private final String streamUrl;
private final String remoteStreamUrl;
private final String coverArtUrl;
private final String remoteCoverArtUrl;
public Entry(int id, Integer trackNumber, String title, String artist, String album, String genre, Integer year,
String bitRate, Integer duration, String durationAsString, String format, String contentType, String fileSize,
boolean starred, String albumUrl, String streamUrl, String remoteStreamUrl, String coverArtUrl, String remoteCoverArtUrl) {
this.id = id;
this.trackNumber = trackNumber;
this.title = title;
this.artist = artist;
this.album = album;
this.genre = genre;
this.year = year;
this.bitRate = bitRate;
this.duration = duration;
this.durationAsString = durationAsString;
this.format = format;
this.contentType = contentType;
this.fileSize = fileSize;
this.starred = starred;
this.albumUrl = albumUrl;
this.streamUrl = streamUrl;
this.remoteStreamUrl = remoteStreamUrl;
this.coverArtUrl = coverArtUrl;
this.remoteCoverArtUrl = remoteCoverArtUrl;
}
public int getId() {
return id;
}
public Integer getTrackNumber() {
return trackNumber;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getGenre() {
return genre;
}
public Integer getYear() {
return year;
}
public String getBitRate() {
return bitRate;
}
public String getDurationAsString() {
return durationAsString;
}
public Integer getDuration() {
return duration;
}
public String getFormat() {
return format;
}
public String getContentType() {
return contentType;
}
public String getFileSize() {
return fileSize;
}
public boolean isStarred() {
return starred;
}
public String getAlbumUrl() {
return albumUrl;
}
public String getStreamUrl() {
return streamUrl;
}
public String getRemoteStreamUrl() {
return remoteStreamUrl;
}
public String getCoverArtUrl() {
return coverArtUrl;
}
public String getRemoteCoverArtUrl() {
return remoteCoverArtUrl;
}
}
}
@@ -0,0 +1,745 @@
/*
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.ajax;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.dao.PlayQueueDao;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.util.StringUtil;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Provides AJAX-enabled services for manipulating the play queue of a player.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
@SuppressWarnings("UnusedDeclaration")
public class PlayQueueService {
private PlayerService playerService;
private JukeboxService jukeboxService;
private TranscodingService transcodingService;
private SettingsService settingsService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SearchService searchService;
private RatingService ratingService;
private PodcastService podcastService;
private org.libresonic.player.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private PlayQueueDao playQueueDao;
private JWTSecurityService jwtSecurityService;
/**
* Returns the play queue for the player of the current user.
*
* @return The play queue.
*/
public PlayQueueInfo getPlayQueue() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
return convert(request, player, false);
}
public PlayQueueInfo start() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doStart(request, response);
}
public PlayQueueInfo doStart(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
return convert(request, player, true);
}
public PlayQueueInfo stop() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doStop(request, response);
}
public PlayQueueInfo doStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
return convert(request, player, true);
}
public PlayQueueInfo toggleStartStop() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doToggleStartStop(request, response);
}
public PlayQueueInfo doToggleStartStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
} else if (player.getPlayQueue().getStatus() == PlayQueue.Status.PLAYING) {
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
}
return convert(request, player, true);
}
public PlayQueueInfo skip(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doSkip(request, response, index, 0);
}
public PlayQueueInfo doSkip(HttpServletRequest request, HttpServletResponse response, int index, int offset) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setIndex(index);
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist, offset);
}
public PlayQueueInfo reloadSearchCriteria() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
Player player = getCurrentPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
if (playQueue.getRandomSearchCriteria() != null) {
playQueue.addFiles(true, mediaFileService.getRandomSongs(playQueue.getRandomSearchCriteria(), username));
}
return convert(request, player, false);
}
public void savePlayQueue(int currentSongIndex, long positionMillis) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
String username = securityService.getCurrentUsername(request);
PlayQueue playQueue = player.getPlayQueue();
List<Integer> ids = MediaFile.toIdList(playQueue.getFiles());
Integer currentId = currentSongIndex == -1 ? null : playQueue.getFile(currentSongIndex).getId();
SavedPlayQueue savedPlayQueue = new SavedPlayQueue(null, username, ids, currentId, positionMillis, new Date(), "Libresonic");
playQueueDao.savePlayQueue(savedPlayQueue);
}
public PlayQueueInfo loadPlayQueue() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
String username = securityService.getCurrentUsername(request);
SavedPlayQueue savedPlayQueue = playQueueDao.getPlayQueue(username);
if (savedPlayQueue == null) {
return convert(request, player, false);
}
PlayQueue playQueue = player.getPlayQueue();
playQueue.clear();
for (Integer mediaFileId : savedPlayQueue.getMediaFileIds()) {
MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
if (mediaFile != null) {
playQueue.addFiles(true, mediaFile);
}
}
PlayQueueInfo result = convert(request, player, false);
Integer currentId = savedPlayQueue.getCurrentMediaFileId();
int currentIndex = -1;
long positionMillis = savedPlayQueue.getPositionMillis() == null ? 0L : savedPlayQueue.getPositionMillis();
if (currentId != null) {
MediaFile current = mediaFileService.getMediaFile(currentId);
currentIndex = playQueue.getFiles().indexOf(current);
if (currentIndex != -1) {
result.setStartPlayerAt(currentIndex);
result.setStartPlayerAtPosition(positionMillis);
}
}
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
if (serverSidePlaylist && currentIndex != -1) {
doSkip(request, response, currentIndex, (int) (positionMillis / 1000L));
}
return result;
}
public PlayQueueInfo play(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
MediaFile file = mediaFileService.getMediaFile(id);
if (file.isFile()) {
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MediaFile> songs;
if (queueFollowingSongs) {
MediaFile dir = mediaFileService.getParentOf(file);
songs = mediaFileService.getChildrenOf(dir, true, false, true);
if (!songs.isEmpty()) {
int index = songs.indexOf(file);
songs = songs.subList(index, songs.size());
}
} else {
songs = Arrays.asList(file);
}
return doPlay(request, player, songs).setStartPlayerAt(0);
} else {
List<MediaFile> songs = mediaFileService.getDescendantsOf(file, true);
return doPlay(request, player, songs).setStartPlayerAt(0);
}
}
/**
* @param index Start playing at this index, or play whole playlist if {@code null}.
*/
public PlayQueueInfo playPlaylist(int id, Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
// Remove non-present files
Iterator<MediaFile> iterator = files.iterator();
while (iterator.hasNext()) {
MediaFile file = iterator.next();
if (!file.isPresent()) {
iterator.remove();
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
/**
* @param index Start playing at this index, or play all top songs if {@code null}.
*/
public PlayQueueInfo playTopSong(int id, Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> files = lastFmService.getTopSongs(mediaFileService.getMediaFile(id), 50, musicFolders);
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playPodcastChannel(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
List<PodcastEpisode> episodes = podcastService.getEpisodes(id);
List<MediaFile> files = new ArrayList<MediaFile>();
for (PodcastEpisode episode : episodes) {
if (episode.getStatus() == PodcastStatus.COMPLETED) {
MediaFile mediaFile = mediaFileService.getMediaFile(episode.getMediaFileId());
if (mediaFile != null && mediaFile.isPresent()) {
files.add(mediaFile);
}
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playPodcastEpisode(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
PodcastEpisode episode = podcastService.getEpisode(id, false);
List<PodcastEpisode> allEpisodes = podcastService.getEpisodes(episode.getChannelId());
List<MediaFile> files = new ArrayList<MediaFile>();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
for (PodcastEpisode ep : allEpisodes) {
if (ep.getStatus() == PodcastStatus.COMPLETED) {
MediaFile mediaFile = mediaFileService.getMediaFile(ep.getMediaFileId());
if (mediaFile != null && mediaFile.isPresent() &&
(ep.getId().equals(episode.getId()) || queueFollowingSongs && !files.isEmpty())) {
files.add(mediaFile);
}
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playNewestPodcastEpisode(Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
List<PodcastEpisode> episodes = podcastService.getNewestEpisodes(10);
List<MediaFile> files = Lists.transform(episodes, new Function<PodcastEpisode, MediaFile>() {
@Override
public MediaFile apply(PodcastEpisode episode) {
return mediaFileService.getMediaFile(episode.getMediaFileId());
}
});
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playStarred() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> files = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playShuffle(String albumListType, int offset, int count, String genre, String decade) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username,
selectedMusicFolder == null ? null : selectedMusicFolder.getId());
List<MediaFile> albums;
if ("highest".equals(albumListType)) {
albums = ratingService.getHighestRatedAlbums(offset, count, musicFolders);
} else if ("frequent".equals(albumListType)) {
albums = mediaFileService.getMostFrequentlyPlayedAlbums(offset, count, musicFolders);
} else if ("recent".equals(albumListType)) {
albums = mediaFileService.getMostRecentlyPlayedAlbums(offset, count, musicFolders);
} else if ("newest".equals(albumListType)) {
albums = mediaFileService.getNewestAlbums(offset, count, musicFolders);
} else if ("starred".equals(albumListType)) {
albums = mediaFileService.getStarredAlbums(offset, count, username, musicFolders);
} else if ("random".equals(albumListType)) {
albums = searchService.getRandomAlbums(count, musicFolders);
} else if ("alphabetical".equals(albumListType)) {
albums = mediaFileService.getAlphabeticalAlbums(offset, count, true, musicFolders);
} else if ("decade".equals(albumListType)) {
int fromYear = Integer.parseInt(decade);
int toYear = fromYear + 9;
albums = mediaFileService.getAlbumsByYear(offset, count, fromYear, toYear, musicFolders);
} else if ("genre".equals(albumListType)) {
albums = mediaFileService.getAlbumsByGenre(offset, count, genre, musicFolders);
} else {
albums = Collections.emptyList();
}
List<MediaFile> songs = new ArrayList<MediaFile>();
for (MediaFile album : albums) {
songs.addAll(mediaFileService.getChildrenOf(album, true, false, false));
}
Collections.shuffle(songs);
songs = songs.subList(0, Math.min(40, songs.size()));
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, songs).setStartPlayerAt(0);
}
private PlayQueueInfo doPlay(HttpServletRequest request, Player player, List<MediaFile> files) throws Exception {
if (player.isWeb()) {
mediaFileService.removeVideoFiles(files);
}
player.getPlayQueue().addFiles(false, files);
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, true);
}
public PlayQueueInfo playRandom(int id, int count) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
MediaFile file = mediaFileService.getMediaFile(id);
List<MediaFile> randomFiles = mediaFileService.getRandomSongsForParent(file, count);
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().addFiles(false, randomFiles);
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, true).setStartPlayerAt(0);
}
public PlayQueueInfo playSimilar(int id, int count) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
MediaFile artist = mediaFileService.getMediaFile(id);
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> similarSongs = lastFmService.getSimilarSongs(artist, count, musicFolders);
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().addFiles(false, similarSongs);
return convert(request, player, true).setStartPlayerAt(0);
}
public PlayQueueInfo add(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doAdd(request, response, new int[]{id}, null);
}
public PlayQueueInfo addAt(int id, int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doAdd(request, response, new int[]{id}, index);
}
public PlayQueueInfo doAdd(HttpServletRequest request, HttpServletResponse response, int[] ids, Integer index) throws Exception {
Player player = getCurrentPlayer(request, response);
List<MediaFile> files = new ArrayList<MediaFile>(ids.length);
for (int id : ids) {
MediaFile ancestor = mediaFileService.getMediaFile(id);
files.addAll(mediaFileService.getDescendantsOf(ancestor, true));
}
if (player.isWeb()) {
mediaFileService.removeVideoFiles(files);
}
if (index != null) {
player.getPlayQueue().addFilesAt(files, index);
} else {
player.getPlayQueue().addFiles(true, files);
}
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, false);
}
public PlayQueueInfo doSet(HttpServletRequest request, HttpServletResponse response, int[] ids) throws Exception {
Player player = getCurrentPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
MediaFile currentFile = playQueue.getCurrentFile();
PlayQueue.Status status = playQueue.getStatus();
playQueue.clear();
PlayQueueInfo result = doAdd(request, response, ids, null);
int index = currentFile == null ? -1 : playQueue.getFiles().indexOf(currentFile);
playQueue.setIndex(index);
playQueue.setStatus(status);
return result;
}
public PlayQueueInfo clear() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doClear(request, response);
}
public PlayQueueInfo doClear(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().clear();
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist);
}
public PlayQueueInfo shuffle() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doShuffle(request, response);
}
public PlayQueueInfo doShuffle(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().shuffle();
return convert(request, player, false);
}
public PlayQueueInfo remove(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doRemove(request, response, index);
}
public PlayQueueInfo toggleStar(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
MediaFile file = player.getPlayQueue().getFile(index);
String username = securityService.getCurrentUsername(request);
boolean starred = mediaFileDao.getMediaFileStarredDate(file.getId(), username) != null;
if (starred) {
mediaFileDao.unstarMediaFile(file.getId(), username);
} else {
mediaFileDao.starMediaFile(file.getId(), username);
}
return convert(request, player, false);
}
public PlayQueueInfo doRemove(HttpServletRequest request, HttpServletResponse response, int index) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().removeFileAt(index);
return convert(request, player, false);
}
public PlayQueueInfo removeMany(int[] indexes) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
for (int i = indexes.length - 1; i >= 0; i--) {
player.getPlayQueue().removeFileAt(indexes[i]);
}
return convert(request, player, false);
}
public PlayQueueInfo rearrange(int[] indexes) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().rearrange(indexes);
return convert(request, player, false);
}
public PlayQueueInfo up(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().moveUp(index);
return convert(request, player, false);
}
public PlayQueueInfo down(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().moveDown(index);
return convert(request, player, false);
}
public PlayQueueInfo toggleRepeat() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
if (playQueue.isRadioEnabled()) {
playQueue.setRandomSearchCriteria(null);
playQueue.setRepeatEnabled(false);
} else {
playQueue.setRepeatEnabled(!player.getPlayQueue().isRepeatEnabled());
}
return convert(request, player, false);
}
public PlayQueueInfo undo() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().undo();
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist);
}
public PlayQueueInfo sortByTrack() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.TRACK);
return convert(request, player, false);
}
public PlayQueueInfo sortByArtist() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.ARTIST);
return convert(request, player, false);
}
public PlayQueueInfo sortByAlbum() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.ALBUM);
return convert(request, player, false);
}
public void setGain(float gain) {
jukeboxService.setGain(gain);
}
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist) throws Exception {
return convert(request, player, serverSidePlaylist, 0);
}
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist, int offset) throws Exception {
String url = NetworkService.getBaseUrl(request);
if (serverSidePlaylist && player.isJukebox()) {
jukeboxService.updateJukebox(player, offset);
}
boolean isCurrentPlayer = player.getIpAddress() != null && player.getIpAddress().equals(request.getRemoteAddr());
boolean m3uSupported = player.isExternal() || player.isExternalWithPlaylist();
serverSidePlaylist = player.isAutoControlEnabled() && m3uSupported && isCurrentPlayer && serverSidePlaylist;
Locale locale = RequestContextUtils.getLocale(request);
List<PlayQueueInfo.Entry> entries = new ArrayList<PlayQueueInfo.Entry>();
PlayQueue playQueue = player.getPlayQueue();
for (MediaFile file : playQueue.getFiles()) {
String albumUrl = url + "/main.view?id=" + file.getId();
String streamUrl = url + "/stream?player=" + player.getId() + "&id=" + file.getId();
String coverArtUrl = url + "/coverArt.view?id=" + file.getId();
String remoteStreamUrl = jwtSecurityService.addJWTToken(url + "/ext/stream?player=" + player.getId() + "&id=" + file.getId());
String remoteCoverArtUrl = jwtSecurityService.addJWTToken(url + "/ext/coverArt.view?id=" + file.getId());
String format = formatFormat(player, file);
String username = securityService.getCurrentUsername(request);
boolean starred = mediaFileService.getMediaFileStarredDate(file.getId(), username) != null;
entries.add(new PlayQueueInfo.Entry(file.getId(), file.getTrackNumber(), file.getTitle(), file.getArtist(),
file.getAlbumName(), file.getGenre(), file.getYear(), formatBitRate(file),
file.getDurationSeconds(), file.getDurationString(), format, formatContentType(format),
formatFileSize(file.getFileSize(), locale), starred, albumUrl, streamUrl, remoteStreamUrl,
coverArtUrl, remoteCoverArtUrl));
}
boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist();
float gain = jukeboxService.getGain();
return new PlayQueueInfo(entries, isStopEnabled, playQueue.isRepeatEnabled(), playQueue.isRadioEnabled(), serverSidePlaylist, gain);
}
private String formatFileSize(Long fileSize, Locale locale) {
if (fileSize == null) {
return null;
}
return StringUtil.formatBytes(fileSize, locale);
}
private String formatFormat(Player player, MediaFile file) {
return file.getFormat();
}
private String formatContentType(String format) {
return StringUtil.getMimeType(format);
}
private String formatBitRate(MediaFile mediaFile) {
if (mediaFile.getBitRate() == null) {
return null;
}
if (mediaFile.isVariableBitRate()) {
return mediaFile.getBitRate() + " Kbps vbr";
}
return mediaFile.getBitRate() + " Kbps";
}
private Player getCurrentPlayer(HttpServletRequest request, HttpServletResponse response) {
return playerService.getPlayer(request, response);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
public void setJukeboxService(JukeboxService jukeboxService) {
this.jukeboxService = jukeboxService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
public void setPlayQueueDao(PlayQueueDao playQueueDao) {
this.playQueueDao = playQueueDao;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setJwtSecurityService(JWTSecurityService jwtSecurityService) {
this.jwtSecurityService = jwtSecurityService;
}
}
@@ -0,0 +1,96 @@
/*
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.ajax;
import org.libresonic.player.domain.Playlist;
import java.util.List;
/**
* The playlist of a player.
*
* @author Sindre Mehus
*/
public class PlaylistInfo {
private final Playlist playlist;
private final List<Entry> entries;
public PlaylistInfo(Playlist playlist, List<Entry> entries) {
this.playlist = playlist;
this.entries = entries;
}
public Playlist getPlaylist() {
return playlist;
}
public List<Entry> getEntries() {
return entries;
}
public static class Entry {
private final int id;
private final String title;
private final String artist;
private final String album;
private final String durationAsString;
private final boolean starred;
private final boolean present;
public Entry(int id, String title, String artist, String album, String durationAsString, boolean starred, boolean present) {
this.id = id;
this.title = title;
this.artist = artist;
this.album = album;
this.durationAsString = durationAsString;
this.starred = starred;
this.present = present;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getDurationAsString() {
return durationAsString;
}
public boolean isStarred() {
return starred;
}
public boolean isPresent() {
return present;
}
}
}
@@ -0,0 +1,263 @@
/*
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.ajax;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.i18n.LibresonicLocaleResolver;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.util.*;
/**
* Provides AJAX-enabled services for manipulating playlists.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class PlaylistService {
private MediaFileService mediaFileService;
private SecurityService securityService;
private org.libresonic.player.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private SettingsService settingsService;
private PlayerService playerService;
private LibresonicLocaleResolver localeResolver;
public List<Playlist> getReadablePlaylists() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
return playlistService.getReadablePlaylistsForUser(username);
}
public List<Playlist> getWritablePlaylists() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
return playlistService.getWritablePlaylistsForUser(username);
}
public PlaylistInfo getPlaylist(int id) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Playlist playlist = playlistService.getPlaylist(id);
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
String username = securityService.getCurrentUsername(request);
mediaFileService.populateStarredDate(files, username);
populateAccess(files, username);
return new PlaylistInfo(playlist, createEntries(files));
}
private void populateAccess(List<MediaFile> files, String username) {
for (MediaFile file : files) {
if (!securityService.isFolderAccessAllowed(file, username)) {
file.setPresent(false);
}
}
}
public List<Playlist> createEmptyPlaylist() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
playlist.setUsername(securityService.getCurrentUsername(request));
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
playlist.setName(dateFormat.format(now));
playlistService.createPlaylist(playlist);
return getReadablePlaylists();
}
public int createPlaylistForPlayQueue() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = playerService.getPlayer(request, response);
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
playlist.setUsername(securityService.getCurrentUsername(request));
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
playlist.setName(dateFormat.format(now));
playlistService.createPlaylist(playlist);
playlistService.setFilesInPlaylist(playlist.getId(), player.getPlayQueue().getFiles());
return playlist.getId();
}
public int createPlaylistForStarredSongs() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
String username = securityService.getCurrentUsername(request);
playlist.setUsername(username);
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
ResourceBundle bundle = ResourceBundle.getBundle("org.libresonic.player.i18n.ResourceBundle", locale);
playlist.setName(bundle.getString("top.starred") + " " + dateFormat.format(now));
playlistService.createPlaylist(playlist);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> songs = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
playlistService.setFilesInPlaylist(playlist.getId(), songs);
return playlist.getId();
}
public void appendToPlaylist(int playlistId, List<Integer> mediaFileIds) {
List<MediaFile> files = playlistService.getFilesInPlaylist(playlistId, true);
for (Integer mediaFileId : mediaFileIds) {
MediaFile file = mediaFileService.getMediaFile(mediaFileId);
if (file != null) {
files.add(file);
}
}
playlistService.setFilesInPlaylist(playlistId, files);
}
private List<PlaylistInfo.Entry> createEntries(List<MediaFile> files) {
List<PlaylistInfo.Entry> result = new ArrayList<PlaylistInfo.Entry>();
for (MediaFile file : files) {
result.add(new PlaylistInfo.Entry(file.getId(), file.getTitle(), file.getArtist(), file.getAlbumName(),
file.getDurationString(), file.getStarredDate() != null, file.isPresent()));
}
return result;
}
public PlaylistInfo toggleStar(int id, int index) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
MediaFile file = files.get(index);
boolean starred = mediaFileDao.getMediaFileStarredDate(file.getId(), username) != null;
if (starred) {
mediaFileDao.unstarMediaFile(file.getId(), username);
} else {
mediaFileDao.starMediaFile(file.getId(), username);
}
return getPlaylist(id);
}
public PlaylistInfo remove(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
files.remove(index);
playlistService.setFilesInPlaylist(id, files);
return getPlaylist(id);
}
public PlaylistInfo up(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (index > 0) {
MediaFile file = files.remove(index);
files.add(index - 1, file);
playlistService.setFilesInPlaylist(id, files);
}
return getPlaylist(id);
}
public PlaylistInfo rearrange(int id, int[] indexes) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
MediaFile[] newFiles = new MediaFile[files.size()];
for (int i = 0; i < indexes.length; i++) {
newFiles[i] = files.get(indexes[i]);
}
playlistService.setFilesInPlaylist(id, Arrays.asList(newFiles));
return getPlaylist(id);
}
public PlaylistInfo down(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (index < files.size() - 1) {
MediaFile file = files.remove(index);
files.add(index + 1, file);
playlistService.setFilesInPlaylist(id, files);
}
return getPlaylist(id);
}
public void deletePlaylist(int id) {
playlistService.deletePlaylist(id);
}
public PlaylistInfo updatePlaylist(int id, String name, String comment, boolean shared) {
Playlist playlist = playlistService.getPlaylist(id);
playlist.setName(name);
playlist.setComment(comment);
playlist.setShared(shared);
playlistService.updatePlaylist(playlist);
return getPlaylist(id);
}
public void setPlaylistService(org.libresonic.player.service.PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setLocaleResolver(LibresonicLocaleResolver localeResolver) {
this.localeResolver = localeResolver;
}
}
@@ -0,0 +1,44 @@
/*
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.ajax;
/**
* Media folder scanning status.
*
* @author Sindre Mehus
*/
public class ScanInfo {
private final boolean scanning;
private final int count;
public ScanInfo(boolean scanning, int count) {
this.scanning = scanning;
this.count = count;
}
public boolean isScanning() {
return scanning;
}
public int getCount() {
return count;
}
}
@@ -0,0 +1,43 @@
/*
* 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 2014 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Contains info about a similar artist.
*
* @author Sindre Mehus
*/
public class SimilarArtist {
private final int mediaFileId;
private final String artistName;
public SimilarArtist(int mediaFileId, String artistName) {
this.mediaFileId = mediaFileId;
this.artistName = artistName;
}
public int getMediaFileId() {
return mediaFileId;
}
public String getArtistName() {
return artistName;
}
}
@@ -0,0 +1,65 @@
/*
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.ajax;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides AJAX-enabled services for starring.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class StarService {
private static final Logger LOG = LoggerFactory.getLogger(StarService.class);
private SecurityService securityService;
private MediaFileDao mediaFileDao;
public void star(int id) {
mediaFileDao.starMediaFile(id, getUser());
}
public void unstar(int id) {
mediaFileDao.unstarMediaFile(id, getUser());
}
private String getUser() {
WebContext webContext = WebContextFactory.get();
User user = securityService.getCurrentUser(webContext.getHttpServletRequest());
return user.getUsername();
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
}
@@ -0,0 +1,131 @@
/*
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.ajax;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.metadata.MetaData;
import org.libresonic.player.service.metadata.MetaDataParser;
import org.libresonic.player.service.metadata.MetaDataParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides AJAX-enabled services for editing tags in music files.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class TagService {
private static final Logger LOG = LoggerFactory.getLogger(TagService.class);
private MetaDataParserFactory metaDataParserFactory;
private MediaFileService mediaFileService;
/**
* Updated tags for a given music file.
*
* @param id The ID of the music file.
* @param track The track number.
* @param artist The artist name.
* @param album The album name.
* @param title The song title.
* @param year The release year.
* @param genre The musical genre.
* @return "UPDATED" if the new tags were updated, "SKIPPED" if no update was necessary.
* Otherwise the error message is returned.
*/
public String setTags(int id, String track, String artist, String album, String title, String year, String genre) {
track = StringUtils.trimToNull(track);
artist = StringUtils.trimToNull(artist);
album = StringUtils.trimToNull(album);
title = StringUtils.trimToNull(title);
year = StringUtils.trimToNull(year);
genre = StringUtils.trimToNull(genre);
Integer trackNumber = null;
if (track != null) {
try {
trackNumber = new Integer(track);
} catch (NumberFormatException x) {
LOG.warn("Illegal track number: " + track, x);
}
}
Integer yearNumber = null;
if (year != null) {
try {
yearNumber = new Integer(year);
} catch (NumberFormatException x) {
LOG.warn("Illegal year: " + year, x);
}
}
try {
MediaFile file = mediaFileService.getMediaFile(id);
MetaDataParser parser = metaDataParserFactory.getParser(file.getFile());
if (!parser.isEditingSupported()) {
return "Tag editing of " + FilenameUtils.getExtension(file.getPath()) + " files is not supported.";
}
if (StringUtils.equals(artist, file.getArtist()) &&
StringUtils.equals(album, file.getAlbumName()) &&
StringUtils.equals(title, file.getTitle()) &&
ObjectUtils.equals(yearNumber, file.getYear()) &&
StringUtils.equals(genre, file.getGenre()) &&
ObjectUtils.equals(trackNumber, file.getTrackNumber())) {
return "SKIPPED";
}
MetaData newMetaData = parser.getMetaData(file.getFile());
// Note: album artist is intentionally set, as it is not user-changeable.
newMetaData.setArtist(artist);
newMetaData.setAlbumName(album);
newMetaData.setTitle(title);
newMetaData.setYear(yearNumber);
newMetaData.setGenre(genre);
newMetaData.setTrackNumber(trackNumber);
parser.setMetaData(file, newMetaData);
mediaFileService.refreshMediaFile(file);
mediaFileService.refreshMediaFile(mediaFileService.getParentOf(file));
return "UPDATED";
} catch (Exception x) {
LOG.warn("Failed to update tags for " + id, x);
return x.getMessage();
}
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setMetaDataParserFactory(MetaDataParserFactory metaDataParserFactory) {
this.metaDataParserFactory = metaDataParserFactory;
}
}
@@ -0,0 +1,67 @@
/*
* 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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* See {@link ArtistInfo}.
*
* @author Sindre Mehus
*/
public class TopSong {
private final int id;
private final String title;
private final String artist;
private final String album;
private final String durationAsString;
private final boolean starred;
public TopSong(int id, String title, String artist, String album, String durationAsString, boolean starred) {
this.id = id;
this.title = title;
this.artist = artist;
this.album = album;
this.durationAsString = durationAsString;
this.starred = starred;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getDurationAsString() {
return durationAsString;
}
public boolean isStarred() {
return starred;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.controller.UploadController;
import org.libresonic.player.domain.TransferStatus;
import javax.servlet.http.HttpSession;
/**
* Provides AJAX-enabled services for retrieving the status of ongoing transfers.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class TransferService {
/**
* Returns info about any ongoing upload within the current session.
* @return Info about ongoing upload.
*/
public UploadInfo getUploadInfo() {
HttpSession session = WebContextFactory.get().getSession();
TransferStatus status = (TransferStatus) session.getAttribute(UploadController.UPLOAD_STATUS);
if (status != null) {
return new UploadInfo(status.getBytesTransfered(), status.getBytesTotal());
}
return new UploadInfo(0L, 0L);
}
}
@@ -0,0 +1,53 @@
/*
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.ajax;
/**
* Contains status for a file upload.
*
* @author Sindre Mehus
*/
public class UploadInfo {
private long bytesUploaded;
private long bytesTotal;
public UploadInfo(long bytesUploaded, long bytesTotal) {
this.bytesUploaded = bytesUploaded;
this.bytesTotal = bytesTotal;
}
/**
* Returns the number of bytes uploaded.
* @return The number of bytes uploaded.
*/
public long getBytesUploaded() {
return bytesUploaded;
}
/**
* Returns the total number of bytes.
* @return The total number of bytes.
*/
public long getBytesTotal() {
return bytesTotal;
}
}
@@ -0,0 +1,222 @@
package org.libresonic.player.boot;
import net.sf.ehcache.constructs.web.ShutdownListener;
import org.directwebremoting.servlet.DwrServlet;
import org.libresonic.player.filter.*;
import org.libresonic.player.spring.LibresonicPropertySourceConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.util.ReflectionUtils;
import javax.servlet.Filter;
import javax.servlet.ServletContextListener;
import java.lang.reflect.Method;
@SpringBootApplication(exclude = {
JmxAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
MultipartAutoConfiguration.class, // TODO: update to use spring boot builtin multipart support
LiquibaseAutoConfiguration.class})
@Configuration
@ImportResource(value = {"classpath:/applicationContext-service.xml",
"classpath:/applicationContext-cache.xml",
"classpath:/applicationContext-sonos.xml",
"classpath:/libresonic-servlet.xml"})
public class Application extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer {
private static final Logger LOG = LoggerFactory.getLogger(Application.class);
/**
* Registers the DWR servlet.
*
* @return a registration bean.
*/
@Bean
public ServletRegistrationBean dwrServletRegistrationBean() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new DwrServlet(), "/dwr/*");
servlet.addInitParameter("crossDomainSessionSecurity","false");
return servlet;
}
@Bean
public ServletRegistrationBean cxfServletBean() {
return new ServletRegistrationBean(new org.apache.cxf.transport.servlet.CXFServlet(), "/ws/*");
}
@Bean
public ServletContextListener ehCacheShutdownListener() {
return new ShutdownListener();
}
@Bean
public FilterRegistrationBean bootstrapVerificationFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(bootstrapVerificationFiler());
registration.addUrlPatterns("/*");
registration.setName("BootstrapVerificationFilter");
registration.setOrder(1);
return registration;
}
@Bean
public Filter bootstrapVerificationFiler() {
return new BootstrapVerificationFilter();
}
@Bean
public FilterRegistrationBean parameterDecodingFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(parameterDecodingFilter());
registration.addUrlPatterns("/*");
registration.setName("ParameterDecodingFilter");
registration.setOrder(2);
return registration;
}
@Bean
public Filter parameterDecodingFilter() {
return new ParameterDecodingFilter();
}
@Bean
public FilterRegistrationBean restFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(restFilter());
registration.addUrlPatterns("/rest/*");
registration.setName("RESTFilter");
registration.setOrder(3);
return registration;
}
@Bean
public Filter restFilter() {
return new RESTFilter();
}
@Bean
public FilterRegistrationBean requestEncodingFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(requestEncodingFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("encoding", "UTF-8");
registration.setName("RequestEncodingFilter");
registration.setOrder(4);
return registration;
}
@Bean
public Filter requestEncodingFilter() {
return new RequestEncodingFilter();
}
@Bean
public FilterRegistrationBean cacheFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(cacheFilter());
registration.addUrlPatterns("/icons/*", "/style/*", "/script/*", "/dwr/*", "/icons/*", "/coverArt.view", "/avatar.view");
registration.addInitParameter("Cache-Control", "max-age=36000");
registration.setName("CacheFilter");
registration.setOrder(5);
return registration;
}
@Bean
public Filter cacheFilter() {
return new ResponseHeaderFilter();
}
@Bean
public FilterRegistrationBean noCacheFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(noCacheFilter());
registration.addUrlPatterns("/statusChart.view", "/userChart.view", "/playQueue.view", "/podcastChannels.view", "/podcastChannel.view", "/help.view", "/top.view", "/home.view");
registration.addInitParameter("Cache-Control", "no-cache, post-check=0, pre-check=0");
registration.addInitParameter("Pragma", "no-cache");
registration.addInitParameter("Expires", "Thu, 01 Dec 1994 16:00:00 GMT");
registration.setName("NoCacheFilter");
registration.setOrder(6);
return registration;
}
@Bean
public Filter metricsFilter() {
return new MetricsFilter();
}
@Bean
public FilterRegistrationBean metricsFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(metricsFilter());
registration.setOrder(7);
return registration;
}
@Bean
public Filter noCacheFilter() {
return new ResponseHeaderFilter();
}
private static SpringApplicationBuilder doConfigure(SpringApplicationBuilder application) {
// Customize the application or call application.sources(...) to add sources
// Since our example is itself a @Configuration class (via @SpringBootApplication)
// we actually don't need to override this method.
return application.sources(Application.class).web(true).initializers(new LibresonicPropertySourceConfigurer());
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return doConfigure(application);
}
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
// Yes, there is a good reason we do this.
// We cannot count on the tomcat classes being on the classpath which will
// happen if the war is deployed to another app server like Jetty. So, we
// ensure this class does not have any direct dependencies on any Tomcat
// specific classes.
try {
Class<?> tomcatESCF = Class.forName("org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory");
if(tomcatESCF.isInstance(container)) {
LOG.debug("Attempting to optimize tomcat");
Object tomcatESCFInstance = tomcatESCF.cast(container);
Class<?> tomcatApplicationClass = Class.forName("org.libresonic.player.boot.TomcatApplication");
Method configure = ReflectionUtils.findMethod(tomcatApplicationClass, "configure", tomcatESCF);
configure.invoke(null, tomcatESCFInstance);
LOG.debug("Tomcat optimizations complete");
} else {
LOG.debug("Skipping tomcat optimization as we are not running on tomcat");
}
} catch (NoClassDefFoundError | ClassNotFoundException e) {
LOG.debug("Skipping tomcat optimization as the tomcat classes are not available");
} catch (Exception e) {
LOG.warn("An error happened while trying to optimize tomcat", e);
}
}
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
doConfigure(builder).run(args);
}
}
@@ -0,0 +1,40 @@
package org.libresonic.player.boot;
import org.apache.catalina.Container;
import org.apache.catalina.Wrapper;
import org.apache.catalina.webresources.StandardRoot;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
public class TomcatApplication {
public static void configure(TomcatEmbeddedServletContainerFactory tomcatFactory) {
tomcatFactory.addContextCustomizers((TomcatContextCustomizer) context -> {
boolean development = (System.getProperty("libresonic.development") != null);
// Increase the size and time before eviction of the Tomcat
// cache so that resources aren't uncompressed too often.
// See https://github.com/jhipster/generator-jhipster/issues/3995
StandardRoot resources = new StandardRoot();
if (development) {
resources.setCachingAllowed(false);
} else {
resources.setCacheMaxSize(100000);
resources.setCacheObjectMaxSize(4000);
resources.setCacheTtl(24 * 3600 * 1000); // 1 day, in milliseconds
}
context.setResources(resources);
// Put Jasper in production mode so that JSP aren't recompiled
// on each request.
// See http://stackoverflow.com/questions/29653326/spring-boot-application-slow-because-of-jsp-compilation
Container jsp = context.findChild("jsp");
if (jsp instanceof Wrapper) {
((Wrapper) jsp).addInitParameter("development", Boolean.toString(development));
}
});
}
}
@@ -0,0 +1,57 @@
/*
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.cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.ConfigurationFactory;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import java.io.File;
/**
* Initializes Ehcache and creates caches.
*
* @author Sindre Mehus
* @version $Id$
*/
public class CacheFactory implements InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(CacheFactory.class);
private CacheManager cacheManager;
public void afterPropertiesSet() throws Exception {
Configuration configuration = ConfigurationFactory.parseConfiguration();
// Override configuration to make sure cache is stored in Libresonic home dir.
File cacheDir = new File(SettingsService.getLibresonicHome(), "cache");
configuration.getDiskStoreConfiguration().setPath(cacheDir.getPath());
cacheManager = CacheManager.create(configuration);
}
public Ehcache getCache(String name) {
return cacheManager.getCache(name);
}
}
@@ -0,0 +1,170 @@
/*
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.command;
import org.libresonic.player.controller.AdvancedSettingsController;
/**
* Command used in {@link AdvancedSettingsController}.
*
* @author Sindre Mehus
*/
public class AdvancedSettingsCommand {
private String downloadLimit;
private String uploadLimit;
private boolean ldapEnabled;
private String ldapUrl;
private String ldapSearchFilter;
private String ldapManagerDn;
private String ldapManagerPassword;
private boolean ldapAutoShadowing;
private String brand;
private String smtpServer;
private String smtpEncryption;
private String smtpPort;
private String smtpUser;
private String smtpPassword;
private String smtpFrom;
public String getDownloadLimit() {
return downloadLimit;
}
public void setDownloadLimit(String downloadLimit) {
this.downloadLimit = downloadLimit;
}
public String getUploadLimit() {
return uploadLimit;
}
public void setUploadLimit(String uploadLimit) {
this.uploadLimit = uploadLimit;
}
public boolean isLdapEnabled() {
return ldapEnabled;
}
public void setLdapEnabled(boolean ldapEnabled) {
this.ldapEnabled = ldapEnabled;
}
public String getLdapUrl() {
return ldapUrl;
}
public void setLdapUrl(String ldapUrl) {
this.ldapUrl = ldapUrl;
}
public String getLdapSearchFilter() {
return ldapSearchFilter;
}
public void setLdapSearchFilter(String ldapSearchFilter) {
this.ldapSearchFilter = ldapSearchFilter;
}
public String getLdapManagerDn() {
return ldapManagerDn;
}
public void setLdapManagerDn(String ldapManagerDn) {
this.ldapManagerDn = ldapManagerDn;
}
public String getLdapManagerPassword() {
return ldapManagerPassword;
}
public void setLdapManagerPassword(String ldapManagerPassword) {
this.ldapManagerPassword = ldapManagerPassword;
}
public boolean isLdapAutoShadowing() {
return ldapAutoShadowing;
}
public void setLdapAutoShadowing(boolean ldapAutoShadowing) {
this.ldapAutoShadowing = ldapAutoShadowing;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setReloadNeeded(boolean reloadNeeded) {
}
public String getSmtpServer() {
return smtpServer;
}
public void setSmtpServer(String smtpServer) {
this.smtpServer = smtpServer;
}
public String getSmtpEncryption() {
return smtpEncryption;
}
public void setSmtpEncryption(String smtpEncryption) {
this.smtpEncryption = smtpEncryption;
}
public String getSmtpPort() {
return smtpPort;
}
public void setSmtpPort(String smtpPort) {
this.smtpPort = smtpPort;
}
public String getSmtpUser() {
return smtpUser;
}
public void setSmtpUser(String smtpUser) {
this.smtpUser = smtpUser;
}
public String getSmtpPassword() {
return smtpPassword;
}
public void setSmtpPassword(String smtpPassword) {
this.smtpPassword = smtpPassword;
}
public String getSmtpFrom() {
return smtpFrom;
}
public void setSmtpFrom(String smtpFrom) {
this.smtpFrom = smtpFrom;
}
}
@@ -0,0 +1,82 @@
package org.libresonic.player.command;
import org.libresonic.player.spring.DataSourceConfigType;
import javax.validation.constraints.NotNull;
public class DatabaseSettingsCommand {
@NotNull
private DataSourceConfigType configType;
private String embedDriver;
private String embedPassword;
private String embedUrl;
private String embedUsername;
private String JNDIName;
private int mysqlVarcharMaxlength;
private String usertableQuote;
public DataSourceConfigType getConfigType() {
return configType;
}
public void setConfigType(DataSourceConfigType configType) {
this.configType = configType;
}
public String getEmbedDriver() {
return embedDriver;
}
public void setEmbedDriver(String embedDriver) {
this.embedDriver = embedDriver;
}
public String getEmbedPassword() {
return embedPassword;
}
public void setEmbedPassword(String embedPassword) {
this.embedPassword = embedPassword;
}
public String getEmbedUrl() {
return embedUrl;
}
public void setEmbedUrl(String embedUrl) {
this.embedUrl = embedUrl;
}
public String getEmbedUsername() {
return embedUsername;
}
public void setEmbedUsername(String embedUsername) {
this.embedUsername = embedUsername;
}
public String getJNDIName() {
return JNDIName;
}
public void setJNDIName(String JNDIName) {
this.JNDIName = JNDIName;
}
public int getMysqlVarcharMaxlength() {
return mysqlVarcharMaxlength;
}
public void setMysqlVarcharMaxlength(int mysqlVarcharMaxlength) {
this.mysqlVarcharMaxlength = mysqlVarcharMaxlength;
}
public String getUsertableQuote() {
return usertableQuote;
}
public void setUsertableQuote(String usertableQuote) {
this.usertableQuote = usertableQuote;
}
}
@@ -0,0 +1,43 @@
/*
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.command;
/**
* Holds the name and description of an enum value.
*
* @author Sindre Mehus
*/
public class EnumHolder {
private String name;
private String description;
public EnumHolder(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,189 @@
/*
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.command;
import org.libresonic.player.controller.GeneralSettingsController;
import org.libresonic.player.domain.Theme;
/**
* Command used in {@link GeneralSettingsController}.
*
* @author Sindre Mehus
*/
public class GeneralSettingsCommand {
private String playlistFolder;
private String musicFileTypes;
private String videoFileTypes;
private String coverArtFileTypes;
private String index;
private String ignoredArticles;
private String shortcuts;
private boolean sortAlbumsByYear;
private boolean gettingStartedEnabled;
private String welcomeTitle;
private String welcomeSubtitle;
private String welcomeMessage;
private String loginMessage;
private String localeIndex;
private String[] locales;
private String themeIndex;
private Theme[] themes;
public String getPlaylistFolder() {
return playlistFolder;
}
public void setPlaylistFolder(String playlistFolder) {
this.playlistFolder = playlistFolder;
}
public String getMusicFileTypes() {
return musicFileTypes;
}
public void setMusicFileTypes(String musicFileTypes) {
this.musicFileTypes = musicFileTypes;
}
public String getVideoFileTypes() {
return videoFileTypes;
}
public void setVideoFileTypes(String videoFileTypes) {
this.videoFileTypes = videoFileTypes;
}
public String getCoverArtFileTypes() {
return coverArtFileTypes;
}
public void setCoverArtFileTypes(String coverArtFileTypes) {
this.coverArtFileTypes = coverArtFileTypes;
}
public String getIndex() {
return index;
}
public void setIndex(String index) {
this.index = index;
}
public String getIgnoredArticles() {
return ignoredArticles;
}
public void setIgnoredArticles(String ignoredArticles) {
this.ignoredArticles = ignoredArticles;
}
public String getShortcuts() {
return shortcuts;
}
public void setShortcuts(String shortcuts) {
this.shortcuts = shortcuts;
}
public String getWelcomeTitle() {
return welcomeTitle;
}
public void setWelcomeTitle(String welcomeTitle) {
this.welcomeTitle = welcomeTitle;
}
public String getWelcomeSubtitle() {
return welcomeSubtitle;
}
public void setWelcomeSubtitle(String welcomeSubtitle) {
this.welcomeSubtitle = welcomeSubtitle;
}
public String getWelcomeMessage() {
return welcomeMessage;
}
public void setWelcomeMessage(String welcomeMessage) {
this.welcomeMessage = welcomeMessage;
}
public String getLoginMessage() {
return loginMessage;
}
public void setLoginMessage(String loginMessage) {
this.loginMessage = loginMessage;
}
public String getLocaleIndex() {
return localeIndex;
}
public void setLocaleIndex(String localeIndex) {
this.localeIndex = localeIndex;
}
public String[] getLocales() {
return locales;
}
public void setLocales(String[] locales) {
this.locales = locales;
}
public String getThemeIndex() {
return themeIndex;
}
public void setThemeIndex(String themeIndex) {
this.themeIndex = themeIndex;
}
public Theme[] getThemes() {
return themes;
}
public void setThemes(Theme[] themes) {
this.themes = themes;
}
public void setReloadNeeded(boolean reloadNeeded) {
}
public boolean isSortAlbumsByYear() {
return sortAlbumsByYear;
}
public void setSortAlbumsByYear(boolean sortAlbumsByYear) {
this.sortAlbumsByYear = sortAlbumsByYear;
}
public boolean isGettingStartedEnabled() {
return gettingStartedEnabled;
}
public void setGettingStartedEnabled(boolean gettingStartedEnabled) {
this.gettingStartedEnabled = gettingStartedEnabled;
}
}
@@ -0,0 +1,179 @@
/*
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.command;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.controller.MusicFolderSettingsController;
import org.libresonic.player.domain.MusicFolder;
import java.io.File;
import java.util.Date;
import java.util.List;
/**
* Command used in {@link MusicFolderSettingsController}.
*
* @author Sindre Mehus
*/
public class MusicFolderSettingsCommand {
private String interval;
private String hour;
private boolean scanning;
private boolean fastCache;
private boolean organizeByFolderStructure;
private List<MusicFolderInfo> musicFolders;
private MusicFolderInfo newMusicFolder;
public String getInterval() {
return interval;
}
public void setInterval(String interval) {
this.interval = interval;
}
public String getHour() {
return hour;
}
public void setHour(String hour) {
this.hour = hour;
}
public boolean isScanning() {
return scanning;
}
public void setScanning(boolean scanning) {
this.scanning = scanning;
}
public boolean isFastCache() {
return fastCache;
}
public List<MusicFolderInfo> getMusicFolders() {
return musicFolders;
}
public void setMusicFolders(List<MusicFolderInfo> musicFolders) {
this.musicFolders = musicFolders;
}
public void setFastCache(boolean fastCache) {
this.fastCache = fastCache;
}
public MusicFolderInfo getNewMusicFolder() {
return newMusicFolder;
}
public void setNewMusicFolder(MusicFolderInfo newMusicFolder) {
this.newMusicFolder = newMusicFolder;
}
public boolean isOrganizeByFolderStructure() {
return organizeByFolderStructure;
}
public void setOrganizeByFolderStructure(boolean organizeByFolderStructure) {
this.organizeByFolderStructure = organizeByFolderStructure;
}
public static class MusicFolderInfo {
private Integer id;
private String path;
private String name;
private boolean enabled;
private boolean delete;
private boolean existing;
public MusicFolderInfo(MusicFolder musicFolder) {
id = musicFolder.getId();
path = musicFolder.getPath().getPath();
name = musicFolder.getName();
enabled = musicFolder.isEnabled();
existing = musicFolder.getPath().exists() && musicFolder.getPath().isDirectory();
}
public MusicFolderInfo() {
enabled = true;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isDelete() {
return delete;
}
public void setDelete(boolean delete) {
this.delete = delete;
}
public MusicFolder toMusicFolder() {
String path = StringUtils.trimToNull(this.path);
if (path == null) {
return null;
}
File file = new File(path);
String name = StringUtils.trimToNull(this.name);
if (name == null) {
name = file.getName();
}
return new MusicFolder(id, new File(path), name, enabled, new Date());
}
public boolean isExisting() {
return existing;
}
}
}
@@ -0,0 +1,67 @@
/*
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.command;
import org.libresonic.player.controller.PasswordSettingsController;
/**
* Command used in {@link PasswordSettingsController}.
*
* @author Sindre Mehus
*/
public class PasswordSettingsCommand {
private String username;
private String password;
private String confirmPassword;
private boolean ldapAuthenticated;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
public boolean isLdapAuthenticated() {
return ldapAuthenticated;
}
public void setLdapAuthenticated(boolean ldapAuthenticated) {
this.ldapAuthenticated = ldapAuthenticated;
}
}
@@ -0,0 +1,276 @@
/*
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.command;
import org.libresonic.player.controller.PersonalSettingsController;
import org.libresonic.player.domain.*;
import java.util.List;
/**
* Command used in {@link PersonalSettingsController}.
*
* @author Sindre Mehus
*/
public class PersonalSettingsCommand {
private User user;
private String localeIndex;
private String[] locales;
private String themeIndex;
private Theme[] themes;
private String albumListId;
private AlbumListType[] albumLists;
private int avatarId;
private List<Avatar> avatars;
private Avatar customAvatar;
private UserSettings.Visibility mainVisibility;
private UserSettings.Visibility playlistVisibility;
private boolean partyModeEnabled;
private boolean showNowPlayingEnabled;
private boolean showArtistInfoEnabled;
private boolean nowPlayingAllowed;
private boolean autoHidePlayQueue;
private boolean keyboardShortcutsEnabled;
private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled;
private boolean queueFollowingSongs;
private boolean lastFmEnabled;
private int listReloadDelay;
private int paginationSize;
private String lastFmUsername;
private String lastFmPassword;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getLocaleIndex() {
return localeIndex;
}
public void setLocaleIndex(String localeIndex) {
this.localeIndex = localeIndex;
}
public String[] getLocales() {
return locales;
}
public void setLocales(String[] locales) {
this.locales = locales;
}
public String getThemeIndex() {
return themeIndex;
}
public void setThemeIndex(String themeIndex) {
this.themeIndex = themeIndex;
}
public Theme[] getThemes() {
return themes;
}
public void setThemes(Theme[] themes) {
this.themes = themes;
}
public String getAlbumListId() {
return albumListId;
}
public void setAlbumListId(String albumListId) {
this.albumListId = albumListId;
}
public AlbumListType[] getAlbumLists() {
return albumLists;
}
public void setAlbumLists(AlbumListType[] albumLists) {
this.albumLists = albumLists;
}
public int getAvatarId() {
return avatarId;
}
public void setAvatarId(int avatarId) {
this.avatarId = avatarId;
}
public List<Avatar> getAvatars() {
return avatars;
}
public void setAvatars(List<Avatar> avatars) {
this.avatars = avatars;
}
public Avatar getCustomAvatar() {
return customAvatar;
}
public void setCustomAvatar(Avatar customAvatar) {
this.customAvatar = customAvatar;
}
public UserSettings.Visibility getMainVisibility() {
return mainVisibility;
}
public void setMainVisibility(UserSettings.Visibility mainVisibility) {
this.mainVisibility = mainVisibility;
}
public UserSettings.Visibility getPlaylistVisibility() {
return playlistVisibility;
}
public void setPlaylistVisibility(UserSettings.Visibility playlistVisibility) {
this.playlistVisibility = playlistVisibility;
}
public boolean isPartyModeEnabled() {
return partyModeEnabled;
}
public void setPartyModeEnabled(boolean partyModeEnabled) {
this.partyModeEnabled = partyModeEnabled;
}
public boolean isShowNowPlayingEnabled() {
return showNowPlayingEnabled;
}
public void setShowNowPlayingEnabled(boolean showNowPlayingEnabled) {
this.showNowPlayingEnabled = showNowPlayingEnabled;
}
public boolean isShowArtistInfoEnabled() {
return showArtistInfoEnabled;
}
public void setShowArtistInfoEnabled(boolean showArtistInfoEnabled) {
this.showArtistInfoEnabled = showArtistInfoEnabled;
}
public boolean isNowPlayingAllowed() {
return nowPlayingAllowed;
}
public void setNowPlayingAllowed(boolean nowPlayingAllowed) {
this.nowPlayingAllowed = nowPlayingAllowed;
}
public boolean isFinalVersionNotificationEnabled() {
return finalVersionNotificationEnabled;
}
public void setFinalVersionNotificationEnabled(boolean finalVersionNotificationEnabled) {
this.finalVersionNotificationEnabled = finalVersionNotificationEnabled;
}
public boolean isBetaVersionNotificationEnabled() {
return betaVersionNotificationEnabled;
}
public void setBetaVersionNotificationEnabled(boolean betaVersionNotificationEnabled) {
this.betaVersionNotificationEnabled = betaVersionNotificationEnabled;
}
public void setSongNotificationEnabled(boolean songNotificationEnabled) {
this.songNotificationEnabled = songNotificationEnabled;
}
public boolean isSongNotificationEnabled() {
return songNotificationEnabled;
}
public boolean isAutoHidePlayQueue() {
return autoHidePlayQueue;
}
public void setAutoHidePlayQueue(boolean autoHidePlayQueue) {
this.autoHidePlayQueue = autoHidePlayQueue;
}
public boolean isKeyboardShortcutsEnabled() {
return keyboardShortcutsEnabled;
}
public void setKeyboardShortcutsEnabled(boolean keyboardShortcutsEnabled) {
this.keyboardShortcutsEnabled = keyboardShortcutsEnabled;
}
public boolean isLastFmEnabled() {
return lastFmEnabled;
}
public void setLastFmEnabled(boolean lastFmEnabled) {
this.lastFmEnabled = lastFmEnabled;
}
public int getListReloadDelay() {
return listReloadDelay;
}
public void setListReloadDelay(int listReloadDelay) {
this.listReloadDelay = listReloadDelay;
}
public String getLastFmUsername() {
return lastFmUsername;
}
public void setLastFmUsername(String lastFmUsername) {
this.lastFmUsername = lastFmUsername;
}
public String getLastFmPassword() {
return lastFmPassword;
}
public void setLastFmPassword(String lastFmPassword) {
this.lastFmPassword = lastFmPassword;
}
public boolean isQueueFollowingSongs() {
return queueFollowingSongs;
}
public void setQueueFollowingSongs(boolean queueFollowingSongs) {
this.queueFollowingSongs = queueFollowingSongs;
}
public int getPaginationSize() {
return paginationSize;
}
public void setPaginationSize(int paginationSize) {
this.paginationSize = paginationSize;
}
}
@@ -0,0 +1,232 @@
/*
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.command;
import org.libresonic.player.controller.PlayerSettingsController;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.PlayerTechnology;
import org.libresonic.player.domain.TranscodeScheme;
import org.libresonic.player.domain.Transcoding;
import java.util.Date;
import java.util.List;
/**
* Command used in {@link PlayerSettingsController}.
*
* @author Sindre Mehus
*/
public class PlayerSettingsCommand {
private String playerId;
private String name;
private String description;
private String type;
private Date lastSeen;
private boolean isDynamicIp;
private boolean isAutoControlEnabled;
private boolean isM3uBomEnabled;
private String technologyName;
private String transcodeSchemeName;
private boolean transcodingSupported;
private String transcodeDirectory;
private List<Transcoding> allTranscodings;
private int[] activeTranscodingIds;
private EnumHolder[] technologyHolders;
private EnumHolder[] transcodeSchemeHolders;
private Player[] players;
private boolean isAdmin;
public String getPlayerId() {
return playerId;
}
public void setPlayerId(String playerId) {
this.playerId = playerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Date getLastSeen() {
return lastSeen;
}
public void setLastSeen(Date lastSeen) {
this.lastSeen = lastSeen;
}
public boolean isDynamicIp() {
return isDynamicIp;
}
public void setDynamicIp(boolean dynamicIp) {
isDynamicIp = dynamicIp;
}
public boolean isAutoControlEnabled() {
return isAutoControlEnabled;
}
public void setAutoControlEnabled(boolean autoControlEnabled) {
isAutoControlEnabled = autoControlEnabled;
}
public boolean isM3uBomEnabled() {
return isM3uBomEnabled;
}
public void setM3uBomEnabled(boolean m3uBomEnabled) {
isM3uBomEnabled = m3uBomEnabled;
}
public String getTranscodeSchemeName() {
return transcodeSchemeName;
}
public void setTranscodeSchemeName(String transcodeSchemeName) {
this.transcodeSchemeName = transcodeSchemeName;
}
public boolean isTranscodingSupported() {
return transcodingSupported;
}
public void setTranscodingSupported(boolean transcodingSupported) {
this.transcodingSupported = transcodingSupported;
}
public String getTranscodeDirectory() {
return transcodeDirectory;
}
public void setTranscodeDirectory(String transcodeDirectory) {
this.transcodeDirectory = transcodeDirectory;
}
public List<Transcoding> getAllTranscodings() {
return allTranscodings;
}
public void setAllTranscodings(List<Transcoding> allTranscodings) {
this.allTranscodings = allTranscodings;
}
public int[] getActiveTranscodingIds() {
return activeTranscodingIds;
}
public void setActiveTranscodingIds(int[] activeTranscodingIds) {
this.activeTranscodingIds = activeTranscodingIds;
}
public EnumHolder[] getTechnologyHolders() {
return technologyHolders;
}
public void setTechnologies(PlayerTechnology[] technologies) {
technologyHolders = new EnumHolder[technologies.length];
for (int i = 0; i < technologies.length; i++) {
PlayerTechnology technology = technologies[i];
technologyHolders[i] = new EnumHolder(technology.name(), technology.toString());
}
}
public EnumHolder[] getTranscodeSchemeHolders() {
return transcodeSchemeHolders;
}
public void setTranscodeSchemes(TranscodeScheme[] transcodeSchemes) {
transcodeSchemeHolders = new EnumHolder[transcodeSchemes.length];
for (int i = 0; i < transcodeSchemes.length; i++) {
TranscodeScheme scheme = transcodeSchemes[i];
transcodeSchemeHolders[i] = new EnumHolder(scheme.name(), scheme.toString());
}
}
public String getTechnologyName() {
return technologyName;
}
public void setTechnologyName(String technologyName) {
this.technologyName = technologyName;
}
public Player[] getPlayers() {
return players;
}
public void setPlayers(Player[] players) {
this.players = players;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean admin) {
isAdmin = admin;
}
public void setReloadNeeded(boolean reloadNeeded) {
}
/**
* Holds the transcoding and whether it is active for the given player.
*/
public static class TranscodingHolder {
private Transcoding transcoding;
private boolean isActive;
public TranscodingHolder(Transcoding transcoding, boolean isActive) {
this.transcoding = transcoding;
this.isActive = isActive;
}
public Transcoding getTranscoding() {
return transcoding;
}
public boolean isActive() {
return isActive;
}
}
}
@@ -0,0 +1,68 @@
/*
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.command;
import org.libresonic.player.controller.PodcastSettingsController;
/**
* Command used in {@link PodcastSettingsController}.
*
* @author Sindre Mehus
*/
public class PodcastSettingsCommand {
private String interval;
private String folder;
private String episodeRetentionCount;
private String episodeDownloadCount;
public String getInterval() {
return interval;
}
public void setInterval(String interval) {
this.interval = interval;
}
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
public String getEpisodeRetentionCount() {
return episodeRetentionCount;
}
public void setEpisodeRetentionCount(String episodeRetentionCount) {
this.episodeRetentionCount = episodeRetentionCount;
}
public String getEpisodeDownloadCount() {
return episodeDownloadCount;
}
public void setEpisodeDownloadCount(String episodeDownloadCount) {
this.episodeDownloadCount = episodeDownloadCount;
}
}
@@ -0,0 +1,138 @@
/*
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.command;
import org.libresonic.player.controller.SearchController;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import java.util.List;
/**
* Command used in {@link SearchController}.
*
* @author Sindre Mehus
*/
public class SearchCommand {
private String query;
private List<MediaFile> artists;
private List<MediaFile> albums;
private List<MediaFile> songs;
private boolean isIndexBeingCreated;
private User user;
private boolean partyModeEnabled;
private Player player;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public boolean isIndexBeingCreated() {
return isIndexBeingCreated;
}
public void setIndexBeingCreated(boolean indexBeingCreated) {
isIndexBeingCreated = indexBeingCreated;
}
public List<MediaFile> getArtists() {
return artists;
}
public void setArtists(List<MediaFile> artists) {
this.artists = artists;
}
public List<MediaFile> getAlbums() {
return albums;
}
public void setAlbums(List<MediaFile> albums) {
this.albums = albums;
}
public List<MediaFile> getSongs() {
return songs;
}
public void setSongs(List<MediaFile> songs) {
this.songs = songs;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public boolean isPartyModeEnabled() {
return partyModeEnabled;
}
public void setPartyModeEnabled(boolean partyModeEnabled) {
this.partyModeEnabled = partyModeEnabled;
}
public Player getPlayer() {
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public static class Match {
private MediaFile mediaFile;
private String title;
private String album;
private String artist;
public Match(MediaFile mediaFile, String title, String album, String artist) {
this.mediaFile = mediaFile;
this.title = title;
this.album = album;
this.artist = artist;
}
public MediaFile getMediaFile() {
return mediaFile;
}
public String getTitle() {
return title;
}
public String getAlbum() {
return album;
}
public String getArtist() {
return artist;
}
}
}
@@ -0,0 +1,301 @@
/*
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.command;
import org.libresonic.player.controller.UserSettingsController;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.TranscodeScheme;
import org.libresonic.player.domain.User;
import java.util.List;
/**
* Command used in {@link UserSettingsController}.
*
* @author Sindre Mehus
*/
public class UserSettingsCommand {
private String username;
private boolean isAdminRole;
private boolean isDownloadRole;
private boolean isUploadRole;
private boolean isCoverArtRole;
private boolean isCommentRole;
private boolean isPodcastRole;
private boolean isStreamRole;
private boolean isJukeboxRole;
private boolean isSettingsRole;
private boolean isShareRole;
private List<User> users;
private boolean isAdmin;
private boolean isPasswordChange;
private boolean isNewUser;
private boolean isDeleteUser;
private String password;
private String confirmPassword;
private String email;
private boolean isLdapAuthenticated;
private boolean isLdapEnabled;
private List<MusicFolder> allMusicFolders;
private int[] allowedMusicFolderIds;
private String transcodeSchemeName;
private EnumHolder[] transcodeSchemeHolders;
private boolean transcodingSupported;
private String transcodeDirectory;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public boolean isAdminRole() {
return isAdminRole;
}
public void setAdminRole(boolean adminRole) {
isAdminRole = adminRole;
}
public boolean isDownloadRole() {
return isDownloadRole;
}
public void setDownloadRole(boolean downloadRole) {
isDownloadRole = downloadRole;
}
public boolean isUploadRole() {
return isUploadRole;
}
public void setUploadRole(boolean uploadRole) {
isUploadRole = uploadRole;
}
public boolean isCoverArtRole() {
return isCoverArtRole;
}
public void setCoverArtRole(boolean coverArtRole) {
isCoverArtRole = coverArtRole;
}
public boolean isCommentRole() {
return isCommentRole;
}
public void setCommentRole(boolean commentRole) {
isCommentRole = commentRole;
}
public boolean isPodcastRole() {
return isPodcastRole;
}
public void setPodcastRole(boolean podcastRole) {
isPodcastRole = podcastRole;
}
public boolean isStreamRole() {
return isStreamRole;
}
public void setStreamRole(boolean streamRole) {
isStreamRole = streamRole;
}
public boolean isJukeboxRole() {
return isJukeboxRole;
}
public void setJukeboxRole(boolean jukeboxRole) {
isJukeboxRole = jukeboxRole;
}
public boolean isSettingsRole() {
return isSettingsRole;
}
public void setSettingsRole(boolean settingsRole) {
isSettingsRole = settingsRole;
}
public boolean isShareRole() {
return isShareRole;
}
public void setShareRole(boolean shareRole) {
isShareRole = shareRole;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean admin) {
isAdmin = admin;
}
public boolean isPasswordChange() {
return isPasswordChange;
}
public void setPasswordChange(boolean passwordChange) {
isPasswordChange = passwordChange;
}
public boolean isNewUser() {
return isNewUser;
}
public void setNewUser(boolean isNewUser) {
this.isNewUser = isNewUser;
}
public boolean isDeleteUser() {
return isDeleteUser;
}
public void setDeleteUser(boolean deleteUser) {
this.isDeleteUser = deleteUser;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isLdapAuthenticated() {
return isLdapAuthenticated;
}
public void setLdapAuthenticated(boolean ldapAuthenticated) {
isLdapAuthenticated = ldapAuthenticated;
}
public boolean isLdapEnabled() {
return isLdapEnabled;
}
public void setLdapEnabled(boolean ldapEnabled) {
isLdapEnabled = ldapEnabled;
}
public List<MusicFolder> getAllMusicFolders() {
return allMusicFolders;
}
public void setAllMusicFolders(List<MusicFolder> allMusicFolders) {
this.allMusicFolders = allMusicFolders;
}
public int[] getAllowedMusicFolderIds() {
return allowedMusicFolderIds;
}
public void setAllowedMusicFolderIds(int[] allowedMusicFolderIds) {
this.allowedMusicFolderIds = allowedMusicFolderIds;
}
public String getTranscodeSchemeName() {
return transcodeSchemeName;
}
public void setTranscodeSchemeName(String transcodeSchemeName) {
this.transcodeSchemeName = transcodeSchemeName;
}
public EnumHolder[] getTranscodeSchemeHolders() {
return transcodeSchemeHolders;
}
public void setTranscodeSchemes(TranscodeScheme[] transcodeSchemes) {
transcodeSchemeHolders = new EnumHolder[transcodeSchemes.length];
for (int i = 0; i < transcodeSchemes.length; i++) {
TranscodeScheme scheme = transcodeSchemes[i];
transcodeSchemeHolders[i] = new EnumHolder(scheme.name(), scheme.toString());
}
}
public boolean isTranscodingSupported() {
return transcodingSupported;
}
public void setTranscodingSupported(boolean transcodingSupported) {
this.transcodingSupported = transcodingSupported;
}
public String getTranscodeDirectory() {
return transcodeDirectory;
}
public void setTranscodeDirectory(String transcodeDirectory) {
this.transcodeDirectory = transcodeDirectory;
}
public void setUser(User user) {
username = user == null ? null : user.getUsername();
isAdminRole = user != null && user.isAdminRole();
isDownloadRole = user != null && user.isDownloadRole();
isUploadRole = user != null && user.isUploadRole();
isCoverArtRole = user != null && user.isCoverArtRole();
isCommentRole = user != null && user.isCommentRole();
isPodcastRole = user != null && user.isPodcastRole();
isStreamRole = user != null && user.isStreamRole();
isJukeboxRole = user != null && user.isJukeboxRole();
isSettingsRole = user != null && user.isSettingsRole();
isShareRole = user != null && user.isShareRole();
isLdapAuthenticated = user != null && user.isLdapAuthenticated();
setNewUser(false);
}
}
@@ -0,0 +1,61 @@
/*
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.controller;
import org.springframework.ui.context.Theme;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.util.Locale;
/**
* Abstract super class for controllers which generate charts.
*
* @author Sindre Mehus
*/
public abstract class AbstractChartController {
/**
* Returns the chart background color for the current theme.
* @param request The servlet request.
* @return The chart background color.
*/
protected Color getBackground(HttpServletRequest request) {
return getColor("backgroundColor", request);
}
/**
* Returns the chart foreground color for the current theme.
* @param request The servlet request.
* @return The chart foreground color.
*/
protected Color getForeground(HttpServletRequest request) {
return getColor("textColor", request);
}
private Color getColor(String code, HttpServletRequest request) {
Theme theme = RequestContextUtils.getTheme(request);
Locale locale = RequestContextUtils.getLocale(request);
String colorHex = theme.getMessageSource().getMessage(code, new Object[0], locale);
return new Color(Integer.parseInt(colorHex, 16));
}
}
@@ -0,0 +1,29 @@
package org.libresonic.player.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/accessDenied")
public class AccessDeniedController {
private static final Logger LOG = LoggerFactory.getLogger(AccessDeniedController.class);
@RequestMapping(method = {RequestMethod.GET})
public ModelAndView accessDenied(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("accessDenied");
}
}
@@ -0,0 +1,106 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.AdvancedSettingsCommand;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* Controller for the page used to administrate advanced settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/advancedSettings")
public class AdvancedSettingsController {
@Autowired
private SettingsService settingsService;
// TODO replace with @GetMapping in Spring 4
@RequestMapping(method = RequestMethod.GET)
protected String formBackingObject(Model model) throws Exception {
AdvancedSettingsCommand command = new AdvancedSettingsCommand();
command.setDownloadLimit(String.valueOf(settingsService.getDownloadBitrateLimit()));
command.setUploadLimit(String.valueOf(settingsService.getUploadBitrateLimit()));
command.setLdapEnabled(settingsService.isLdapEnabled());
command.setLdapUrl(settingsService.getLdapUrl());
command.setLdapSearchFilter(settingsService.getLdapSearchFilter());
command.setLdapManagerDn(settingsService.getLdapManagerDn());
command.setLdapAutoShadowing(settingsService.isLdapAutoShadowing());
command.setBrand(settingsService.getBrand());
command.setSmtpServer(settingsService.getSmtpServer());
command.setSmtpEncryption(settingsService.getSmtpEncryption());
command.setSmtpPort(settingsService.getSmtpPort());
command.setSmtpUser(settingsService.getSmtpUser());
command.setSmtpFrom(settingsService.getSmtpFrom());
model.addAttribute("command", command);
return "advancedSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute AdvancedSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
redirectAttributes.addFlashAttribute("settings_reload", false);
redirectAttributes.addFlashAttribute("settings_toast", true);
try {
settingsService.setDownloadBitrateLimit(Long.parseLong(command.getDownloadLimit()));
} catch (NumberFormatException x) { /* Intentionally ignored. */ }
try {
settingsService.setUploadBitrateLimit(Long.parseLong(command.getUploadLimit()));
} catch (NumberFormatException x) { /* Intentionally ignored. */ }
settingsService.setLdapEnabled(command.isLdapEnabled());
settingsService.setLdapUrl(command.getLdapUrl());
settingsService.setLdapSearchFilter(command.getLdapSearchFilter());
settingsService.setLdapManagerDn(command.getLdapManagerDn());
settingsService.setLdapAutoShadowing(command.isLdapAutoShadowing());
if (StringUtils.isNotEmpty(command.getLdapManagerPassword())) {
settingsService.setLdapManagerPassword(command.getLdapManagerPassword());
}
settingsService.setSmtpServer(command.getSmtpServer());
settingsService.setSmtpEncryption(command.getSmtpEncryption());
settingsService.setSmtpPort(command.getSmtpPort());
settingsService.setSmtpUser(command.getSmtpUser());
settingsService.setSmtpFrom(command.getSmtpFrom());
if (StringUtils.isNotEmpty(command.getSmtpPassword())) {
settingsService.setSmtpPassword(command.getSmtpPassword());
}
settingsService.save();
return "redirect:advancedSettings.view";
}
}
@@ -0,0 +1,45 @@
/*
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.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller for the page which forwards to allmusic.com.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/allmusic")
public class AllmusicController {
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView result = new ModelAndView();
result.addObject("album", request.getParameter("album"));
return result;
}
}
@@ -0,0 +1,67 @@
/*
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 2013 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.RandomStringUtils;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class AutoCoverDemo {
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.add(new AlbumComponent(110, 110));
panel.add(new AlbumComponent(150, 150));
panel.add(new AlbumComponent(200, 200));
panel.add(new AlbumComponent(300, 300));
panel.add(new AlbumComponent(400, 240));
panel.add(new AlbumComponent(240, 400));
panel.setBackground(Color.LIGHT_GRAY);
frame.add(panel);
frame.setSize(1000, 800);
frame.setVisible(true);
}
private static class AlbumComponent extends JComponent {
private final int width;
private final int height;
public AlbumComponent(int width, int height) {
this.width = width;
this.height = height;
setPreferredSize(new Dimension(width, height));
}
@Override
protected void paintComponent(Graphics g) {
String key = RandomStringUtils.random(5);
new CoverArtController.AutoCover((Graphics2D) g, key, "Artist with a very long name", "Album", width, height).paintCover();
}
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller which produces avatar images.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/avatar")
public class AvatarController implements LastModified {
@Autowired
private SettingsService settingsService;
public long getLastModified(HttpServletRequest request) {
Avatar avatar = getAvatar(request);
long result = avatar == null ? -1L : avatar.getCreatedDate().getTime();
String username = request.getParameter("username");
if (username != null) {
UserSettings userSettings = settingsService.getUserSettings(username);
result = Math.max(result, userSettings.getChanged().getTime());
}
return result;
}
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Avatar avatar = getAvatar(request);
if (avatar == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
response.setContentType(avatar.getMimeType());
response.getOutputStream().write(avatar.getData());
return null;
}
private Avatar getAvatar(HttpServletRequest request) {
String id = request.getParameter("id");
boolean forceCustom = ServletRequestUtils.getBooleanParameter(request, "forceCustom", false);
if (id != null) {
return settingsService.getSystemAvatar(Integer.parseInt(id));
}
String username = request.getParameter("username");
if (username == null) {
return null;
}
UserSettings userSettings = settingsService.getUserSettings(username);
if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM || forceCustom) {
return settingsService.getCustomAvatar(username);
}
if(userSettings.getAvatarScheme() == AvatarScheme.NONE) {
return null;
}
return settingsService.getSystemAvatar(userSettings.getSystemAvatarId());
}
}
@@ -0,0 +1,142 @@
/*
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.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller which receives uploaded avatar images.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/avatarUpload")
public class AvatarUploadController {
private static final Logger LOG = LoggerFactory.getLogger(AvatarUploadController.class);
private static final int MAX_AVATAR_SIZE = 64;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.POST)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
String username = securityService.getCurrentUsername(request);
// Check that we have a file upload request.
if (!ServletFileUpload.isMultipartContent(request)) {
throw new Exception("Illegal request.");
}
Map<String, Object> map = new HashMap<String, Object>();
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
// Look for file items.
for (Object o : items) {
FileItem item = (FileItem) o;
if (!item.isFormField()) {
String fileName = item.getName();
byte[] data = item.get();
if (StringUtils.isNotBlank(fileName) && data.length > 0) {
createAvatar(fileName, data, username, map);
} else {
map.put("error", new Exception("Missing file."));
LOG.warn("Failed to upload personal image. No file specified.");
}
break;
}
}
map.put("username", username);
map.put("avatar", settingsService.getCustomAvatar(username));
return new ModelAndView("avatarUploadResult","model",map);
}
private void createAvatar(String fileName, byte[] data, String username, Map<String, Object> map) throws IOException {
BufferedImage image;
try {
image = ImageIO.read(new ByteArrayInputStream(data));
if (image == null) {
throw new Exception("Failed to decode incoming image: " + fileName + " (" + data.length + " bytes).");
}
int width = image.getWidth();
int height = image.getHeight();
String mimeType = StringUtil.getMimeType(FilenameUtils.getExtension(fileName));
// Scale down image if necessary.
if (width > MAX_AVATAR_SIZE || height > MAX_AVATAR_SIZE) {
double scaleFactor = (double) MAX_AVATAR_SIZE / (double) Math.max(width, height);
height = (int) (height * scaleFactor);
width = (int) (width * scaleFactor);
image = CoverArtController.scale(image, width, height);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "jpeg", out);
data = out.toByteArray();
mimeType = StringUtil.getMimeType("jpeg");
map.put("resized", true);
}
Avatar avatar = new Avatar(0, fileName, new Date(), mimeType, width, height, data);
settingsService.setCustomAvatar(avatar, username);
LOG.info("Created avatar '" + fileName + "' (" + data.length + " bytes) for user " + username);
} catch (Exception x) {
LOG.warn("Failed to upload personal image: " + x, x);
map.put("error", x);
}
}
}
@@ -0,0 +1,74 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for changing cover art.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/changeCoverArt")
public class ChangeCoverArtController {
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
String artist = request.getParameter("artist");
String album = request.getParameter("album");
MediaFile dir = mediaFileService.getMediaFile(id);
if (StringUtils.isBlank(artist)) {
artist = dir.getArtist();
}
if (StringUtils.isBlank(album)) {
album = dir.getAlbumName();
}
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("artist", artist);
map.put("album", album);
return new ModelAndView("changeCoverArt","model",map);
}
}
@@ -0,0 +1,25 @@
package org.libresonic.player.controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
/**
* This class has been created to refactor code previously present
* in the MultiController.
*/
public class ControllerUtils {
public static String extractMatched(final HttpServletRequest request){
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
return apm.extractPathWithinPattern(bestMatchPattern, path);
}
}
@@ -0,0 +1,699 @@
/*
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.controller;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.dao.AlbumDao;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.service.metadata.JaudiotaggerParser;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
/**
* Controller which produces cover art images.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/coverArt", "/ext/coverArt"})
public class CoverArtController implements LastModified {
public static final String ALBUM_COVERART_PREFIX = "al-";
public static final String ARTIST_COVERART_PREFIX = "ar-";
public static final String PLAYLIST_COVERART_PREFIX = "pl-";
public static final String PODCAST_COVERART_PREFIX = "pod-";
private static final Logger LOG = LoggerFactory.getLogger(CoverArtController.class);
@Autowired
private MediaFileService mediaFileService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlaylistService playlistService;
@Autowired
private PodcastService podcastService;
@Autowired
private ArtistDao artistDao;
@Autowired
private AlbumDao albumDao;
private Semaphore semaphore;
@PostConstruct
public void init() {
semaphore = new Semaphore(settingsService.getCoverArtConcurrency());
}
public long getLastModified(HttpServletRequest request) {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
// LOG.info("getLastModified - " + coverArtRequest + ": " + new Date(result));
return coverArtRequest.lastModified();
}
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
// LOG.info("handleRequest - " + coverArtRequest);
Integer size = ServletRequestUtils.getIntParameter(request, "size");
// Send fallback image if no ID is given. (No need to cache it, since it will be cached in browser.)
if (coverArtRequest == null) {
sendFallback(size, response);
return null;
}
// Optimize if no scaling is required.
if (size == null && coverArtRequest.getCoverArt() != null) {
// LOG.info("sendUnscaled - " + coverArtRequest);
sendUnscaled(coverArtRequest, response);
return null;
}
// Send cached image, creating it if necessary.
if (size == null) {
size = CoverArtScheme.LARGE.getSize() * 2;
}
try {
File cachedImage = getCachedImage(coverArtRequest, size);
sendImage(cachedImage, response);
} catch (IOException e) {
sendFallback(size, response);
}
return null;
}
private CoverArtRequest createCoverArtRequest(HttpServletRequest request) {
String id = request.getParameter("id");
if (id == null) {
return null;
}
if (id.startsWith(ALBUM_COVERART_PREFIX)) {
return createAlbumCoverArtRequest(Integer.valueOf(id.replace(ALBUM_COVERART_PREFIX, "")));
}
if (id.startsWith(ARTIST_COVERART_PREFIX)) {
return createArtistCoverArtRequest(Integer.valueOf(id.replace(ARTIST_COVERART_PREFIX, "")));
}
if (id.startsWith(PLAYLIST_COVERART_PREFIX)) {
return createPlaylistCoverArtRequest(Integer.valueOf(id.replace(PLAYLIST_COVERART_PREFIX, "")));
}
if (id.startsWith(PODCAST_COVERART_PREFIX)) {
return createPodcastCoverArtRequest(Integer.valueOf(id.replace(PODCAST_COVERART_PREFIX, "")), request);
}
return createMediaFileCoverArtRequest(Integer.valueOf(id), request);
}
private CoverArtRequest createAlbumCoverArtRequest(int id) {
Album album = albumDao.getAlbum(id);
return album == null ? null : new AlbumCoverArtRequest(album);
}
private CoverArtRequest createArtistCoverArtRequest(int id) {
Artist artist = artistDao.getArtist(id);
return artist == null ? null : new ArtistCoverArtRequest(artist);
}
private PlaylistCoverArtRequest createPlaylistCoverArtRequest(int id) {
Playlist playlist = playlistService.getPlaylist(id);
return playlist == null ? null : new PlaylistCoverArtRequest(playlist);
}
private CoverArtRequest createPodcastCoverArtRequest(int id, HttpServletRequest request) {
PodcastChannel channel = podcastService.getChannel(id);
if (channel == null) {
return null;
}
if (channel.getMediaFileId() == null) {
return new PodcastCoverArtRequest(channel);
}
return createMediaFileCoverArtRequest(channel.getMediaFileId(), request);
}
private CoverArtRequest createMediaFileCoverArtRequest(int id, HttpServletRequest request) {
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile == null) {
return null;
}
if (mediaFile.isVideo()) {
int offset = ServletRequestUtils.getIntParameter(request, "offset", 60);
return new VideoCoverArtRequest(mediaFile, offset);
}
return new MediaFileCoverArtRequest(mediaFile);
}
private void sendImage(File file, HttpServletResponse response) throws IOException {
response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));
InputStream in = new FileInputStream(file);
try {
IOUtils.copy(in, response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private void sendFallback(Integer size, HttpServletResponse response) throws IOException {
if (response.getContentType() == null) {
response.setContentType(StringUtil.getMimeType("jpeg"));
}
InputStream in = null;
try {
in = getClass().getResourceAsStream("default_cover.jpg");
BufferedImage image = ImageIO.read(in);
if (size != null) {
image = scale(image, size, size);
}
ImageIO.write(image, "jpeg", response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private void sendUnscaled(CoverArtRequest coverArtRequest, HttpServletResponse response) throws IOException {
File file = coverArtRequest.getCoverArt();
JaudiotaggerParser parser = new JaudiotaggerParser();
if (!parser.isApplicable(file)) {
response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));
}
InputStream in = null;
try {
in = getImageInputStream(file);
IOUtils.copy(in, response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private File getCachedImage(CoverArtRequest request, int size) throws IOException {
String hash = DigestUtils.md5Hex(request.getKey());
String encoding = request.getCoverArt() != null ? "jpeg" : "png";
File cachedImage = new File(getImageCacheDirectory(size), hash + "." + encoding);
// Synchronize to avoid concurrent writing to the same file.
synchronized (hash.intern()) {
// Is cache missing or obsolete?
if (!cachedImage.exists() || request.lastModified() > cachedImage.lastModified()) {
// LOG.info("Cache MISS - " + request + " (" + size + ")");
OutputStream out = null;
try {
semaphore.acquire();
BufferedImage image = request.createImage(size);
if (image == null) {
throw new Exception("Unable to decode image.");
}
out = new FileOutputStream(cachedImage);
ImageIO.write(image, encoding, out);
} catch (Throwable x) {
// Delete corrupt (probably empty) thumbnail cache.
LOG.warn("Failed to create thumbnail for " + request, x);
IOUtils.closeQuietly(out);
cachedImage.delete();
throw new IOException("Failed to create thumbnail for " + request + ". " + x.getMessage());
} finally {
semaphore.release();
IOUtils.closeQuietly(out);
}
} else {
// LOG.info("Cache HIT - " + request + " (" + size + ")");
}
return cachedImage;
}
}
/**
* Returns an input stream to the image in the given file. If the file is an audio file,
* the embedded album art is returned.
*/
private InputStream getImageInputStream(File file) throws IOException {
JaudiotaggerParser parser = new JaudiotaggerParser();
if (parser.isApplicable(file)) {
MediaFile mediaFile = mediaFileService.getMediaFile(file);
return new ByteArrayInputStream(parser.getImageData(mediaFile));
} else {
return new FileInputStream(file);
}
}
private InputStream getImageInputStreamForVideo(MediaFile mediaFile, int width, int height, int offset) throws Exception {
VideoTranscodingSettings videoSettings = new VideoTranscodingSettings(width, height, offset, 0, false);
TranscodingService.Parameters parameters = new TranscodingService.Parameters(mediaFile, videoSettings);
String command = settingsService.getVideoImageCommand();
parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false));
return transcodingService.getTranscodedInputStream(parameters);
}
private synchronized File getImageCacheDirectory(int size) {
File dir = new File(SettingsService.getLibresonicHome(), "thumbs");
dir = new File(dir, String.valueOf(size));
if (!dir.exists()) {
if (dir.mkdirs()) {
LOG.info("Created thumbnail cache " + dir);
} else {
LOG.error("Failed to create thumbnail cache " + dir);
}
}
return dir;
}
public static BufferedImage scale(BufferedImage image, int width, int height) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage thumb = image;
// For optimal results, use step by step bilinear resampling - halfing the size at each step.
do {
w /= 2;
h /= 2;
if (w < width) {
w = width;
}
if (h < height) {
h = height;
}
BufferedImage temp = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (w != width);
return thumb;
}
private abstract class CoverArtRequest {
protected File coverArt;
private CoverArtRequest() {
}
private CoverArtRequest(String coverArtPath) {
this.coverArt = coverArtPath == null ? null : new File(coverArtPath);
}
private File getCoverArt() {
return coverArt;
}
public abstract String getKey();
public abstract long lastModified();
public BufferedImage createImage(int size) {
if (coverArt != null) {
InputStream in = null;
try {
in = getImageInputStream(coverArt);
return scale(ImageIO.read(in), size, size);
} catch (Throwable x) {
LOG.warn("Failed to process cover art " + coverArt + ": " + x, x);
} finally {
IOUtils.closeQuietly(in);
}
}
return createAutoCover(size, size);
}
protected BufferedImage createAutoCover(int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
AutoCover autoCover = new AutoCover(graphics, getKey(), getArtist(), getAlbum(), width, height);
autoCover.paintCover();
graphics.dispose();
return image;
}
public abstract String getAlbum();
public abstract String getArtist();
}
private class ArtistCoverArtRequest extends CoverArtRequest {
private final Artist artist;
private ArtistCoverArtRequest(Artist artist) {
super(artist.getCoverArtPath());
this.artist = artist;
}
@Override
public String getKey() {
return artist.getCoverArtPath() != null ? artist.getCoverArtPath() : (ARTIST_COVERART_PREFIX + artist.getId());
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : artist.getLastScanned().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return artist.getName();
}
@Override
public String toString() {
return "Artist " + artist.getId() + " - " + artist.getName();
}
}
private class AlbumCoverArtRequest extends CoverArtRequest {
private final Album album;
private AlbumCoverArtRequest(Album album) {
super(album.getCoverArtPath());
this.album = album;
}
@Override
public String getKey() {
return album.getCoverArtPath() != null ? album.getCoverArtPath() : (ALBUM_COVERART_PREFIX + album.getId());
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : album.getLastScanned().getTime();
}
@Override
public String getAlbum() {
return album.getName();
}
@Override
public String getArtist() {
return album.getArtist();
}
@Override
public String toString() {
return "Album " + album.getId() + " - " + album.getName();
}
}
private class PlaylistCoverArtRequest extends CoverArtRequest {
private final Playlist playlist;
private PlaylistCoverArtRequest(Playlist playlist) {
super(null);
this.playlist = playlist;
}
@Override
public String getKey() {
return PLAYLIST_COVERART_PREFIX + playlist.getId();
}
@Override
public long lastModified() {
return playlist.getChanged().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return playlist.getName();
}
@Override
public String toString() {
return "Playlist " + playlist.getId() + " - " + playlist.getName();
}
@Override
public BufferedImage createImage(int size) {
List<MediaFile> albums = getRepresentativeAlbums();
if (albums.isEmpty()) {
return createAutoCover(size, size);
}
if (albums.size() < 4) {
return new MediaFileCoverArtRequest(albums.get(0)).createImage(size);
}
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
int half = size / 2;
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(0)).createImage(half), null, 0, 0);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(1)).createImage(half), null, half, 0);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(2)).createImage(half), null, 0, half);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(3)).createImage(half), null, half, half);
graphics.dispose();
return image;
}
private List<MediaFile> getRepresentativeAlbums() {
Set<MediaFile> albums = new LinkedHashSet<MediaFile>();
for (MediaFile song : playlistService.getFilesInPlaylist(playlist.getId())) {
MediaFile album = mediaFileService.getParentOf(song);
if (album != null && !mediaFileService.isRoot(album)) {
albums.add(album);
}
}
return new ArrayList<>(albums);
}
}
private class PodcastCoverArtRequest extends CoverArtRequest {
private final PodcastChannel channel;
PodcastCoverArtRequest(PodcastChannel channel) {
this.channel = channel;
}
@Override
public String getKey() {
return PODCAST_COVERART_PREFIX + channel.getId();
}
@Override
public long lastModified() {
return -1;
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return channel.getTitle() != null ? channel.getTitle() : channel.getUrl();
}
}
private class MediaFileCoverArtRequest extends CoverArtRequest {
private final MediaFile mediaFile;
private final MediaFile dir;
private MediaFileCoverArtRequest(MediaFile mediaFile) {
this.mediaFile = mediaFile;
dir = mediaFile.isDirectory() ? mediaFile : mediaFileService.getParentOf(mediaFile);
coverArt = mediaFileService.getCoverArt(mediaFile);
}
@Override
public String getKey() {
return coverArt != null ? coverArt.getPath() : dir.getPath();
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : dir.getChanged().getTime();
}
@Override
public String getAlbum() {
return dir.getName();
}
@Override
public String getArtist() {
return dir.getAlbumArtist() != null ? dir.getAlbumArtist() : dir.getArtist();
}
@Override
public String toString() {
return "Media file " + mediaFile.getId() + " - " + mediaFile;
}
}
private class VideoCoverArtRequest extends CoverArtRequest {
private final MediaFile mediaFile;
private final int offset;
private VideoCoverArtRequest(MediaFile mediaFile, int offset) {
this.mediaFile = mediaFile;
this.offset = offset;
}
@Override
public BufferedImage createImage(int size) {
int height;
height = size;
int width = height * 16 / 9;
InputStream in = null;
try {
in = getImageInputStreamForVideo(mediaFile, width, height, offset);
BufferedImage result = ImageIO.read(in);
if (result == null) {
throw new NullPointerException();
}
return result;
} catch (Throwable x) {
LOG.warn("Failed to process cover art for " + mediaFile + ": " + x, x);
} finally {
IOUtils.closeQuietly(in);
}
return createAutoCover(width, height);
}
@Override
public String getKey() {
return mediaFile.getPath() + "/" + offset;
}
@Override
public long lastModified() {
return mediaFile.getChanged().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return mediaFile.getName();
}
@Override
public String toString() {
return "Video file " + mediaFile.getId() + " - " + mediaFile;
}
}
static class AutoCover {
private final static int[] COLORS = {0x33B5E5, 0xAA66CC, 0x99CC00, 0xFFBB33, 0xFF4444};
private final Graphics2D graphics;
private final String artist;
private final String album;
private final int width;
private final int height;
private final Color color;
AutoCover(Graphics2D graphics, String key, String artist, String album, int width, int height) {
this.graphics = graphics;
this.artist = artist;
this.album = album;
this.width = width;
this.height = height;
int hash = key.hashCode();
int rgb = COLORS[Math.abs(hash) % COLORS.length];
this.color = new Color(rgb);
}
public void paintCover() {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.setPaint(color);
graphics.fillRect(0, 0, width, height);
int y = height * 2 / 3;
graphics.setPaint(new GradientPaint(0, y, new Color(82, 82, 82), 0, height, Color.BLACK));
graphics.fillRect(0, y, width, height / 3);
graphics.setPaint(Color.WHITE);
float fontSize = 3.0f + height * 0.07f;
Font font = new Font(Font.SANS_SERIF, Font.BOLD, (int) fontSize);
graphics.setFont(font);
if (album != null) {
graphics.drawString(album, width * 0.05f, height * 0.6f);
}
if (artist != null) {
graphics.drawString(artist, width * 0.05f, height * 0.8f);
}
int borderWidth = height / 50;
graphics.fillRect(0, 0, borderWidth, height);
graphics.fillRect(width - borderWidth, 0, height - borderWidth, height);
graphics.fillRect(0, 0, width, borderWidth);
graphics.fillRect(0, height - borderWidth, width, height);
}
}
}
@@ -0,0 +1,70 @@
/*
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.controller;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.libresonic.player.dao.DaoHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the DB admin page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/db")
public class DBController {
@Autowired
private DaoHelper daoHelper;
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String query = request.getParameter("query");
if (query != null) {
map.put("query", query);
try {
List<?> result = daoHelper.getJdbcTemplate().query(query, new ColumnMapRowMapper());
map.put("result", result);
} catch (DataAccessException x) {
map.put("error", ExceptionUtils.getRootCause(x).getMessage());
}
}
return new ModelAndView("db","model",map);
}
}
@@ -0,0 +1,95 @@
/*
* 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 2013 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.UPnPService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the UPnP/DLNA server settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/dlnaSettings")
public class DLNASettingsController {
@Autowired
private UPnPService upnpService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String handleGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("dlnaEnabled", settingsService.isDlnaEnabled());
map.put("dlnaServerName", settingsService.getDlnaServerName());
map.put("dlnaBaseLANURL", settingsService.getDlnaBaseLANURL());
model.addAttribute("model", map);
return "dlnaSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String handlePost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:dlnaSettings.view";
}
private void handleParameters(HttpServletRequest request) {
boolean dlnaEnabled = ServletRequestUtils.getBooleanParameter(request, "dlnaEnabled", false);
String dlnaServerName = StringUtils.trimToNull(request.getParameter("dlnaServerName"));
String dlnaBaseLANURL = StringUtils.trimToNull(request.getParameter("dlnaBaseLANURL"));
if (dlnaServerName == null) {
dlnaServerName = "Libresonic";
}
upnpService.setMediaServerEnabled(false);
settingsService.setDlnaEnabled(dlnaEnabled);
settingsService.setDlnaServerName(dlnaServerName);
settingsService.setDlnaBaseLANURL(dlnaBaseLANURL);
settingsService.save();
upnpService.setMediaServerEnabled(dlnaEnabled);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setUpnpService(UPnPService upnpService) {
this.upnpService = upnpService;
}
}
@@ -0,0 +1,94 @@
/*
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.controller;
import org.libresonic.player.command.DatabaseSettingsCommand;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.spring.DataSourceConfigType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/databaseSettings")
public class DatabaseSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "databaseSettings";
}
@ModelAttribute
protected void formBackingObject(Model model) throws Exception {
DatabaseSettingsCommand command = new DatabaseSettingsCommand();
command.setConfigType(settingsService.getDatabaseConfigType());
command.setEmbedDriver(settingsService.getDatabaseConfigEmbedDriver());
command.setEmbedPassword(settingsService.getDatabaseConfigEmbedPassword());
command.setEmbedUrl(settingsService.getDatabaseConfigEmbedUrl());
command.setEmbedUsername(settingsService.getDatabaseConfigEmbedUsername());
command.setJNDIName(settingsService.getDatabaseConfigJNDIName());
command.setMysqlVarcharMaxlength(settingsService.getDatabaseMysqlVarcharMaxlength());
command.setUsertableQuote(settingsService.getDatabaseUsertableQuote());
model.addAttribute("command", command);
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(@ModelAttribute("command") @Validated DatabaseSettingsCommand command,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) throws Exception {
if (!bindingResult.hasErrors()) {
settingsService.resetDatabaseToDefault();
settingsService.setDatabaseConfigType(command.getConfigType());
switch(command.getConfigType()) {
case EMBED:
settingsService.setDatabaseConfigEmbedDriver(command.getEmbedDriver());
settingsService.setDatabaseConfigEmbedPassword(command.getEmbedPassword());
settingsService.setDatabaseConfigEmbedUrl(command.getEmbedUrl());
settingsService.setDatabaseConfigEmbedUsername(command.getEmbedUsername());
break;
case JNDI:
settingsService.setDatabaseConfigJNDIName(command.getJNDIName());
break;
case LEGACY:
default:
break;
}
if(command.getConfigType() != DataSourceConfigType.LEGACY) {
settingsService.setDatabaseMysqlVarcharMaxlength(command.getMysqlVarcharMaxlength());
settingsService.setDatabaseUsertableQuote(command.getUsertableQuote());
}
redirectAttributes.addFlashAttribute("settings_toast", true);
settingsService.save();
return "redirect:databaseSettings.view";
} else {
return "databaseSettings.view";
}
}
}
@@ -0,0 +1,385 @@
/*
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.controller;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.io.RangeOutputStream;
import org.libresonic.player.service.*;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.HttpRange;
import org.libresonic.player.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* A controller used for downloading files to a remote client. If the requested path refers to a file, the
* given file is downloaded. If the requested path refers to a directory, the entire directory (including
* sub-directories) are downloaded as an uncompressed zip-file.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/download")
public class DownloadController implements LastModified {
private static final Logger LOG = LoggerFactory.getLogger(DownloadController.class);
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaFileService mediaFileService;
public long getLastModified(HttpServletRequest request) {
try {
MediaFile mediaFile = getMediaFile(request);
if (mediaFile == null || mediaFile.isDirectory() || mediaFile.getChanged() == null) {
return -1;
}
return mediaFile.getChanged().getTime();
} catch (ServletRequestBindingException e) {
return -1;
}
}
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
TransferStatus status = null;
try {
status = statusService.createDownloadStatus(playerService.getPlayer(request, response, false, false));
MediaFile mediaFile = getMediaFile(request);
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
String playerId = request.getParameter("player");
int[] indexes = request.getParameter("i") == null ? null : ServletRequestUtils.getIntParameters(request, "i");
if (mediaFile != null) {
response.setIntHeader("ETag", mediaFile.getId());
response.setHeader("Accept-Ranges", "bytes");
}
HttpRange range = HttpRange.valueOf(request.getHeader("Range"));
if (range != null) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
LOG.info("Got HTTP range: " + range);
}
if (mediaFile != null) {
if (!securityService.isFolderAccessAllowed(mediaFile, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + user.getUsername());
return null;
}
if (mediaFile.isFile()) {
downloadFile(response, status, mediaFile.getFile(), range);
} else {
List<MediaFile> children = mediaFileService.getChildrenOf(mediaFile, true, false, true);
String zipFileName = FilenameUtils.getBaseName(mediaFile.getPath()) + ".zip";
File coverArtFile = indexes == null ? mediaFile.getCoverArtFile() : null;
downloadFiles(response, status, children, indexes, coverArtFile, range, zipFileName);
}
} else if (playlistId != null) {
List<MediaFile> songs = playlistService.getFilesInPlaylist(playlistId);
Playlist playlist = playlistService.getPlaylist(playlistId);
downloadFiles(response, status, songs, null, null, range, playlist.getName() + ".zip");
} else if (playerId != null) {
Player player = playerService.getPlayerById(playerId);
PlayQueue playQueue = player.getPlayQueue();
playQueue.setName("Playlist");
downloadFiles(response, status, playQueue.getFiles(), indexes, null, range, "download.zip");
}
} finally {
if (status != null) {
statusService.removeDownloadStatus(status);
securityService.updateUserByteCounts(user, 0L, status.getBytesTransfered(), 0L);
}
}
return null;
}
private MediaFile getMediaFile(HttpServletRequest request) throws ServletRequestBindingException {
Integer id = ServletRequestUtils.getIntParameter(request, "id");
return id == null ? null : mediaFileService.getMediaFile(id);
}
/**
* Downloads a single file.
*
* @param response The HTTP response.
* @param status The download status.
* @param file The file to download.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void downloadFile(HttpServletResponse response, TransferStatus status, File file, HttpRange range) throws IOException {
LOG.info("Starting to download '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
status.setFile(file);
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodeAsRFC5987(file.getName()));
if (range == null) {
Util.setContentLength(response, file.length());
}
copyFileToStream(file, RangeOutputStream.wrap(response.getOutputStream(), range), status, range);
LOG.info("Downloaded '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
}
private String encodeAsRFC5987(String string) throws UnsupportedEncodingException {
byte[] stringAsByteArray = string.getBytes("UTF-8");
char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
byte[] attrChar = {'!', '#', '$', '&', '+', '-', '.', '^', '_', '`', '|', '~', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
StringBuilder sb = new StringBuilder();
for (byte b : stringAsByteArray) {
if (Arrays.binarySearch(attrChar, b) >= 0) {
sb.append((char) b);
} else {
sb.append('%');
sb.append(digits[0x0f & (b >>> 4)]);
sb.append(digits[b & 0x0f]);
}
}
return sb.toString();
}
/**
* Downloads the given files. The files are packed together in an
* uncompressed zip-file.
*
* @param response The HTTP response.
* @param status The download status.
* @param files The files to download.
* @param indexes Only download songs at these indexes. May be <code>null</code>.
* @param coverArtFile The cover art file to include, may be {@code null}.
* @param range The byte range, may be <code>null</code>.
* @param zipFileName The name of the resulting zip file. @throws IOException If an I/O error occurs.
*/
private void downloadFiles(HttpServletResponse response, TransferStatus status, List<MediaFile> files, int[] indexes, File coverArtFile, HttpRange range, String zipFileName) throws IOException {
if (indexes != null && indexes.length == 1) {
downloadFile(response, status, files.get(indexes[0]).getFile(), range);
return;
}
LOG.info("Starting to download '" + zipFileName + "' to " + status.getPlayer());
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodeAsRFC5987(zipFileName));
ZipOutputStream out = new ZipOutputStream(RangeOutputStream.wrap(response.getOutputStream(), range));
out.setMethod(ZipOutputStream.STORED); // No compression.
Set<MediaFile> filesToDownload = new HashSet<>();
if (indexes == null) {
filesToDownload.addAll(files);
} else {
for (int index : indexes) {
try {
filesToDownload.add(files.get(index));
} catch (IndexOutOfBoundsException x) { /* Ignored */}
}
}
for (MediaFile mediaFile : filesToDownload) {
zip(out, mediaFile.getParentFile(), mediaFile.getFile(), status, range);
}
if (coverArtFile != null && coverArtFile.exists()) {
zip(out, coverArtFile.getParentFile(), coverArtFile, status, range);
}
out.close();
LOG.info("Downloaded '" + zipFileName + "' to " + status.getPlayer());
}
/**
* Utility method for writing the content of a given file to a given output stream.
*
* @param file The file to copy.
* @param out The output stream to write to.
* @param status The download status.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void copyFileToStream(File file, OutputStream out, TransferStatus status, HttpRange range) throws IOException {
LOG.info("Downloading '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
final int bufferSize = 16 * 1024; // 16 Kbit
InputStream in = new BufferedInputStream(new FileInputStream(file), bufferSize);
try {
byte[] buf = new byte[bufferSize];
long bitrateLimit = 0;
long lastLimitCheck = 0;
while (true) {
long before = System.currentTimeMillis();
int n = in.read(buf);
if (n == -1) {
break;
}
out.write(buf, 0, n);
// Don't sleep if outside range.
if (range != null && !range.contains(status.getBytesSkipped() + status.getBytesTransfered())) {
status.addBytesSkipped(n);
continue;
}
status.addBytesTransfered(n);
long after = System.currentTimeMillis();
// Calculate bitrate limit every 5 seconds.
if (after - lastLimitCheck > 5000) {
bitrateLimit = 1024L * settingsService.getDownloadBitrateLimit() /
Math.max(1, statusService.getAllDownloadStatuses().size());
lastLimitCheck = after;
}
// Sleep for a while to throttle bitrate.
if (bitrateLimit != 0) {
long sleepTime = 8L * 1000 * bufferSize / bitrateLimit - (after - before);
if (sleepTime > 0L) {
try {
Thread.sleep(sleepTime);
} catch (Exception x) {
LOG.warn("Failed to sleep.", x);
}
}
}
}
} finally {
out.flush();
IOUtils.closeQuietly(in);
}
}
/**
* Writes a file or a directory structure to a zip output stream. File entries in the zip file are relative
* to the given root.
*
* @param out The zip output stream.
* @param root The root of the directory structure. Used to create path information in the zip file.
* @param file The file or directory to zip.
* @param status The download status.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void zip(ZipOutputStream out, File root, File file, TransferStatus status, HttpRange range) throws IOException {
// Exclude all hidden files starting with a "."
if (file.getName().startsWith(".")) {
return;
}
String zipName = file.getCanonicalPath().substring(root.getCanonicalPath().length() + 1);
if (file.isFile()) {
status.setFile(file);
ZipEntry zipEntry = new ZipEntry(zipName);
zipEntry.setSize(file.length());
zipEntry.setCompressedSize(file.length());
zipEntry.setCrc(computeCrc(file));
out.putNextEntry(zipEntry);
copyFileToStream(file, out, status, range);
out.closeEntry();
} else {
ZipEntry zipEntry = new ZipEntry(zipName + '/');
zipEntry.setSize(0);
zipEntry.setCompressedSize(0);
zipEntry.setCrc(0);
out.putNextEntry(zipEntry);
out.closeEntry();
File[] children = FileUtil.listFiles(file);
for (File child : children) {
zip(out, root, child, status, range);
}
}
}
/**
* Computes the CRC checksum for the given file.
*
* @param file The file to compute checksum for.
* @return A CRC32 checksum.
* @throws IOException If an I/O error occurs.
*/
private long computeCrc(File file) throws IOException {
CRC32 crc = new CRC32();
InputStream in = new FileInputStream(file);
try {
byte[] buf = new byte[8192];
int n = in.read(buf);
while (n != -1) {
crc.update(buf, 0, n);
n = in.read(buf);
}
} finally {
in.close();
}
return crc.getValue();
}
}
@@ -0,0 +1,196 @@
/*
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.controller;
import org.apache.commons.io.FilenameUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.metadata.JaudiotaggerParser;
import org.libresonic.player.service.metadata.MetaDataParser;
import org.libresonic.player.service.metadata.MetaDataParserFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the page used to edit MP3 tags.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/editTags")
public class EditTagsController {
@Autowired
private MetaDataParserFactory metaDataParserFactory;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
MediaFile dir = mediaFileService.getMediaFile(id);
List<MediaFile> files = mediaFileService.getChildrenOf(dir, true, false, true, false);
Map<String, Object> map = new HashMap<String, Object>();
if (!files.isEmpty()) {
map.put("defaultArtist", files.get(0).getArtist());
map.put("defaultAlbum", files.get(0).getAlbumName());
map.put("defaultYear", files.get(0).getYear());
map.put("defaultGenre", files.get(0).getGenre());
}
map.put("allGenres", JaudiotaggerParser.getID3V1Genres());
List<Song> songs = new ArrayList<Song>();
for (int i = 0; i < files.size(); i++) {
songs.add(createSong(files.get(i), i));
}
map.put("id", id);
map.put("songs", songs);
return new ModelAndView("editTags","model",map);
}
private Song createSong(MediaFile file, int index) {
MetaDataParser parser = metaDataParserFactory.getParser(file.getFile());
Song song = new Song();
song.setId(file.getId());
song.setFileName(FilenameUtils.getBaseName(file.getPath()));
song.setTrack(file.getTrackNumber());
song.setSuggestedTrack(index + 1);
song.setTitle(file.getTitle());
song.setSuggestedTitle(parser.guessTitle(file.getFile()));
song.setArtist(file.getArtist());
song.setAlbum(file.getAlbumName());
song.setYear(file.getYear());
song.setGenre(file.getGenre());
return song;
}
/**
* Contains information about a single song.
*/
public static class Song {
private int id;
private String fileName;
private Integer suggestedTrack;
private Integer track;
private String suggestedTitle;
private String title;
private String artist;
private String album;
private Integer year;
private String genre;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Integer getSuggestedTrack() {
return suggestedTrack;
}
public void setSuggestedTrack(Integer suggestedTrack) {
this.suggestedTrack = suggestedTrack;
}
public Integer getTrack() {
return track;
}
public void setTrack(Integer track) {
this.track = track;
}
public String getSuggestedTitle() {
return suggestedTitle;
}
public void setSuggestedTitle(String suggestedTitle) {
this.suggestedTitle = suggestedTitle;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
}
}
@@ -0,0 +1,52 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/exportPlaylist")
public class ExportPlayListController {
private static final Logger LOG = LoggerFactory.getLogger(ExportPlayListController.class);
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = { RequestMethod.GET })
public ModelAndView exportPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Playlist playlist = playlistService.getPlaylist(id);
if (!playlistService.isReadAllowed(playlist, securityService.getCurrentUsername(request))) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename=\"" + StringUtil.fileSystemSafe(playlist.getName()) + ".m3u8\"");
playlistService.exportPlaylist(id, response.getOutputStream());
return null;
}
}
@@ -0,0 +1,152 @@
/*
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.controller;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.security.JWTAuthenticationToken;
import org.libresonic.player.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* Controller for the page used to play shared music (Twitter, Facebook etc).
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/ext/share/**"})
public class ExternalPlayerController {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPlayerController.class);
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@Autowired
private ShareService shareService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
String shareName = ControllerUtils.extractMatched(request);
LOG.debug("Share name is {}", shareName);
if(StringUtils.isBlank(shareName)) {
LOG.warn("Could not find share with shareName " + shareName);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
Share share = shareService.getShareByName(shareName);
if (share != null && share.getExpires() != null && share.getExpires().before(new Date())) {
LOG.warn("Share " + shareName + " is expired");
share = null;
}
if (share != null) {
share.setLastVisited(new Date());
share.setVisitCount(share.getVisitCount() + 1);
shareService.updateShare(share);
}
Player player = playerService.getGuestPlayer(request);
map.put("share", share);
map.put("songs", getSongs(request, share, player));
return new ModelAndView("externalPlayer", "model", map);
}
private List<MediaFileWithUrlInfo> getSongs(HttpServletRequest request, Share share, Player player) throws IOException {
Date expires = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication instanceof JWTAuthenticationToken) {
DecodedJWT token = jwtSecurityService.verify((String) authentication.getCredentials());
expires = token.getExpiresAt();
}
Date finalExpires = expires;
List<MediaFileWithUrlInfo> result = new ArrayList<>();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(player.getUsername());
if (share != null) {
for (MediaFile file : shareService.getSharedFiles(share.getId(), musicFolders)) {
if (file.getFile().exists()) {
if (file.isDirectory()) {
List<MediaFile> childrenOf = mediaFileService.getChildrenOf(file, true, false, true);
result.addAll(childrenOf.stream().map(mf -> addUrlInfo(request, player, mf, finalExpires)).collect(Collectors.toList()));
} else {
result.add(addUrlInfo(request, player, file, finalExpires));
}
}
}
}
return result;
}
public MediaFileWithUrlInfo addUrlInfo(HttpServletRequest request, Player player, MediaFile mediaFile, Date expires) {
String prefix = "/ext";
String streamUrl = jwtSecurityService.addJWTToken(
UriComponentsBuilder
.fromHttpUrl(NetworkService.getBaseUrl(request) + prefix + "/stream")
.queryParam("id", mediaFile.getId())
.queryParam("player", player.getId())
.queryParam("maxBitRate", "1200"),
expires)
.build()
.toUriString();
String coverArtUrl = jwtSecurityService.addJWTToken(
UriComponentsBuilder
.fromHttpUrl(NetworkService.getBaseUrl(request) + prefix + "/coverArt.view")
.queryParam("id", mediaFile.getId())
.queryParam("size", "500"),
expires)
.build()
.toUriString();
return new MediaFileWithUrlInfo(mediaFile, coverArtUrl, streamUrl);
}
}
@@ -0,0 +1,132 @@
/*
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.controller;
import org.libresonic.player.command.GeneralSettingsCommand;
import org.libresonic.player.domain.Theme;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Locale;
/**
* Controller for the page used to administrate general settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/generalSettings")
public class GeneralSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "generalSettings";
}
@ModelAttribute
protected void formBackingObject(Model model) throws Exception {
GeneralSettingsCommand command = new GeneralSettingsCommand();
command.setCoverArtFileTypes(settingsService.getCoverArtFileTypes());
command.setIgnoredArticles(settingsService.getIgnoredArticles());
command.setShortcuts(settingsService.getShortcuts());
command.setIndex(settingsService.getIndexString());
command.setPlaylistFolder(settingsService.getPlaylistFolder());
command.setMusicFileTypes(settingsService.getMusicFileTypes());
command.setVideoFileTypes(settingsService.getVideoFileTypes());
command.setSortAlbumsByYear(settingsService.isSortAlbumsByYear());
command.setGettingStartedEnabled(settingsService.isGettingStartedEnabled());
command.setWelcomeTitle(settingsService.getWelcomeTitle());
command.setWelcomeSubtitle(settingsService.getWelcomeSubtitle());
command.setWelcomeMessage(settingsService.getWelcomeMessage());
command.setLoginMessage(settingsService.getLoginMessage());
Theme[] themes = settingsService.getAvailableThemes();
command.setThemes(themes);
String currentThemeId = settingsService.getThemeId();
for (int i = 0; i < themes.length; i++) {
if (currentThemeId.equals(themes[i].getId())) {
command.setThemeIndex(String.valueOf(i));
break;
}
}
Locale currentLocale = settingsService.getLocale();
Locale[] locales = settingsService.getAvailableLocales();
String[] localeStrings = new String[locales.length];
for (int i = 0; i < locales.length; i++) {
localeStrings[i] = locales[i].getDisplayName(locales[i]);
if (currentLocale.equals(locales[i])) {
command.setLocaleIndex(String.valueOf(i));
}
}
command.setLocales(localeStrings);
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") GeneralSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
int themeIndex = Integer.parseInt(command.getThemeIndex());
Theme theme = settingsService.getAvailableThemes()[themeIndex];
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = settingsService.getAvailableLocales()[localeIndex];
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute(
"settings_reload",
!settingsService.getIndexString().equals(command.getIndex())
|| !settingsService.getIgnoredArticles().equals(command.getIgnoredArticles())
|| !settingsService.getShortcuts().equals(command.getShortcuts())
|| !settingsService.getThemeId().equals(theme.getId())
|| !settingsService.getLocale().equals(locale));
settingsService.setIndexString(command.getIndex());
settingsService.setIgnoredArticles(command.getIgnoredArticles());
settingsService.setShortcuts(command.getShortcuts());
settingsService.setPlaylistFolder(command.getPlaylistFolder());
settingsService.setMusicFileTypes(command.getMusicFileTypes());
settingsService.setVideoFileTypes(command.getVideoFileTypes());
settingsService.setCoverArtFileTypes(command.getCoverArtFileTypes());
settingsService.setSortAlbumsByYear(command.isSortAlbumsByYear());
settingsService.setGettingStartedEnabled(command.isGettingStartedEnabled());
settingsService.setWelcomeTitle(command.getWelcomeTitle());
settingsService.setWelcomeSubtitle(command.getWelcomeSubtitle());
settingsService.setWelcomeMessage(command.getWelcomeMessage());
settingsService.setLoginMessage(command.getLoginMessage());
settingsService.setThemeId(theme.getId());
settingsService.setLocale(locale);
settingsService.save();
return "redirect:generalSettings.view";
}
}
@@ -0,0 +1,56 @@
/*
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.controller;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/gettingStarted")
public class GettingStartedController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView gettingStarted(HttpServletRequest request) {
if (request.getParameter("hide") != null) {
settingsService.setGettingStartedEnabled(false);
settingsService.save();
return new ModelAndView(new RedirectView("home.view"));
}
Map<String, Object> map = new HashMap<>();;
map.put("runningAsRoot", "root".equals(System.getProperty("user.name")));
return new ModelAndView("gettingStarted", "model", map);
}
}
@@ -0,0 +1,213 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.JWTSecurityService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.Pair;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Controller which produces the HLS (Http Live Streaming) playlist.
*
* @author Sindre Mehus
*/
@Controller(value = "hlsController")
@RequestMapping(value = {"/hls/**", "/ext/hls/**"})
public class HLSController {
private static final int SEGMENT_DURATION = 10;
private static final Pattern BITRATE_PATTERN = Pattern.compile("(\\d+)(@(\\d+)x(\\d+))?");
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SecurityService securityService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
int id = ServletRequestUtils.getIntParameter(request, "id");
MediaFile mediaFile = mediaFileService.getMediaFile(id);
Player player = playerService.getPlayer(request, response);
String username = player.getUsername();
if (mediaFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Media file not found: " + id);
return null;
}
if (username != null && !securityService.isFolderAccessAllowed(mediaFile, username)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + username);
return null;
}
Integer duration = mediaFile.getDurationSeconds();
if (duration == null || duration == 0) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown duration for media file: " + id);
return null;
}
response.setContentType("application/vnd.apple.mpegurl");
response.setCharacterEncoding(StringUtil.ENCODING_UTF8);
List<Pair<Integer, Dimension>> bitRates = parseBitRates(request);
PrintWriter writer = response.getWriter();
if (bitRates.size() > 1) {
generateVariantPlaylist(request, id, player, bitRates, writer);
} else {
generateNormalPlaylist(request, id, player, bitRates.size() == 1 ? bitRates.get(0) : null, duration, writer);
}
return null;
}
private List<Pair<Integer, Dimension>> parseBitRates(HttpServletRequest request) throws IllegalArgumentException {
List<Pair<Integer, Dimension>> result = new ArrayList<Pair<Integer, Dimension>>();
String[] bitRates = request.getParameterValues("bitRate");
if (bitRates != null) {
for (String bitRate : bitRates) {
result.add(parseBitRate(bitRate));
}
}
return result;
}
/**
* Parses a string containing the bitrate and an optional width/height, e.g., 1200@640x480
*/
protected Pair<Integer, Dimension> parseBitRate(String bitRate) throws IllegalArgumentException {
Matcher matcher = BITRATE_PATTERN.matcher(bitRate);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid bitrate specification: " + bitRate);
}
int kbps = Integer.parseInt(matcher.group(1));
if (matcher.group(3) == null) {
return new Pair<Integer, Dimension>(kbps, null);
} else {
int width = Integer.parseInt(matcher.group(3));
int height = Integer.parseInt(matcher.group(4));
return new Pair<Integer, Dimension>(kbps, new Dimension(width, height));
}
}
private void generateVariantPlaylist(HttpServletRequest request, int id, Player player, List<Pair<Integer, Dimension>> bitRates, PrintWriter writer) {
writer.println("#EXTM3U");
writer.println("#EXT-X-VERSION:1");
// writer.println("#EXT-X-TARGETDURATION:" + SEGMENT_DURATION);
String contextPath = getContextPath(request);
for (Pair<Integer, Dimension> bitRate : bitRates) {
Integer kbps = bitRate.getFirst();
writer.println("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + kbps * 1000L);
UriComponentsBuilder url = (UriComponentsBuilder.fromUriString(contextPath + "ext/hls/hls.m3u8")
.queryParam("id", id)
.queryParam("player", player.getId())
.queryParam("bitRate", kbps));
jwtSecurityService.addJWTToken(url);
writer.print(url.toUriString());
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
writer.print("@" + dimension.width + "x" + dimension.height);
}
writer.println();
}
// writer.println("#EXT-X-ENDLIST");
}
private void generateNormalPlaylist(HttpServletRequest request, int id, Player player, Pair<Integer, Dimension> bitRate, int totalDuration, PrintWriter writer) {
writer.println("#EXTM3U");
writer.println("#EXT-X-VERSION:1");
writer.println("#EXT-X-TARGETDURATION:" + SEGMENT_DURATION);
for (int i = 0; i < totalDuration / SEGMENT_DURATION; i++) {
int offset = i * SEGMENT_DURATION;
writer.println("#EXTINF:" + SEGMENT_DURATION + ",");
writer.println(createStreamUrl(request, player, id, offset, SEGMENT_DURATION, bitRate));
}
int remainder = totalDuration % SEGMENT_DURATION;
if (remainder > 0) {
writer.println("#EXTINF:" + remainder + ",");
int offset = totalDuration - remainder;
writer.println(createStreamUrl(request, player, id, offset, remainder, bitRate));
}
writer.println("#EXT-X-ENDLIST");
}
private String createStreamUrl(HttpServletRequest request, Player player, int id, int offset, int duration, Pair<Integer, Dimension> bitRate) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(getContextPath(request) + "ext/stream/stream.ts");
builder.queryParam("id", id);
builder.queryParam("hls", "true");
builder.queryParam("timeOffset", offset);
builder.queryParam("player", player.getId());
builder.queryParam("duration", duration);
if (bitRate != null) {
builder.queryParam("maxBitRate", bitRate.getFirst());
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
builder.queryParam("size", dimension.width);
builder.queryParam("x", dimension.height);
}
}
jwtSecurityService.addJWTToken(builder);
return builder.toUriString();
}
private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (StringUtils.isEmpty(contextPath)) {
contextPath = "/";
} else {
contextPath += "/";
}
return contextPath;
}
}
@@ -0,0 +1,115 @@
/*
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.controller;
import org.apache.commons.io.input.ReversedLinesFileReader;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.VersionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the help page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/help")
public class HelpController {
private static final Logger logger = LoggerFactory.getLogger(HelpController.class);
private static final int LOG_LINES_TO_SHOW = 50;
@Autowired
private VersionService versionService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
if (versionService.isNewFinalVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestFinalVersion());
} else if (versionService.isNewBetaVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestBetaVersion());
}
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
String serverInfo = request.getSession().getServletContext().getServerInfo() +
", java " + System.getProperty("java.version") +
", " + System.getProperty("os.name");
map.put("user", securityService.getCurrentUser(request));
map.put("brand", settingsService.getBrand());
map.put("localVersion", versionService.getLocalVersion());
map.put("buildDate", versionService.getLocalBuildDate());
map.put("buildNumber", versionService.getLocalBuildNumber());
map.put("serverInfo", serverInfo);
map.put("usedMemory", totalMemory - freeMemory);
map.put("totalMemory", totalMemory);
File logFile = SettingsService.getLogFile();
List<String> latestLogEntries = getLatestLogEntries(logFile);
map.put("logEntries", latestLogEntries);
map.put("logFile", logFile);
return new ModelAndView("help","model",map);
}
private static List<String> getLatestLogEntries(File logFile) {
try {
List<String> lines = new ArrayList<>(LOG_LINES_TO_SHOW);
ReversedLinesFileReader reader = new ReversedLinesFileReader(logFile, Charset.defaultCharset());
String current;
while((current = reader.readLine()) != null && lines.size() < LOG_LINES_TO_SHOW) {
lines.add(0, current);
}
return lines;
} catch (Exception e) {
logger.warn("Could not open log file " + logFile, e);
return null;
}
}
}
@@ -0,0 +1,341 @@
/*
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.controller;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import static org.springframework.web.bind.ServletRequestUtils.getIntParameter;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* Controller for the home page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/home")
public class HomeController {
private static final int LIST_SIZE = 40;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private RatingService ratingService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
User user = securityService.getCurrentUser(request);
if (user.isAdminRole() && settingsService.isGettingStartedEnabled()) {
return new ModelAndView(new RedirectView("gettingStarted.view"));
}
int listOffset = getIntParameter(request, "listOffset", 0);
AlbumListType listType = AlbumListType.fromId(getStringParameter(request, "listType"));
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
if (listType == null) {
listType = userSettings.getDefaultAlbumList();
}
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(user.getUsername());
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername(),
selectedMusicFolder == null ? null : selectedMusicFolder.getId());
Map<String, Object> map = new HashMap<>();
List<Album> albums = Collections.emptyList();
switch (listType) {
case HIGHEST:
albums = getHighestRated(listOffset, LIST_SIZE, musicFolders);
break;
case FREQUENT:
albums = getMostFrequent(listOffset, LIST_SIZE, musicFolders);
break;
case RECENT:
albums = getMostRecent(listOffset, LIST_SIZE, musicFolders);
break;
case NEWEST:
albums = getNewest(listOffset, LIST_SIZE, musicFolders);
break;
case STARRED:
albums = getStarred(listOffset, LIST_SIZE, user.getUsername(), musicFolders);
break;
case RANDOM:
albums = getRandom(LIST_SIZE, musicFolders);
break;
case ALPHABETICAL:
albums = getAlphabetical(listOffset, LIST_SIZE, true, musicFolders);
break;
case DECADE:
List<Integer> decades = createDecades();
map.put("decades", decades);
int decade = getIntParameter(request, "decade", decades.get(0));
map.put("decade", decade);
albums = getByYear(listOffset, LIST_SIZE, decade, decade + 9, musicFolders);
break;
case GENRE:
List<Genre> genres = mediaFileService.getGenres(true);
map.put("genres", genres);
if (!genres.isEmpty()) {
String genre = getStringParameter(request, "genre", genres.get(0).getName());
map.put("genre", genre);
albums = getByGenre(listOffset, LIST_SIZE, genre, musicFolders);
}
break;
default:
break;
}
map.put("albums", albums);
map.put("welcomeTitle", settingsService.getWelcomeTitle());
map.put("welcomeSubtitle", settingsService.getWelcomeSubtitle());
map.put("welcomeMessage", settingsService.getWelcomeMessage());
map.put("isIndexBeingCreated", mediaScannerService.isScanning());
map.put("musicFoldersExist", !settingsService.getAllMusicFolders().isEmpty());
map.put("listType", listType.getId());
map.put("listSize", LIST_SIZE);
map.put("coverArtSize", CoverArtScheme.MEDIUM.getSize());
map.put("listOffset", listOffset);
map.put("musicFolder", selectedMusicFolder);
map.put("listReloadDelay", userSettings.getListReloadDelay());
return new ModelAndView("home","model",map);
}
private List<Album> getHighestRated(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
for (MediaFile mediaFile : ratingService.getHighestRatedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setRating((int) Math.round(ratingService.getAverageRating(mediaFile) * 10.0D));
result.add(album);
}
return result;
}
private List<Album> getMostFrequent(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
for (MediaFile mediaFile : mediaFileService.getMostFrequentlyPlayedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setPlayCount(mediaFile.getPlayCount());
result.add(album);
}
return result;
}
private List<Album> getMostRecent(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
for (MediaFile mediaFile : mediaFileService.getMostRecentlyPlayedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setLastPlayed(mediaFile.getLastPlayed());
result.add(album);
}
return result;
}
private List<Album> getNewest(int offset, int count, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<>();
for (MediaFile file : mediaFileService.getNewestAlbums(offset, count, musicFolders)) {
Album album = createAlbum(file);
Date created = file.getCreated();
if (created == null) {
created = file.getChanged();
}
album.setCreated(created);
result.add(album);
}
return result;
}
private List<Album> getStarred(int offset, int count, String username, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<>();
for (MediaFile file : mediaFileService.getStarredAlbums(offset, count, username, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getRandom(int count, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<>();
for (MediaFile file : searchService.getRandomAlbums(count, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getAlphabetical(int offset, int count, boolean byArtist, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<>();
for (MediaFile file : mediaFileService.getAlphabeticalAlbums(offset, count, byArtist, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getByYear(int offset, int count, int fromYear, int toYear, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
for (MediaFile file : mediaFileService.getAlbumsByYear(offset, count, fromYear, toYear, musicFolders)) {
Album album = createAlbum(file);
album.setYear(file.getYear());
result.add(album);
}
return result;
}
private List<Integer> createDecades() {
List<Integer> result = new ArrayList<>();
int decade = Calendar.getInstance().get(Calendar.YEAR) / 10;
for (int i = 0; i < 10; i++) {
result.add((decade - i) * 10);
}
return result;
}
private List<Album> getByGenre(int offset, int count, String genre, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
for (MediaFile file : mediaFileService.getAlbumsByGenre(offset, count, genre, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private Album createAlbum(MediaFile file) {
Album album = new Album();
album.setId(file.getId());
album.setPath(file.getPath());
album.setArtist(file.getArtist());
album.setAlbumTitle(file.getAlbumName());
album.setCoverArtPath(file.getCoverArtPath());
return album;
}
/**
* Contains info for a single album.
*/
public static class Album {
private String path;
private String coverArtPath;
private String artist;
private String albumTitle;
private Date created;
private Date lastPlayed;
private Integer playCount;
private Integer rating;
private int id;
private Integer year;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getCoverArtPath() {
return coverArtPath;
}
public void setCoverArtPath(String coverArtPath) {
this.coverArtPath = coverArtPath;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbumTitle() {
return albumTitle;
}
public void setAlbumTitle(String albumTitle) {
this.albumTitle = albumTitle;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getLastPlayed() {
return lastPlayed;
}
public void setLastPlayed(Date lastPlayed) {
this.lastPlayed = lastPlayed;
}
public Integer getPlayCount() {
return playCount;
}
public void setPlayCount(Integer playCount) {
this.playCount = playCount;
}
public Integer getRating() {
return rating;
}
public void setRating(Integer rating) {
this.rating = rating;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/importPlaylist")
public class ImportPlaylistController {
private static final long MAX_PLAYLIST_SIZE_MB = 5L;
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@RequestMapping(method = RequestMethod.POST)
protected String handlePost(RedirectAttributes redirectAttributes,
HttpServletRequest request
) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
try {
if (ServletFileUpload.isMultipartContent(request)) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
for (Object o : items) {
FileItem item = (FileItem) o;
if ("file".equals(item.getFieldName()) && !StringUtils.isBlank(item.getName())) {
if (item.getSize() > MAX_PLAYLIST_SIZE_MB * 1024L * 1024L) {
throw new Exception("The playlist file is too large. Max file size is " + MAX_PLAYLIST_SIZE_MB + " MB.");
}
String playlistName = FilenameUtils.getBaseName(item.getName());
String fileName = FilenameUtils.getName(item.getName());
String format = StringUtils.lowerCase(FilenameUtils.getExtension(item.getName()));
String username = securityService.getCurrentUsername(request);
Playlist playlist = playlistService.importPlaylist(username, playlistName, fileName,
item.getInputStream(), null);
map.put("playlist", playlist);
}
}
}
} catch (Exception e) {
map.put("error", e.getMessage());
}
redirectAttributes.addFlashAttribute("model", map);
return "redirect:importPlaylist";
}
@RequestMapping(method = RequestMethod.GET)
public String handleGet() {
return "importPlaylist";
}
}
@@ -0,0 +1,46 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/index")
public class IndexController {
private static final Logger LOG = LoggerFactory.getLogger(IndexController.class);
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = { RequestMethod.GET})
public ModelAndView index(HttpServletRequest request) {
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
Map<String, Object> map = new HashMap<String, Object>();
map.put("showRight", userSettings.isShowNowPlayingEnabled());
map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue());
map.put("listReloadDelay", userSettings.getListReloadDelay());
map.put("keyboardShortcutsEnabled", userSettings.isKeyboardShortcutsEnabled());
map.put("showSideBar", userSettings.isShowSideBar());
map.put("brand", settingsService.getBrand());
return new ModelAndView("index", "model", map);
}
}
@@ -0,0 +1,120 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.InternetRadio;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the page used to administrate the set of internet radio/tv stations.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/internetRadioSettings")
public class InternetRadioSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("internetRadios", settingsService.getAllInternetRadios(true));
model.addAttribute("model", map);
return "internetRadioSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
String error = handleParameters(request);
Map<String, Object> map = new HashMap<>();
if(error == null) {
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute("settings_reload", true);
}
redirectAttributes.addFlashAttribute("error", error);
return "redirect:internetRadioSettings.view";
}
private String handleParameters(HttpServletRequest request) {
List<InternetRadio> radios = settingsService.getAllInternetRadios(true);
for (InternetRadio radio : radios) {
Integer id = radio.getId();
String streamUrl = getParameter(request, "streamUrl", id);
String homepageUrl = getParameter(request, "homepageUrl", id);
String name = getParameter(request, "name", id);
boolean enabled = getParameter(request, "enabled", id) != null;
boolean delete = getParameter(request, "delete", id) != null;
if (delete) {
settingsService.deleteInternetRadio(id);
} else {
if (name == null) {
return "internetradiosettings.noname";
}
if (streamUrl == null) {
return "internetradiosettings.nourl";
}
settingsService.updateInternetRadio(new InternetRadio(id, name, streamUrl, homepageUrl, enabled, new Date()));
}
}
String name = StringUtils.trimToNull(request.getParameter("name"));
String streamUrl = StringUtils.trimToNull(request.getParameter("streamUrl"));
String homepageUrl = StringUtils.trimToNull(request.getParameter("homepageUrl"));
boolean enabled = StringUtils.trimToNull(request.getParameter("enabled")) != null;
if (name != null || streamUrl != null || homepageUrl != null) {
if (name == null) {
return "internetradiosettings.noname";
}
if (streamUrl == null) {
return "internetradiosettings.nourl";
}
settingsService.createInternetRadio(new InternetRadio(name, streamUrl, homepageUrl, enabled, new Date()));
}
return null;
}
private String getParameter(HttpServletRequest request, String name, Integer id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
}
@@ -0,0 +1,178 @@
/*
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.controller;
import org.apache.commons.io.IOUtils;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subsonic.restapi.Error;
import org.subsonic.restapi.ObjectFactory;
import org.subsonic.restapi.Response;
import org.subsonic.restapi.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import java.util.GregorianCalendar;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class JAXBWriter {
private static final Logger LOG = LoggerFactory.getLogger(JAXBWriter.class);
private final javax.xml.bind.JAXBContext jaxbContext;
private final DatatypeFactory datatypeFactory;
private final String restProtocolVersion;
public JAXBWriter() {
try {
jaxbContext = JAXBContext.newInstance(Response.class);
datatypeFactory = DatatypeFactory.newInstance();
restProtocolVersion = getRESTProtocolVersion();
} catch (Exception x) {
throw new RuntimeException(x);
}
}
private Marshaller createXmlMarshaller() {
Marshaller marshaller = null;
try {
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
private Marshaller createJsonMarshaller() {
try {
Marshaller marshaller;
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
return marshaller;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
private String getRESTProtocolVersion() throws Exception {
InputStream in = null;
try {
in = StringUtil.class.getResourceAsStream("/subsonic-rest-api.xsd");
Document document = new SAXBuilder().build(in);
Attribute version = document.getRootElement().getAttribute("version");
return version.getValue();
} finally {
IOUtils.closeQuietly(in);
}
}
public String getRestProtocolVersion() {
return restProtocolVersion;
}
public Response createResponse(boolean ok) {
Response response = new ObjectFactory().createResponse();
response.setStatus(ok ? ResponseStatus.OK : ResponseStatus.FAILED);
response.setVersion(restProtocolVersion);
return response;
}
public void writeResponse(HttpServletRequest request, HttpServletResponse httpResponse, Response jaxbResponse) {
String format = getStringParameter(request, "f", "xml");
String jsonpCallback = request.getParameter("callback");
boolean json = "json".equals(format);
boolean jsonp = "jsonp".equals(format) && jsonpCallback != null;
Marshaller marshaller;
if (json) {
marshaller = createJsonMarshaller();
httpResponse.setContentType("application/json");
} else if (jsonp) {
marshaller = createJsonMarshaller();
httpResponse.setContentType("text/javascript");
} else {
marshaller = createXmlMarshaller();
httpResponse.setContentType("text/xml");
}
httpResponse.setCharacterEncoding(StringUtil.ENCODING_UTF8);
try {
StringWriter writer = new StringWriter();
if (jsonp) {
writer.append(jsonpCallback).append('(');
}
marshaller.marshal(new ObjectFactory().createSubsonicResponse(jaxbResponse), writer);
if (jsonp) {
writer.append(");");
}
httpResponse.getWriter().append(writer.getBuffer());
} catch (JAXBException | IOException x) {
LOG.error("Failed to marshal JAXB", x);
throw new RuntimeException(x);
}
}
public void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
RESTController.ErrorCode code, String message) throws Exception {
Response res = createResponse(false);
Error error = new Error();
res.setError(error);
error.setCode(code.getCode());
error.setMessage(message);
writeResponse(request, response, res);
}
public XMLGregorianCalendar convertDate(Date date) {
if (date == null) {
return null;
}
GregorianCalendar c = new GregorianCalendar();
c.setTime(date);
return datatypeFactory.newXMLGregorianCalendar(c).normalize();
}
}
@@ -0,0 +1,174 @@
/*
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.controller;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.*;
/**
* Controller for the left index frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/left")
public class LeftController {
// Update this time if you want to force a refresh in clients.
private static final Calendar LAST_COMPATIBILITY_TIME = Calendar.getInstance();
static {
LAST_COMPATIBILITY_TIME.set(2012, Calendar.MARCH, 6, 0, 0, 0);
LAST_COMPATIBILITY_TIME.set(Calendar.MILLISECOND, 0);
}
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private MusicIndexService musicIndexService;
@Autowired
private PlayerService playerService;
/**
* Note: This class intentionally does not implement org.springframework.web.servlet.mvc.LastModified
* as we don't need browser-side caching of left.jsp. This method is only used by RESTController.
*/
long getLastModified(HttpServletRequest request) {
saveSelectedMusicFolder(request);
if (mediaScannerService.isScanning()) {
return -1L;
}
long lastModified = LAST_COMPATIBILITY_TIME.getTimeInMillis();
String username = securityService.getCurrentUsername(request);
// When was settings last changed?
lastModified = Math.max(lastModified, settingsService.getSettingsChanged());
// When was music folder(s) on disk last changed?
List<MusicFolder> allMusicFolders = settingsService.getMusicFoldersForUser(username);
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
if (selectedMusicFolder != null) {
File file = selectedMusicFolder.getPath();
lastModified = Math.max(lastModified, FileUtil.lastModified(file));
} else {
for (MusicFolder musicFolder : allMusicFolders) {
File file = musicFolder.getPath();
lastModified = Math.max(lastModified, FileUtil.lastModified(file));
}
}
// When was music folder table last changed?
for (MusicFolder musicFolder : allMusicFolders) {
lastModified = Math.max(lastModified, musicFolder.getChanged().getTime());
}
// When was internet radio table last changed?
for (InternetRadio internetRadio : settingsService.getAllInternetRadios()) {
lastModified = Math.max(lastModified, internetRadio.getChanged().getTime());
}
// When was user settings last changed?
UserSettings userSettings = settingsService.getUserSettings(username);
lastModified = Math.max(lastModified, userSettings.getChanged().getTime());
return lastModified;
}
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
boolean musicFolderChanged = saveSelectedMusicFolder(request);
Map<String, Object> map = new HashMap<>();
MediaLibraryStatistics statistics = mediaScannerService.getStatistics();
Locale locale = RequestContextUtils.getLocale(request);
boolean refresh = ServletRequestUtils.getBooleanParameter(request, "refresh", false);
if (refresh) {
settingsService.clearMusicFolderCache();
}
String username = securityService.getCurrentUsername(request);
List<MusicFolder> allMusicFolders = settingsService.getMusicFoldersForUser(username);
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
List<MusicFolder> musicFoldersToUse = selectedMusicFolder == null ? allMusicFolders : Collections.singletonList(selectedMusicFolder);
UserSettings userSettings = settingsService.getUserSettings(username);
MusicFolderContent musicFolderContent = musicIndexService.getMusicFolderContent(musicFoldersToUse, refresh);
map.put("player", playerService.getPlayer(request, response));
map.put("scanning", mediaScannerService.isScanning());
map.put("musicFolders", allMusicFolders);
map.put("selectedMusicFolder", selectedMusicFolder);
map.put("radios", settingsService.getAllInternetRadios());
map.put("shortcuts", musicIndexService.getShortcuts(musicFoldersToUse));
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("organizeByFolderStructure", settingsService.isOrganizeByFolderStructure());
map.put("musicFolderChanged", musicFolderChanged);
if (statistics != null) {
map.put("statistics", statistics);
long bytes = statistics.getTotalLengthInBytes();
long hours = statistics.getTotalDurationInSeconds() / 3600L;
map.put("hours", hours);
map.put("bytes", StringUtil.formatBytes(bytes, locale));
}
map.put("indexedArtists", musicFolderContent.getIndexedArtists());
map.put("singleSongs", musicFolderContent.getSingleSongs());
map.put("indexes", musicFolderContent.getIndexedArtists().keySet());
map.put("user", securityService.getCurrentUser(request));
return new ModelAndView("left","model",map);
}
private boolean saveSelectedMusicFolder(HttpServletRequest request) {
if (request.getParameter("musicFolderId") == null) {
return false;
}
int musicFolderId = Integer.parseInt(request.getParameter("musicFolderId"));
// Note: UserSettings.setChanged() is intentionally not called. This would break browser caching
// of the left frame.
UserSettings settings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
settings.setSelectedMusicFolderId(musicFolderId);
settingsService.updateUserSettings(settings);
return true;
}
}
@@ -0,0 +1,66 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
public class LoginController {
private static final Logger LOG = LoggerFactory.getLogger(LoginController.class);
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Auto-login if "user" and "password" parameters are given.
String username = request.getParameter("user");
String password = request.getParameter("password");
if (username != null && password != null) {
username = StringUtil.urlEncode(username);
password = StringUtil.urlEncode(password);
return new ModelAndView(new RedirectView("/login?"+
UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY+"=" + username +
"&"+UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY+"=" + password
));
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("logout", request.getParameter("logout") != null);
map.put("error", request.getParameter("error") != null);
map.put("brand", settingsService.getBrand());
map.put("loginMessage", settingsService.getLoginMessage());
User admin = securityService.getUserByName(User.USERNAME_ADMIN);
if (User.USERNAME_ADMIN.equals(admin.getPassword())) {
map.put("insecure", true);
}
return new ModelAndView("login", "model", map);
}
}
@@ -0,0 +1,51 @@
/*
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.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the lyrics popup.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/lyrics")
public class LyricsController {
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("artist", request.getParameter("artist"));
map.put("song", request.getParameter("song"));
return new ModelAndView("lyrics","model",map);
}
}
@@ -0,0 +1,123 @@
/*
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.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.JWTSecurityService;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.TranscodingService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* Controller which produces the M3U playlist.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/play.m3u")
public class M3UController {
@Autowired
private PlayerService playerService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("audio/x-mpegurl");
response.setCharacterEncoding(StringUtil.ENCODING_UTF8);
Player player = playerService.getPlayer(request, response);
String url = NetworkService.getBaseUrl(request);
url = url + "ext/stream?";
if (player.isExternalWithPlaylist()) {
createClientSidePlaylist(response.getWriter(), player, url);
} else {
createServerSidePlaylist(response.getWriter(), player, url);
}
return null;
}
private void createClientSidePlaylist(PrintWriter out, Player player, String url) throws Exception {
if (player.isM3uBomEnabled()) {
out.print("\ufeff");
}
out.println("#EXTM3U");
List<MediaFile> result;
synchronized (player.getPlayQueue()) {
result = player.getPlayQueue().getFiles();
}
for (MediaFile mediaFile : result) {
Integer duration = mediaFile.getDurationSeconds();
if (duration == null) {
duration = -1;
}
out.println("#EXTINF:" + duration + "," + mediaFile.getArtist() + " - " + mediaFile.getTitle());
String urlNoAuth = url + "player=" + player.getId() + "&id=" + mediaFile.getId() + "&suffix=." +
transcodingService.getSuffix(player, mediaFile, null);
String urlWithAuth = jwtSecurityService.addJWTToken(urlNoAuth);
out.println(urlWithAuth);
}
}
private void createServerSidePlaylist(PrintWriter out, Player player, String url) throws IOException {
url += "player=" + player.getId();
// Get suffix of current file, e.g., ".mp3".
String suffix = getSuffix(player);
if (suffix != null) {
url += "&suffix=." + suffix;
}
if (player.isM3uBomEnabled()) {
out.print("\ufeff");
}
out.println("#EXTM3U");
out.println("#EXTINF:-1,Libresonic");
out.println(jwtSecurityService.addJWTToken(url));
}
private String getSuffix(Player player) {
PlayQueue playQueue = player.getPlayQueue();
return playQueue.isEmpty() ? null : transcodingService.getSuffix(player, playQueue.getFile(0), null);
}
}
@@ -0,0 +1,281 @@
/*
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.controller;
import org.apache.commons.lang3.BooleanUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* Controller for the main page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/main")
public class MainController {
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private SettingsService settingsService;
@Autowired
private RatingService ratingService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(@RequestParam(name = "showAll", required = false) Boolean showAll,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
Player player = playerService.getPlayer(request, response);
List<MediaFile> mediaFiles = getMediaFiles(request);
if (mediaFiles.isEmpty()) {
return new ModelAndView(new RedirectView("notFound.view"));
}
MediaFile dir = mediaFiles.get(0);
if (dir.isFile()) {
dir = mediaFileService.getParentOf(dir);
}
// Redirect if root directory.
if (mediaFileService.isRoot(dir)) {
return new ModelAndView(new RedirectView("home.view?"));
}
String username = securityService.getCurrentUsername(request);
if (!securityService.isFolderAccessAllowed(dir, username)) {
return new ModelAndView(new RedirectView("accessDenied.view"));
}
UserSettings userSettings = settingsService.getUserSettings(username);
List<MediaFile> children = mediaFiles.size() == 1 ? mediaFileService.getChildrenOf(dir,
true,
true,
true) : getMultiFolderChildren(mediaFiles);
List<MediaFile> files = new ArrayList<>();
List<MediaFile> subDirs = new ArrayList<>();
for (MediaFile child : children) {
if (child.isFile()) {
files.add(child);
} else {
subDirs.add(child);
}
}
int userPaginationPreference = userSettings.getPaginationSize();
if(userPaginationPreference <= 0) {
showAll = true;
}
boolean thereIsMoreSubDirs = trimToSize(showAll, subDirs, userPaginationPreference);
boolean thereIsMoreSAlbums = false;
mediaFileService.populateStarredDate(dir, username);
mediaFileService.populateStarredDate(children, username);
map.put("dir", dir);
map.put("files", files);
map.put("subDirs", subDirs);
map.put("ancestors", getAncestors(dir));
map.put("coverArtSizeMedium", CoverArtScheme.MEDIUM.getSize());
map.put("coverArtSizeLarge", CoverArtScheme.LARGE.getSize());
map.put("player", player);
map.put("user", securityService.getCurrentUser(request));
map.put("visibility", userSettings.getMainVisibility());
map.put("showAlbumYear", settingsService.isSortAlbumsByYear());
map.put("showArtistInfo", userSettings.isShowArtistInfoEnabled());
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("brand", settingsService.getBrand());
map.put("viewAsList", isViewAsList(request, userSettings));
if (dir.isAlbum()) {
List<MediaFile> sieblingAlbums = getSieblingAlbums(dir);
thereIsMoreSAlbums = trimToSize(showAll, sieblingAlbums, userPaginationPreference);
map.put("sieblingAlbums", sieblingAlbums);
map.put("artist", guessArtist(children));
map.put("album", guessAlbum(children));
}
try {
MediaFile parent = mediaFileService.getParentOf(dir);
map.put("parent", parent);
map.put("navigateUpAllowed", !mediaFileService.isRoot(parent));
} catch (SecurityException x) {
// Happens if Podcast directory is outside music folder.
}
map.put("thereIsMore", (thereIsMoreSubDirs || thereIsMoreSAlbums) && !BooleanUtils.isTrue(showAll));
Integer userRating = ratingService.getRatingForUser(username, dir);
Double averageRating = ratingService.getAverageRating(dir);
if (userRating == null) {
userRating = 0;
}
if (averageRating == null) {
averageRating = 0.0D;
}
map.put("userRating", 10 * userRating);
map.put("averageRating", Math.round(10.0D * averageRating));
map.put("starred", mediaFileService.getMediaFileStarredDate(dir.getId(), username) != null);
String view;
if (isVideoOnly(children)) {
view = "videoMain";
} else if (dir.isAlbum()) {
view = "albumMain";
} else {
view = "artistMain";
}
return new ModelAndView(view, "model", map);
}
private <T> boolean trimToSize(Boolean showAll, List<T> list, int userPaginationPreference) {
boolean trimmed = false;
if(!BooleanUtils.isTrue(showAll)) {
if(list.size() > userPaginationPreference) {
trimmed = true;
list.subList(userPaginationPreference, list.size()).clear();
}
}
return trimmed;
}
private boolean isViewAsList(HttpServletRequest request, UserSettings userSettings) {
boolean viewAsList = ServletRequestUtils.getBooleanParameter(request, "viewAsList", userSettings.isViewAsList());
if (viewAsList != userSettings.isViewAsList()) {
userSettings.setViewAsList(viewAsList);
userSettings.setChanged(new Date());
settingsService.updateUserSettings(userSettings);
}
return viewAsList;
}
private boolean isVideoOnly(List<MediaFile> children) {
boolean videoFound = false;
for (MediaFile child : children) {
if (child.isAudio()) {
return false;
}
if (child.isVideo()) {
videoFound = true;
}
}
return videoFound;
}
private List<MediaFile> getMediaFiles(HttpServletRequest request) {
List<MediaFile> mediaFiles = new ArrayList<>();
for (String path : ServletRequestUtils.getStringParameters(request, "path")) {
MediaFile mediaFile = mediaFileService.getMediaFile(path);
if (mediaFile != null) {
mediaFiles.add(mediaFile);
}
}
for (int id : ServletRequestUtils.getIntParameters(request, "id")) {
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile != null) {
mediaFiles.add(mediaFile);
}
}
return mediaFiles;
}
private String guessArtist(List<MediaFile> children) {
for (MediaFile child : children) {
if (child.isFile() && child.getArtist() != null) {
return child.getArtist();
}
}
return null;
}
private String guessAlbum(List<MediaFile> children) {
for (MediaFile child : children) {
if (child.isFile() && child.getArtist() != null) {
return child.getAlbumName();
}
}
return null;
}
private List<MediaFile> getMultiFolderChildren(List<MediaFile> mediaFiles) throws IOException {
SortedSet<MediaFile> result = new TreeSet<>(new MediaFileComparator(settingsService.isSortAlbumsByYear()));
for (MediaFile mediaFile : mediaFiles) {
if (mediaFile.isFile()) {
mediaFile = mediaFileService.getParentOf(mediaFile);
}
result.addAll(mediaFileService.getChildrenOf(mediaFile, true, true, true));
}
return new ArrayList<>(result);
}
private List<MediaFile> getAncestors(MediaFile dir) throws IOException {
LinkedList<MediaFile> result = new LinkedList<>();
try {
MediaFile parent = mediaFileService.getParentOf(dir);
while (parent != null && !mediaFileService.isRoot(parent)) {
result.addFirst(parent);
parent = mediaFileService.getParentOf(parent);
}
} catch (SecurityException x) {
// Happens if Podcast directory is outside music folder.
}
return result;
}
private List<MediaFile> getSieblingAlbums(MediaFile dir) {
List<MediaFile> result = new ArrayList<>();
MediaFile parent = mediaFileService.getParentOf(dir);
if (!mediaFileService.isRoot(parent)) {
List<MediaFile> sieblings = mediaFileService.getChildrenOf(parent, false, true, true);
result.addAll(sieblings.stream().filter(siebling -> siebling.isAlbum() && !siebling.equals(dir)).collect(Collectors.toList()));
}
return result;
}
}
@@ -0,0 +1,85 @@
/*
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.controller;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the "more" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/more")
public class MoreController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
String uploadDirectory = null;
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
if (musicFolders.size() > 0) {
uploadDirectory = new File(musicFolders.get(0).getPath(), "Incoming").getPath();
}
Player player = playerService.getPlayer(request, response);
ModelAndView result = new ModelAndView();
result.addObject("model", map);
map.put("user", user);
map.put("uploadDirectory", uploadDirectory);
map.put("genres", mediaFileService.getGenres(false));
map.put("currentYear", Calendar.getInstance().get(Calendar.YEAR));
map.put("musicFolders", musicFolders);
map.put("clientSidePlaylist", player.isExternalWithPlaylist() || player.isWeb());
map.put("brand", settingsService.getBrand());
return result;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.MusicFolderSettingsCommand;
import org.libresonic.player.dao.AlbumDao;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.service.MediaScannerService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Controller for the page used to administrate the set of music folders.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/musicFolderSettings")
public class MusicFolderSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private ArtistDao artistDao;
@Autowired
private AlbumDao albumDao;
@Autowired
private MediaFileDao mediaFileDao;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "musicFolderSettings";
}
@ModelAttribute
protected void formBackingObject(@RequestParam(value = "scanNow",required = false) String scanNow,
@RequestParam(value = "expunge",required = false) String expunge,
Model model) throws Exception {
MusicFolderSettingsCommand command = new MusicFolderSettingsCommand();
if (scanNow != null) {
settingsService.clearMusicFolderCache();
mediaScannerService.scanLibrary();
}
if (expunge != null) {
expunge();
}
command.setInterval(String.valueOf(settingsService.getIndexCreationInterval()));
command.setHour(String.valueOf(settingsService.getIndexCreationHour()));
command.setFastCache(settingsService.isFastCacheEnabled());
command.setOrganizeByFolderStructure(settingsService.isOrganizeByFolderStructure());
command.setScanning(mediaScannerService.isScanning());
command.setMusicFolders(wrap(settingsService.getAllMusicFolders(true, true)));
command.setNewMusicFolder(new MusicFolderSettingsCommand.MusicFolderInfo());
model.addAttribute("command",command);
}
private void expunge() {
artistDao.expunge();
albumDao.expunge();
mediaFileDao.expunge();
}
private List<MusicFolderSettingsCommand.MusicFolderInfo> wrap(List<MusicFolder> musicFolders) {
return musicFolders.stream().map(MusicFolderSettingsCommand.MusicFolderInfo::new).collect(Collectors.toCollection(ArrayList::new));
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(@ModelAttribute("command") MusicFolderSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
for (MusicFolderSettingsCommand.MusicFolderInfo musicFolderInfo : command.getMusicFolders()) {
if (musicFolderInfo.isDelete()) {
settingsService.deleteMusicFolder(musicFolderInfo.getId());
} else {
MusicFolder musicFolder = musicFolderInfo.toMusicFolder();
if (musicFolder != null) {
settingsService.updateMusicFolder(musicFolder);
}
}
}
MusicFolder newMusicFolder = command.getNewMusicFolder().toMusicFolder();
if (newMusicFolder != null) {
settingsService.createMusicFolder(newMusicFolder);
}
settingsService.setIndexCreationInterval(Integer.parseInt(command.getInterval()));
settingsService.setIndexCreationHour(Integer.parseInt(command.getHour()));
settingsService.setFastCacheEnabled(command.isFastCache());
settingsService.setOrganizeByFolderStructure(command.isOrganizeByFolderStructure());
settingsService.save();
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute("settings_reload", true);
mediaScannerService.schedule();
return "redirect:musicFolderSettings.view";
}
}
@@ -0,0 +1,29 @@
package org.libresonic.player.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/notFound")
public class NotFoundController {
private static final Logger LOG = LoggerFactory.getLogger(NotFoundController.class);
@RequestMapping(method = {RequestMethod.GET})
public ModelAndView notFound(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("notFound");
}
}
@@ -0,0 +1,74 @@
/*
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.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.StatusService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller for showing what's currently playing.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/nowPlaying")
public class NowPlayingController {
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = playerService.getPlayer(request, response);
List<TransferStatus> statuses = statusService.getStreamStatusesForPlayer(player);
MediaFile current = statuses.isEmpty() ? null : mediaFileService.getMediaFile(statuses.get(0).getFile());
MediaFile dir = current == null ? null : mediaFileService.getParentOf(current);
String url;
if (dir != null && !mediaFileService.isRoot(dir)) {
url = "main.view?id=" + dir.getId();
} else {
url = "home.view";
}
return new ModelAndView(new RedirectView(url));
}
}
@@ -0,0 +1,84 @@
/*
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.controller;
import org.libresonic.player.command.PasswordSettingsCommand;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.validator.PasswordSettingsValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to change password.
*
* @author Sindre Mehus
*/
@org.springframework.stereotype.Controller
@RequestMapping("/passwordSettings")
public class PasswordSettingsController {
@Autowired
private SecurityService securityService;
@Autowired
private PasswordSettingsValidator passwordSettingsValidator;
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(passwordSettingsValidator);
}
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView displayForm(HttpServletRequest request) throws Exception {
PasswordSettingsCommand command = new PasswordSettingsCommand();
User user = securityService.getCurrentUser(request);
command.setUsername(user.getUsername());
command.setLdapAuthenticated(user.isLdapAuthenticated());
return new ModelAndView("passwordSettings","command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") @Validated PasswordSettingsCommand command, BindingResult bindingResult, RedirectAttributes redirectAttributes) throws Exception {
if (!bindingResult.hasErrors()) {
User user = securityService.getUserByName(command.getUsername());
user.setPassword(command.getPassword());
securityService.updateUser(user);
command.setPassword(null);
command.setConfirmPassword(null);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:passwordSettings.view";
} else {
return "passwordSettings";
}
}
}
@@ -0,0 +1,193 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.PersonalSettingsCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Locale;
/**
* Controller for the page used to administrate per-user settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/personalSettings")
public class PersonalSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@ModelAttribute
protected void formBackingObject(HttpServletRequest request,Model model) throws Exception {
PersonalSettingsCommand command = new PersonalSettingsCommand();
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
command.setUser(user);
command.setLocaleIndex("-1");
command.setThemeIndex("-1");
command.setAlbumLists(AlbumListType.values());
command.setAlbumListId(userSettings.getDefaultAlbumList().getId());
command.setAvatars(settingsService.getAllSystemAvatars());
command.setCustomAvatar(settingsService.getCustomAvatar(user.getUsername()));
command.setAvatarId(getAvatarId(userSettings));
command.setPartyModeEnabled(userSettings.isPartyModeEnabled());
command.setQueueFollowingSongs(userSettings.isQueueFollowingSongs());
command.setShowNowPlayingEnabled(userSettings.isShowNowPlayingEnabled());
command.setShowArtistInfoEnabled(userSettings.isShowArtistInfoEnabled());
command.setNowPlayingAllowed(userSettings.isNowPlayingAllowed());
command.setMainVisibility(userSettings.getMainVisibility());
command.setPlaylistVisibility(userSettings.getPlaylistVisibility());
command.setFinalVersionNotificationEnabled(userSettings.isFinalVersionNotificationEnabled());
command.setBetaVersionNotificationEnabled(userSettings.isBetaVersionNotificationEnabled());
command.setSongNotificationEnabled(userSettings.isSongNotificationEnabled());
command.setAutoHidePlayQueue(userSettings.isAutoHidePlayQueue());
command.setListReloadDelay(userSettings.getListReloadDelay());
command.setKeyboardShortcutsEnabled(userSettings.isKeyboardShortcutsEnabled());
command.setLastFmEnabled(userSettings.isLastFmEnabled());
command.setLastFmUsername(userSettings.getLastFmUsername());
command.setLastFmPassword(userSettings.getLastFmPassword());
command.setPaginationSize(userSettings.getPaginationSize());
Locale currentLocale = userSettings.getLocale();
Locale[] locales = settingsService.getAvailableLocales();
String[] localeStrings = new String[locales.length];
for (int i = 0; i < locales.length; i++) {
localeStrings[i] = locales[i].getDisplayName(locales[i]);
if (locales[i].equals(currentLocale)) {
command.setLocaleIndex(String.valueOf(i));
}
}
command.setLocales(localeStrings);
String currentThemeId = userSettings.getThemeId();
Theme[] themes = settingsService.getAvailableThemes();
command.setThemes(themes);
for (int i = 0; i < themes.length; i++) {
if (themes[i].getId().equals(currentThemeId)) {
command.setThemeIndex(String.valueOf(i));
break;
}
}
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "personalSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") PersonalSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = null;
if (localeIndex != -1) {
locale = settingsService.getAvailableLocales()[localeIndex];
}
int themeIndex = Integer.parseInt(command.getThemeIndex());
String themeId = null;
if (themeIndex != -1) {
themeId = settingsService.getAvailableThemes()[themeIndex].getId();
}
String username = command.getUser().getUsername();
UserSettings settings = settingsService.getUserSettings(username);
settings.setLocale(locale);
settings.setThemeId(themeId);
settings.setDefaultAlbumList(AlbumListType.fromId(command.getAlbumListId()));
settings.setPartyModeEnabled(command.isPartyModeEnabled());
settings.setQueueFollowingSongs(command.isQueueFollowingSongs());
settings.setShowNowPlayingEnabled(command.isShowNowPlayingEnabled());
settings.setShowArtistInfoEnabled(command.isShowArtistInfoEnabled());
settings.setNowPlayingAllowed(command.isNowPlayingAllowed());
settings.setMainVisibility(command.getMainVisibility());
settings.setPlaylistVisibility(command.getPlaylistVisibility());
settings.setFinalVersionNotificationEnabled(command.isFinalVersionNotificationEnabled());
settings.setBetaVersionNotificationEnabled(command.isBetaVersionNotificationEnabled());
settings.setSongNotificationEnabled(command.isSongNotificationEnabled());
settings.setAutoHidePlayQueue(command.isAutoHidePlayQueue());
settings.setListReloadDelay(command.getListReloadDelay());
settings.setKeyboardShortcutsEnabled(command.isKeyboardShortcutsEnabled());
settings.setLastFmEnabled(command.isLastFmEnabled());
settings.setLastFmUsername(command.getLastFmUsername());
settings.setSystemAvatarId(getSystemAvatarId(command));
settings.setAvatarScheme(getAvatarScheme(command));
settings.setPaginationSize(command.getPaginationSize());
if (StringUtils.isNotBlank(command.getLastFmPassword())) {
settings.setLastFmPassword(command.getLastFmPassword());
}
settings.setChanged(new Date());
settingsService.updateUserSettings(settings);
redirectAttributes.addFlashAttribute("settings_reload", true);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:personalSettings.view";
}
private int getAvatarId(UserSettings userSettings) {
AvatarScheme avatarScheme = userSettings.getAvatarScheme();
return avatarScheme == AvatarScheme.SYSTEM ? userSettings.getSystemAvatarId() : avatarScheme.getCode();
}
private AvatarScheme getAvatarScheme(PersonalSettingsCommand command) {
if (command.getAvatarId() == AvatarScheme.NONE.getCode()) {
return AvatarScheme.NONE;
}
if (command.getAvatarId() == AvatarScheme.CUSTOM.getCode()) {
return AvatarScheme.CUSTOM;
}
return AvatarScheme.SYSTEM;
}
private Integer getSystemAvatarId(PersonalSettingsCommand command) {
int avatarId = command.getAvatarId();
if (avatarId == AvatarScheme.NONE.getCode() ||
avatarId == AvatarScheme.CUSTOM.getCode()) {
return null;
}
return avatarId;
}
}
@@ -0,0 +1,85 @@
/*
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.controller;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the playlist frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playQueue")
public class PlayQueueController {
@Autowired
private PlayerService playerService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
Player player = playerService.getPlayer(request, response);
Map<String, Object> map = new HashMap<>();
map.put("user", user);
map.put("player", player);
map.put("players", playerService.getPlayersForUserAndClientId(user.getUsername(), null));
map.put("visibility", userSettings.getPlaylistVisibility());
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("notify", userSettings.isSongNotificationEnabled());
map.put("autoHide", userSettings.isAutoHidePlayQueue());
return new ModelAndView("playQueue","model",map);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,150 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.PlayerSettingsCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.TranscodingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* Controller for the player settings page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playerSettings")
public class PlayerSettingsController {
@Autowired
private PlayerService playerService;
@Autowired
private SecurityService securityService;
@Autowired
private TranscodingService transcodingService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "playerSettings";
}
@ModelAttribute
protected void formBackingObject(HttpServletRequest request, Model model) throws Exception {
handleRequestParameters(request);
List<Player> players = getPlayers(request);
User user = securityService.getCurrentUser(request);
PlayerSettingsCommand command = new PlayerSettingsCommand();
Player player = null;
String playerId = request.getParameter("id");
if (playerId != null) {
player = playerService.getPlayerById(playerId);
} else if (!players.isEmpty()) {
player = players.get(0);
}
if (player != null) {
command.setPlayerId(player.getId());
command.setName(player.getName());
command.setDescription(player.toString());
command.setType(player.getType());
command.setLastSeen(player.getLastSeen());
command.setDynamicIp(player.isDynamicIp());
command.setAutoControlEnabled(player.isAutoControlEnabled());
command.setM3uBomEnabled(player.isM3uBomEnabled());
command.setTranscodeSchemeName(player.getTranscodeScheme().name());
command.setTechnologyName(player.getTechnology().name());
command.setAllTranscodings(transcodingService.getAllTranscodings());
List<Transcoding> activeTranscodings = transcodingService.getTranscodingsForPlayer(player);
int[] activeTranscodingIds = new int[activeTranscodings.size()];
for (int i = 0; i < activeTranscodings.size(); i++) {
activeTranscodingIds[i] = activeTranscodings.get(i).getId();
}
command.setActiveTranscodingIds(activeTranscodingIds);
}
command.setTranscodingSupported(transcodingService.isDownsamplingSupported(null));
command.setTranscodeDirectory(transcodingService.getTranscodeDirectory().getPath());
command.setTranscodeSchemes(TranscodeScheme.values());
command.setTechnologies(PlayerTechnology.values());
command.setPlayers(players.toArray(new Player[players.size()]));
command.setAdmin(user.isAdminRole());
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") PlayerSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
Player player = playerService.getPlayerById(command.getPlayerId());
player.setAutoControlEnabled(command.isAutoControlEnabled());
player.setM3uBomEnabled(command.isM3uBomEnabled());
player.setDynamicIp(command.isDynamicIp());
player.setName(StringUtils.trimToNull(command.getName()));
player.setTranscodeScheme(TranscodeScheme.valueOf(command.getTranscodeSchemeName()));
player.setTechnology(PlayerTechnology.valueOf(command.getTechnologyName()));
playerService.updatePlayer(player);
transcodingService.setTranscodingsForPlayer(player, command.getActiveTranscodingIds());
redirectAttributes.addFlashAttribute("settings_reload", true);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:playerSettings.view";
}
private List<Player> getPlayers(HttpServletRequest request) {
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
List<Player> players = playerService.getAllPlayers();
List<Player> authorizedPlayers = new ArrayList<Player>();
for (Player player : players) {
// Only display authorized players.
if (user.isAdminRole() || username.equals(player.getUsername())) {
authorizedPlayers.add(player);
}
}
return authorizedPlayers;
}
private void handleRequestParameters(HttpServletRequest request) {
if (request.getParameter("delete") != null) {
playerService.removePlayerById(request.getParameter("delete"));
} else if (request.getParameter("clone") != null) {
playerService.clonePlayer(request.getParameter("clone"));
}
}
}
@@ -0,0 +1,88 @@
/*
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.controller;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the playlist page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playlist")
public class PlaylistController {
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
UserSettings userSettings = settingsService.getUserSettings(username);
Player player = playerService.getPlayer(request, response);
Playlist playlist = playlistService.getPlaylist(id);
if (playlist == null) {
return new ModelAndView(new RedirectView("notFound"));
}
map.put("playlist", playlist);
map.put("user", user);
map.put("player", player);
map.put("editAllowed", username.equals(playlist.getUsername()) || securityService.isAdmin(username));
map.put("partyMode", userSettings.isPartyModeEnabled());
return new ModelAndView("playlist","model",map);
}
}
@@ -0,0 +1,64 @@
/*
* 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 2014 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the playlists page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playlists")
public class PlaylistsController {
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(HttpServletRequest request, Model model) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(user.getUsername());
map.put("playlists", playlists);
model.addAttribute("model", map);
return "playlists";
}
}
@@ -0,0 +1,66 @@
/*
* 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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the "Podcast channel" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastChannel")
public class PodcastChannelController {
@Autowired
private PodcastService podcastService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView();
result.addObject("model", map);
int channelId = ServletRequestUtils.getRequiredIntParameter(request, "id");
map.put("user", securityService.getCurrentUser(request));
map.put("channel", podcastService.getChannel(channelId));
map.put("episodes", podcastService.getEpisodes(channelId));
return result;
}
}
@@ -0,0 +1,79 @@
/*
* 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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.PodcastChannel;
import org.libresonic.player.domain.PodcastEpisode;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the "Podcast channels" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastChannels")
public class PodcastChannelsController {
@Autowired
private PodcastService podcastService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView();
result.addObject("model", map);
Map<PodcastChannel, List<PodcastEpisode>> channels = new LinkedHashMap<>();
Map<Integer, PodcastChannel> channelMap = new HashMap<>();
for (PodcastChannel channel : podcastService.getAllChannels()) {
channels.put(channel, podcastService.getEpisodes(channel.getId()));
channelMap.put(channel.getId(), channel);
}
map.put("user", securityService.getCurrentUser(request));
map.put("channels", channels);
map.put("channelMap", channelMap);
map.put("newestEpisodes", podcastService.getNewestEpisodes(10));
return result;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Controller for the page used to generate the Podcast XML file.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcast")
public class PodcastController {
private static final DateFormat RSS_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = request.getRequestURL().toString();
String username = securityService.getCurrentUsername(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(username);
List<Podcast> podcasts = new ArrayList<>();
for (Playlist playlist : playlists) {
List<MediaFile> songs = playlistService.getFilesInPlaylist(playlist.getId());
if (songs.isEmpty()) {
continue;
}
long length = 0L;
for (MediaFile song : songs) {
length += song.getFileSize();
}
String publishDate = RSS_DATE_FORMAT.format(playlist.getCreated());
// Resolve content type.
String suffix = songs.get(0).getFormat();
String type = StringUtil.getMimeType(suffix);
String enclosureUrl = url + "/stream?playlist=" + playlist.getId();
podcasts.add(new Podcast(playlist.getName(), publishDate, enclosureUrl, length, type));
}
Map<String, Object> map = new HashMap<>();
map.put("url", url);
map.put("podcasts", podcasts);
return new ModelAndView("podcast","model",map);
}
/**
* Contains information about a single Podcast.
*/
public static class Podcast {
private String name;
private String publishDate;
private String enclosureUrl;
private long length;
private String type;
public Podcast(String name, String publishDate, String enclosureUrl, long length, String type) {
this.name = name;
this.publishDate = publishDate;
this.enclosureUrl = enclosureUrl;
this.length = length;
this.type = type;
}
public String getName() {
return name;
}
public String getPublishDate() {
return publishDate;
}
public String getEnclosureUrl() {
return enclosureUrl;
}
public long getLength() {
return length;
}
public String getType() {
return type;
}
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.PodcastEpisode;
import org.libresonic.player.domain.PodcastStatus;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller for the "Podcast receiver" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastReceiverAdmin")
public class PodcastReceiverAdminController {
@Autowired
private PodcastService podcastService;
@RequestMapping(method = { RequestMethod.POST, RequestMethod.GET })
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Integer channelId = ServletRequestUtils.getIntParameter(request, "channelId");
if (request.getParameter("add") != null) {
String url = StringUtils.trim(request.getParameter("add"));
podcastService.createChannel(url);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
if (request.getParameter("downloadEpisode") != null) {
download(StringUtil.parseInts(request.getParameter("downloadEpisode")));
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
}
if (request.getParameter("deleteChannel") != null) {
podcastService.deleteChannel(channelId);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
if (request.getParameter("deleteEpisode") != null) {
for (int episodeId : StringUtil.parseInts(request.getParameter("deleteEpisode"))) {
podcastService.deleteEpisode(episodeId, true);
}
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
}
if (request.getParameter("refresh") != null) {
if (channelId != null) {
podcastService.refreshChannel(channelId, true);
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
} else {
podcastService.refreshAllChannels(true);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
}
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
private void download(int[] episodeIds) {
for (Integer episodeId : episodeIds) {
PodcastEpisode episode = podcastService.getEpisode(episodeId, false);
if (episode != null && episode.getUrl() != null &&
(episode.getStatus() == PodcastStatus.NEW ||
episode.getStatus() == PodcastStatus.ERROR ||
episode.getStatus() == PodcastStatus.SKIPPED)) {
podcastService.downloadEpisode(episode);
}
}
}
}
@@ -0,0 +1,73 @@
/*
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.controller;
import org.libresonic.player.command.PodcastSettingsCommand;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* Controller for the page used to administrate the Podcast receiver.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastSettings")
public class PodcastSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private PodcastService podcastService;
@RequestMapping(method = RequestMethod.GET)
protected String formBackingObject(Model model) throws Exception {
PodcastSettingsCommand command = new PodcastSettingsCommand();
command.setInterval(String.valueOf(settingsService.getPodcastUpdateInterval()));
command.setEpisodeRetentionCount(String.valueOf(settingsService.getPodcastEpisodeRetentionCount()));
command.setEpisodeDownloadCount(String.valueOf(settingsService.getPodcastEpisodeDownloadCount()));
command.setFolder(settingsService.getPodcastFolder());
model.addAttribute("command",command);
return "podcastSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute PodcastSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
settingsService.setPodcastUpdateInterval(Integer.parseInt(command.getInterval()));
settingsService.setPodcastEpisodeRetentionCount(Integer.parseInt(command.getEpisodeRetentionCount()));
settingsService.setPodcastEpisodeDownloadCount(Integer.parseInt(command.getEpisodeDownloadCount()));
settingsService.setPodcastFolder(command.getFolder());
settingsService.save();
podcastService.schedule();
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:podcastSettings.view";
}
}
@@ -0,0 +1,76 @@
/*
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.controller;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
/**
* A proxy for external HTTP requests.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/proxy")
public class ProxyController {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = ServletRequestUtils.getRequiredStringParameter(request, "url");
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000)
.setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
InputStream in = null;
try (CloseableHttpClient client = HttpClients.createDefault()) {
try (CloseableHttpResponse resp = client.execute(method)) {
int statusCode = resp.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
response.sendError(statusCode);
} else {
in = resp.getEntity().getContent();
IOUtils.copy(in, response.getOutputStream());
}
}
} finally {
IOUtils.closeQuietly(in);
}
return null;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,258 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for the creating a random play queue.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/randomPlayQueue.view")
public class RandomPlayQueueController {
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.POST)
protected String handleRandomPlayQueue(
ModelMap model,
HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "size") int size,
@RequestParam(value = "genre", required = false) String genre,
@RequestParam(value = "year", required = false) String year,
@RequestParam(value = "songRating", required = false) String songRating,
@RequestParam(value = "lastPlayedValue", required = false) String lastPlayedValue,
@RequestParam(value = "lastPlayedComp", required = false) String lastPlayedComp,
@RequestParam(value = "albumRatingValue", required = false) Integer albumRatingValue,
@RequestParam(value = "albumRatingComp", required = false) String albumRatingComp,
@RequestParam(value = "playCountValue", required = false) Integer playCountValue,
@RequestParam(value = "playCountComp", required = false) String playCountComp,
@RequestParam(value = "format", required = false) String format,
@RequestParam(value = "autoRandom", required = false) String autoRandom
) throws Exception {
Integer fromYear = null;
Integer toYear = null;
Integer minAlbumRating = null;
Integer maxAlbumRating = null;
Integer minPlayCount = null;
Integer maxPlayCount = null;
Date minLastPlayedDate = null;
Date maxLastPlayedDate = null;
boolean doesShowStarredSongs = false;
boolean doesShowUnstarredSongs = false;
// Handle the genre filter
if (StringUtils.equalsIgnoreCase("any", genre)) {
genre = null;
}
// Handle the release year filter
if (!StringUtils.equalsIgnoreCase("any", year)) {
String[] tmp = StringUtils.split(year);
fromYear = Integer.parseInt(tmp[0]);
toYear = Integer.parseInt(tmp[1]);
}
// Handle the song rating filter
if (StringUtils.equalsIgnoreCase("any", songRating)) {
doesShowStarredSongs = true;
doesShowUnstarredSongs = true;
} else if (StringUtils.equalsIgnoreCase("starred", songRating)) {
doesShowStarredSongs = true;
doesShowUnstarredSongs = false;
} else if (StringUtils.equalsIgnoreCase("unstarred", songRating)) {
doesShowStarredSongs = false;
doesShowUnstarredSongs = true;
}
// Handle the last played date filter
Calendar lastPlayed = Calendar.getInstance();
lastPlayed.setTime(new Date());
switch (lastPlayedValue) {
case "any":
lastPlayed = null;
break;
case "1day":
lastPlayed.add(Calendar.DAY_OF_YEAR, -1);
break;
case "1week":
lastPlayed.add(Calendar.WEEK_OF_YEAR, -1);
break;
case "1month":
lastPlayed.add(Calendar.MONTH, -1);
break;
case "3months":
lastPlayed.add(Calendar.MONTH, -3);
break;
case "6months":
lastPlayed.add(Calendar.MONTH, -6);
break;
case "1year":
lastPlayed.add(Calendar.YEAR, -1);
break;
}
if (lastPlayed != null) {
switch (lastPlayedComp) {
case "lt":
minLastPlayedDate = null;
maxLastPlayedDate = lastPlayed.getTime();
break;
case "gt":
minLastPlayedDate = lastPlayed.getTime();
maxLastPlayedDate = null;
break;
}
}
// Handle the album rating filter
if (albumRatingValue != null) {
switch (albumRatingComp) {
case "lt":
minAlbumRating = null;
maxAlbumRating = albumRatingValue - 1;
break;
case "gt":
minAlbumRating = albumRatingValue + 1;
maxAlbumRating = null;
break;
case "le":
minAlbumRating = null;
maxAlbumRating = albumRatingValue;
break;
case "ge":
minAlbumRating = albumRatingValue;
maxAlbumRating = null;
break;
case "eq":
minAlbumRating = albumRatingValue;
maxAlbumRating = albumRatingValue;
break;
}
}
// Handle the play count filter
if (playCountValue != null) {
switch (playCountComp) {
case "lt":
minPlayCount = null;
maxPlayCount = playCountValue - 1;
break;
case "gt":
minPlayCount = playCountValue + 1;
maxPlayCount = null;
break;
case "le":
minPlayCount = null;
maxPlayCount = playCountValue;
break;
case "ge":
minPlayCount = playCountValue;
maxPlayCount = null;
break;
case "eq":
minPlayCount = playCountValue;
maxPlayCount = playCountValue;
break;
}
}
// Handle the format filter
if (StringUtils.equalsIgnoreCase(format, "any")) format = null;
// Handle the music folder filter
List<MusicFolder> musicFolders = getMusicFolders(request);
// Do we add to the current playlist or do we replace it?
boolean shouldAddToPlayList = request.getParameter("addToPlaylist") != null;
// Search the database using these criteria
RandomSearchCriteria criteria = new RandomSearchCriteria(
size,
genre,
fromYear,
toYear,
musicFolders,
minLastPlayedDate,
maxLastPlayedDate,
minAlbumRating,
maxAlbumRating,
minPlayCount,
maxPlayCount,
doesShowStarredSongs,
doesShowUnstarredSongs,
format
);
User user = securityService.getCurrentUser(request);
Player player = playerService.getPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
playQueue.addFiles(shouldAddToPlayList, mediaFileService.getRandomSongs(criteria, user.getUsername()));
if (autoRandom != null) {
playQueue.setRandomSearchCriteria(criteria);
}
// Render the 'reload' view to reload the play queue and the main page
List<ReloadFrame> reloadFrames = new ArrayList<>();
reloadFrames.add(new ReloadFrame("playQueue", "playQueue.view?"));
reloadFrames.add(new ReloadFrame("main", "more.view"));
Map<String, Object> map = new HashMap<>();
map.put("reloadFrames", reloadFrames);
model.addAttribute("model", map);
return "reload";
}
private List<MusicFolder> getMusicFolders(HttpServletRequest request) throws ServletRequestBindingException {
String username = securityService.getCurrentUsername(request);
Integer selectedMusicFolderId = ServletRequestUtils.getRequiredIntParameter(request, "musicFolderId");
if (selectedMusicFolderId == -1) {
selectedMusicFolderId = null;
}
return settingsService.getMusicFoldersForUser(username, selectedMusicFolderId);
}
}
@@ -0,0 +1,164 @@
package org.libresonic.player.controller;
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaResponse;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/recover")
public class RecoverController {
private static final Logger LOG = LoggerFactory.getLogger(RecoverController.class);
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView recover(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String usernameOrEmail = StringUtils.trimToNull(request.getParameter("usernameOrEmail"));
ReCaptcha captcha = ReCaptchaFactory.newSecureReCaptcha("6LcZ3OMSAAAAANkKMdFdaNopWu9iS03V-nLOuoiH",
"6LcZ3OMSAAAAAPaFg89mEzs-Ft0fIu7wxfKtkwmQ", false);
boolean showCaptcha = true;
if (usernameOrEmail != null) {
map.put("usernameOrEmail", usernameOrEmail);
User user = getUserByUsernameOrEmail(usernameOrEmail);
String challenge = request.getParameter("recaptcha_challenge_field");
String uresponse = request.getParameter("recaptcha_response_field");
ReCaptchaResponse captchaResponse = captcha.checkAnswer(request.getRemoteAddr(), challenge, uresponse);
if (!captchaResponse.isValid()) {
map.put("error", "recover.error.invalidcaptcha");
} else if (user == null) {
map.put("error", "recover.error.usernotfound");
} else if (user.getEmail() == null) {
map.put("error", "recover.error.noemail");
} else {
String password = RandomStringUtils.randomAlphanumeric(8);
if (emailPassword(password, user.getUsername(), user.getEmail())) {
map.put("sentTo", user.getEmail());
user.setLdapAuthenticated(false);
user.setPassword(password);
securityService.updateUser(user);
showCaptcha = false;
} else {
map.put("error", "recover.error.sendfailed");
}
}
}
if (showCaptcha) {
map.put("captcha", captcha.createRecaptchaHtml(null, null));
}
return new ModelAndView("recover", "model", map);
}
private User getUserByUsernameOrEmail(String usernameOrEmail) {
if (usernameOrEmail != null) {
User user = securityService.getUserByName(usernameOrEmail);
if (user != null) {
return user;
}
return securityService.getUserByEmail(usernameOrEmail);
}
return null;
}
/*
* e-mail user new password via configured Smtp server
*/
private boolean emailPassword(String password, String username, String email) {
/* Default to protocol smtp when SmtpEncryption is set to "None" */
String prot = "smtp";
if (settingsService.getSmtpServer() == null || settingsService.getSmtpServer().isEmpty()) {
LOG.warn("Can not send email; no Smtp server configured.");
return false;
}
Properties props = new Properties();
if (settingsService.getSmtpEncryption().equals("SSL/TLS")) {
prot = "smtps";
props.put("mail." + prot + ".ssl.enable", "true");
} else if (settingsService.getSmtpEncryption().equals("STARTTLS")) {
prot = "smtp";
props.put("mail." + prot + ".starttls.enable", "true");
}
props.put("mail." + prot + ".host", settingsService.getSmtpServer());
props.put("mail." + prot + ".port", settingsService.getSmtpPort());
/* use authentication when SmtpUser is configured */
if (settingsService.getSmtpUser() != null && !settingsService.getSmtpUser().isEmpty()) {
props.put("mail." + prot + ".auth", "true");
}
Session session = Session.getInstance(props, null);
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(settingsService.getSmtpFrom()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email));
message.setSubject("Libresonic Password");
message.setText("Hi there!\n\n" +
"You have requested to reset your Libresonic password. Please find your new login details below.\n\n" +
"Username: " + username + "\n" +
"Password: " + password + "\n\n" +
"--\n" +
"Your Libresonic server\n" +
"libresonic.org");
message.setSentDate(new Date());
Transport trans = session.getTransport(prot);
try {
if (props.get("mail." + prot + ".auth") != null && props.get("mail." + prot + ".auth").equals("true")) {
trans.connect(settingsService.getSmtpServer(), settingsService.getSmtpUser(), settingsService.getSmtpPassword());
} else {
trans.connect();
}
trans.sendMessage(message, message.getAllRecipients());
} finally {
trans.close();
}
return true;
} catch (Exception x) {
LOG.warn("Failed to send email.", x);
return false;
}
}
}
@@ -0,0 +1,53 @@
/*
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.controller;
/**
* Used in libresonic-servlet.xml to specify frame reloading.
*
* @author Sindre Mehus
*/
public class ReloadFrame {
private String frame;
private String view;
public ReloadFrame() {}
public ReloadFrame(String frame, String view) {
this.frame = frame;
this.view = view;
}
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getView() {
return view;
}
public void setView(String view) {
this.view = view;
}
}
@@ -0,0 +1,76 @@
/*
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.controller;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.VersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the right frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/right")
public class RightController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private VersionService versionService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView("right");
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
if (userSettings.isFinalVersionNotificationEnabled() && versionService.isNewFinalVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestFinalVersion());
} else if (userSettings.isBetaVersionNotificationEnabled() && versionService.isNewBetaVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestBetaVersion());
}
map.put("brand", settingsService.getBrand());
map.put("showNowPlaying", userSettings.isShowNowPlayingEnabled());
map.put("user", securityService.getCurrentUser(request));
result.addObject("model", map);
return result;
}
}
@@ -0,0 +1,104 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.SearchCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller for the search page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/search")
public class SearchController {
private static final int MATCH_COUNT = 25;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "search";
}
@ModelAttribute
protected void formBackingObject(HttpServletRequest request, Model model) throws Exception {
model.addAttribute("command",new SearchCommand());
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(HttpServletRequest request, HttpServletResponse response,@ModelAttribute("command") SearchCommand command, Model model)
throws Exception {
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
command.setUser(user);
command.setPartyModeEnabled(userSettings.isPartyModeEnabled());
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
String query = StringUtils.trimToNull(command.getQuery());
if (query != null) {
SearchCriteria criteria = new SearchCriteria();
criteria.setCount(MATCH_COUNT);
criteria.setQuery(query);
SearchResult artists = searchService.search(criteria, musicFolders, SearchService.IndexType.ARTIST);
command.setArtists(artists.getMediaFiles());
SearchResult albums = searchService.search(criteria, musicFolders, SearchService.IndexType.ALBUM);
command.setAlbums(albums.getMediaFiles());
SearchResult songs = searchService.search(criteria, musicFolders, SearchService.IndexType.SONG);
command.setSongs(songs.getMediaFiles());
command.setPlayer(playerService.getPlayer(request, response));
}
return "search";
}
}
@@ -0,0 +1,63 @@
/*
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.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for updating music file metadata.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/setMusicFileInfo")
public class SetMusicFileInfoController {
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
String action = request.getParameter("action");
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if ("comment".equals(action)) {
mediaFile.setComment(StringUtil.toHtml(request.getParameter("comment")));
mediaFileService.updateMediaFile(mediaFile);
}
String url = "main.view?id=" + id;
return new ModelAndView(new RedirectView(url));
}
}
@@ -0,0 +1,66 @@
/*
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.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for updating music file ratings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/setRating")
public class SetRatingController {
@Autowired
private RatingService ratingService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Integer rating = ServletRequestUtils.getIntParameter(request, "rating");
if (rating == 0) {
rating = null;
}
MediaFile mediaFile = mediaFileService.getMediaFile(id);
String username = securityService.getCurrentUsername(request);
ratingService.setRatingForUser(username, mediaFile, rating);
return new ModelAndView(new RedirectView("main.view?id=" + id));
}
}
@@ -0,0 +1,56 @@
/*
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.controller;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the main settings page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/settings")
public class SettingsController {
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
User user = securityService.getCurrentUser(request);
// Redirect to music folder settings if admin.
String view = user.isAdminRole() ? "musicFolderSettings" : "personalSettings";
return new ModelAndView(new RedirectView(view));
}
}
@@ -0,0 +1,127 @@
/*
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.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Share;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for sharing music on Twitter, Facebook etc.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/createShare")
public class ShareManagementController {
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SettingsService settingsService;
@Autowired
private ShareService shareService;
@Autowired
private PlayerService playerService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView createShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
List<MediaFile> files = getMediaFiles(request);
MediaFile dir = null;
if (!files.isEmpty()) {
dir = files.get(0);
if (!dir.isAlbum()) {
dir = mediaFileService.getParentOf(dir);
}
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("dir", dir);
map.put("user", securityService.getCurrentUser(request));
Share share = shareService.createShare(request, files);
String description = getDescription(request);
if (description != null) {
share.setDescription(description);
shareService.updateShare(share);
}
map.put("playUrl", shareService.getShareUrl(request, share));
return new ModelAndView("createShare", "model", map);
}
private String getDescription(HttpServletRequest request) throws ServletRequestBindingException {
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
return playlistId == null ? null : playlistService.getPlaylist(playlistId).getName();
}
private List<MediaFile> getMediaFiles(HttpServletRequest request) throws Exception {
Integer id = ServletRequestUtils.getIntParameter(request, "id");
String playerId = request.getParameter("player");
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
List<MediaFile> result = new ArrayList<>();
if (id != null) {
MediaFile album = mediaFileService.getMediaFile(id);
int[] indexes = ServletRequestUtils.getIntParameters(request, "i");
if (indexes.length == 0) {
return Arrays.asList(album);
}
List<MediaFile> children = mediaFileService.getChildrenOf(album, true, false, true);
for (int index : indexes) {
result.add(children.get(index));
}
}
else if (playerId != null) {
Player player = playerService.getPlayerById(playerId);
PlayQueue playQueue = player.getPlayQueue();
result = playQueue.getFiles();
}
else if (playlistId != null) {
result = playlistService.getFilesInPlaylist(playlistId);
}
return result;
}
}
@@ -0,0 +1,170 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Share;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.ShareService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* Controller for the page used to administrate the set of shared media.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/shareSettings")
public class ShareSettingsController {
@Autowired
private ShareService shareService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(HttpServletRequest request, Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("shareInfos", getShareInfos(request));
map.put("user", securityService.getCurrentUser(request));
model.addAttribute("model", map);
return "shareSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:shareSettings.view";
}
private void handleParameters(HttpServletRequest request) {
User user = securityService.getCurrentUser(request);
for (Share share : shareService.getSharesForUser(user)) {
int id = share.getId();
String description = getParameter(request, "description", id);
boolean delete = getParameter(request, "delete", id) != null;
String expireIn = getParameter(request, "expireIn", id);
if (delete) {
shareService.deleteShare(id);
} else {
if (expireIn != null) {
share.setExpires(parseExpireIn(expireIn));
}
share.setDescription(description);
shareService.updateShare(share);
}
}
boolean deleteExpired = ServletRequestUtils.getBooleanParameter(request, "deleteExpired", false);
if (deleteExpired) {
Date now = new Date();
for (Share share : shareService.getSharesForUser(user)) {
Date expires = share.getExpires();
if (expires != null && expires.before(now)) {
shareService.deleteShare(share.getId());
}
}
}
}
private List<ShareInfo> getShareInfos(HttpServletRequest request) {
List<ShareInfo> result = new ArrayList<ShareInfo>();
User user = securityService.getCurrentUser(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
for (Share share : shareService.getSharesForUser(user)) {
List<MediaFile> files = shareService.getSharedFiles(share.getId(), musicFolders);
if (!files.isEmpty()) {
MediaFile file = files.get(0);
result.add(new ShareInfo(shareService.getShareUrl(request, share), share, file.isDirectory() ? file :
mediaFileService
.getParentOf(file)));
}
}
return result;
}
private String getParameter(HttpServletRequest request, String name, int id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
private Date parseExpireIn(String expireIn) {
int days = Integer.parseInt(expireIn);
if (days == 0) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, days);
return calendar.getTime();
}
public static class ShareInfo {
private final String shareUrl;
private final Share share;
private final MediaFile dir;
public ShareInfo(String shareUrl, Share share, MediaFile dir) {
this.shareUrl = shareUrl;
this.share = share;
this.dir = dir;
}
public Share getShare() {
return share;
}
public String getShareUrl() {
return shareUrl;
}
public MediaFile getDir() {
return dir;
}
}
}
@@ -0,0 +1,95 @@
/*
* 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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.SonosService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the Sonos music service settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/sonosSettings")
public class SonosSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private SonosService sonosService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("sonosEnabled", settingsService.isSonosEnabled());
map.put("sonosServiceName", settingsService.getSonosServiceName());
model.addAttribute("model", map);
return "sonosSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:sonosSettings.view";
}
private void handleParameters(HttpServletRequest request) {
boolean sonosEnabled = ServletRequestUtils.getBooleanParameter(request, "sonosEnabled", false);
String sonosServiceName = StringUtils.trimToNull(request.getParameter("sonosServiceName"));
if (sonosServiceName == null) {
sonosServiceName = "Libresonic";
}
settingsService.setSonosEnabled(sonosEnabled);
settingsService.setSonosServiceName(sonosServiceName);
settingsService.save();
sonosService.setMusicServiceEnabled(false, NetworkService.getBaseUrl(request));
sonosService.setMusicServiceEnabled(sonosEnabled, NetworkService.getBaseUrl(request));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSonosService(SonosService sonosService) {
this.sonosService = sonosService;
}
}
@@ -0,0 +1,95 @@
/*
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.controller;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for showing a user's starred items.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/starred")
public class StarredController {
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileDao mediaFileDao;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
UserSettings userSettings = settingsService.getUserSettings(username);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> artists = mediaFileDao.getStarredDirectories(0, Integer.MAX_VALUE, username, musicFolders);
List<MediaFile> albums = mediaFileDao.getStarredAlbums(0, Integer.MAX_VALUE, username, musicFolders);
List<MediaFile> files = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
mediaFileService.populateStarredDate(artists, username);
mediaFileService.populateStarredDate(albums, username);
mediaFileService.populateStarredDate(files, username);
List<MediaFile> songs = new ArrayList<>();
List<MediaFile> videos = new ArrayList<>();
for (MediaFile file : files) {
(file.isVideo() ? videos : songs).add(file);
}
map.put("user", user);
map.put("partyModeEnabled", userSettings.isPartyModeEnabled());
map.put("player", playerService.getPlayer(request, response));
map.put("coverArtSize", CoverArtScheme.MEDIUM.getSize());
map.put("artists", artists);
map.put("albums", albums);
map.put("songs", songs);
map.put("videos", videos);
return new ModelAndView("starred","model",map);
}
}
@@ -0,0 +1,161 @@
/*
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.controller;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.Range;
import org.jfree.data.time.*;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.StatusService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Controller for generating a chart showing bitrate vs time.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/statusChart")
public class StatusChartController extends AbstractChartController {
@Autowired
private StatusService statusService;
public static final int IMAGE_WIDTH = 350;
public static final int IMAGE_HEIGHT = 150;
@RequestMapping(method = RequestMethod.GET)
public synchronized ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String type = request.getParameter("type");
int index = Integer.parseInt(request.getParameter("index"));
List<TransferStatus> statuses = Collections.emptyList();
if ("stream".equals(type)) {
statuses = statusService.getAllStreamStatuses();
} else if ("download".equals(type)) {
statuses = statusService.getAllDownloadStatuses();
} else if ("upload".equals(type)) {
statuses = statusService.getAllUploadStatuses();
}
if (index < 0 || index >= statuses.size()) {
return null;
}
TransferStatus status = statuses.get(index);
TimeSeries series = new TimeSeries("Kbps", Millisecond.class);
TransferStatus.SampleHistory history = status.getHistory();
long to = System.currentTimeMillis();
long from = to - status.getHistoryLengthMillis();
Range range = new DateRange(from, to);
if (!history.isEmpty()) {
TransferStatus.Sample previous = history.get(0);
for (int i = 1; i < history.size(); i++) {
TransferStatus.Sample sample = history.get(i);
long elapsedTimeMilis = sample.getTimestamp() - previous.getTimestamp();
long bytesStreamed = Math.max(0L, sample.getBytesTransfered() - previous.getBytesTransfered());
double kbps = (8.0 * bytesStreamed / 1024.0) / (elapsedTimeMilis / 1000.0);
series.addOrUpdate(new Millisecond(new Date(sample.getTimestamp())), kbps);
previous = sample;
}
}
// Compute moving average.
series = MovingAverage.createMovingAverage(series, "Kbps", 20000, 5000);
// Find min and max values.
double min = 100;
double max = 250;
for (Object obj : series.getItems()) {
TimeSeriesDataItem item = (TimeSeriesDataItem) obj;
double value = item.getValue().doubleValue();
if (item.getPeriod().getFirstMillisecond() > from) {
min = Math.min(min, value);
max = Math.max(max, value);
}
}
// Add 10% to max value.
max *= 1.1D;
// Subtract 10% from min value.
min *= 0.9D;
TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(series);
JFreeChart chart = ChartFactory.createTimeSeriesChart(null, null, null, dataset, false, false, false);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
Paint background = new GradientPaint(0, 0, Color.lightGray, 0, IMAGE_HEIGHT, Color.white);
plot.setBackgroundPaint(background);
XYItemRenderer renderer = plot.getRendererForDataset(dataset);
renderer.setSeriesPaint(0, Color.blue.darker());
renderer.setSeriesStroke(0, new BasicStroke(2f));
// Set theme-specific colors.
Color bgColor = getBackground(request);
Color fgColor = getForeground(request);
chart.setBackgroundPaint(bgColor);
ValueAxis domainAxis = plot.getDomainAxis();
domainAxis.setRange(range);
domainAxis.setTickLabelPaint(fgColor);
domainAxis.setTickMarkPaint(fgColor);
domainAxis.setAxisLinePaint(fgColor);
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setRange(new Range(min, max));
rangeAxis.setTickLabelPaint(fgColor);
rangeAxis.setTickMarkPaint(fgColor);
rangeAxis.setAxisLinePaint(fgColor);
ChartUtilities.writeChartAsPNG(response.getOutputStream(), chart, IMAGE_WIDTH, IMAGE_HEIGHT);
return null;
}
}
@@ -0,0 +1,140 @@
/*
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.controller;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for the status page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/status")
public class StatusController {
@Autowired
private StatusService statusService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
List<TransferStatus> streamStatuses = statusService.getAllStreamStatuses();
List<TransferStatus> downloadStatuses = statusService.getAllDownloadStatuses();
List<TransferStatus> uploadStatuses = statusService.getAllUploadStatuses();
Locale locale = RequestContextUtils.getLocale(request);
List<TransferStatusHolder> transferStatuses = new ArrayList<>();
for (int i = 0; i < streamStatuses.size(); i++) {
long minutesAgo = streamStatuses.get(i).getMillisSinceLastUpdate() / 1000L / 60L;
if (minutesAgo < 60L) {
transferStatuses.add(new TransferStatusHolder(streamStatuses.get(i), true, false, false, i, locale));
}
}
for (int i = 0; i < downloadStatuses.size(); i++) {
transferStatuses.add(new TransferStatusHolder(downloadStatuses.get(i), false, true, false, i, locale));
}
for (int i = 0; i < uploadStatuses.size(); i++) {
transferStatuses.add(new TransferStatusHolder(uploadStatuses.get(i), false, false, true, i, locale));
}
map.put("transferStatuses", transferStatuses);
map.put("chartWidth", StatusChartController.IMAGE_WIDTH);
map.put("chartHeight", StatusChartController.IMAGE_HEIGHT);
return new ModelAndView("status","model",map);
}
public static class TransferStatusHolder {
private TransferStatus transferStatus;
private boolean isStream;
private boolean isDownload;
private boolean isUpload;
private int index;
private Locale locale;
TransferStatusHolder(TransferStatus transferStatus, boolean isStream, boolean isDownload, boolean isUpload,
int index, Locale locale) {
this.transferStatus = transferStatus;
this.isStream = isStream;
this.isDownload = isDownload;
this.isUpload = isUpload;
this.index = index;
this.locale = locale;
}
public boolean isStream() {
return isStream;
}
public boolean isDownload() {
return isDownload;
}
public boolean isUpload() {
return isUpload;
}
public int getIndex() {
return index;
}
public Player getPlayer() {
return transferStatus.getPlayer();
}
public String getPlayerType() {
Player player = transferStatus.getPlayer();
return player == null ? null : player.getType();
}
public String getUsername() {
Player player = transferStatus.getPlayer();
return player == null ? null : player.getUsername();
}
public String getPath() {
return FileUtil.getShortPath(transferStatus.getFile());
}
public String getBytes() {
return StringUtil.formatBytes(transferStatus.getBytesTransfered(), locale);
}
}
}
@@ -0,0 +1,428 @@
/*
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.controller;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.io.PlayQueueInputStream;
import org.libresonic.player.io.RangeOutputStream;
import org.libresonic.player.io.ShoutCastOutputStream;
import org.libresonic.player.security.JWTAuthenticationToken;
import org.libresonic.player.service.*;
import org.libresonic.player.service.sonos.SonosHelper;
import org.libresonic.player.util.HttpRange;
import org.libresonic.player.util.StringUtil;
import org.libresonic.player.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A controller which streams the content of a {@link org.libresonic.player.domain.PlayQueue} to a remote
* {@link Player}.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/stream/**", "/ext/stream/**"})
public class StreamController {
private static final Logger LOG = LoggerFactory.getLogger(StreamController.class);
@Autowired
private StatusService statusService;
@Autowired
private PlayerService playerService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private AudioScrobblerService audioScrobblerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
TransferStatus status = null;
PlayQueueInputStream in = null;
Player player = playerService.getPlayer(request, response, false, true);
User user = securityService.getUserByName(player.getUsername());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try {
if (!(authentication instanceof JWTAuthenticationToken) && !user.isStreamRole()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Streaming is forbidden for user " + user.getUsername());
return null;
}
// If "playlist" request parameter is set, this is a Podcast request. In that case, create a separate
// play queue (in order to support multiple parallel Podcast streams).
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
boolean isPodcast = playlistId != null;
if (isPodcast) {
PlayQueue playQueue = new PlayQueue();
playQueue.addFiles(false, playlistService.getFilesInPlaylist(playlistId));
player.setPlayQueue(playQueue);
Util.setContentLength(response, playQueue.length());
LOG.info("Incoming Podcast request for playlist " + playlistId);
}
response.setHeader("Access-Control-Allow-Origin", "*");
String contentType = StringUtil.getMimeType(request.getParameter("suffix"));
response.setContentType(contentType);
String preferredTargetFormat = request.getParameter("format");
Integer maxBitRate = ServletRequestUtils.getIntParameter(request, "maxBitRate");
if (Integer.valueOf(0).equals(maxBitRate)) {
maxBitRate = null;
}
VideoTranscodingSettings videoTranscodingSettings = null;
// Is this a request for a single file (typically from the embedded Flash player)?
// In that case, create a separate playlist (in order to support multiple parallel streams).
// Also, enable partial download (HTTP byte range).
MediaFile file = getSingleFile(request);
boolean isSingleFile = file != null;
HttpRange range = null;
if (isSingleFile) {
if (!(authentication instanceof JWTAuthenticationToken) && !securityService.isFolderAccessAllowed(file, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + file.getId() + " is forbidden for user " + user.getUsername());
return null;
}
// Update the index of the currently playing media file. At
// this point we haven't yet modified the play queue to support
// multiple streams, so the current play queue is the real one.
int currentIndex = player.getPlayQueue().getFiles().indexOf(file);
player.getPlayQueue().setIndex(currentIndex);
// Create a new, fake play queue that only contains the
// currently playing media file, in case multiple streams want
// to use the same player.
PlayQueue playQueue = new PlayQueue();
playQueue.addFiles(true, file);
player.setPlayQueue(playQueue);
if (!file.isVideo()) {
response.setIntHeader("ETag", file.getId());
response.setHeader("Accept-Ranges", "bytes");
}
TranscodingService.Parameters parameters = transcodingService.getParameters(file, player, maxBitRate, preferredTargetFormat, null);
long fileLength = getFileLength(parameters);
boolean isConversion = parameters.isDownsample() || parameters.isTranscode();
boolean estimateContentLength = ServletRequestUtils.getBooleanParameter(request, "estimateContentLength", false);
boolean isHls = ServletRequestUtils.getBooleanParameter(request, "hls", false);
range = getRange(request, file);
if (range != null && !file.isVideo()) {
LOG.info("Got HTTP range: " + range);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
Util.setContentLength(response, range.isClosed() ? range.size() : fileLength - range.getFirstBytePos());
long lastBytePos = range.getLastBytePos() != null ? range.getLastBytePos() : fileLength - 1;
response.setHeader("Content-Range", "bytes " + range.getFirstBytePos() + "-" + lastBytePos + "/" + fileLength);
} else if (!isHls && (!isConversion || estimateContentLength)) {
Util.setContentLength(response, fileLength);
}
if (isHls) {
response.setContentType(StringUtil.getMimeType("ts")); // HLS is always MPEG TS.
} else {
String transcodedSuffix = transcodingService.getSuffix(player, file, preferredTargetFormat);
boolean sonos = SonosHelper.LIBRESONIC_CLIENT_ID.equals(player.getClientId());
response.setContentType(StringUtil.getMimeType(transcodedSuffix, sonos));
setContentDuration(response, file);
}
if (file.isVideo() || isHls) {
videoTranscodingSettings = createVideoTranscodingSettings(file, request);
}
}
if (request.getMethod().equals("HEAD")) {
return null;
}
// Terminate any other streams to this player.
if (!isPodcast && !isSingleFile) {
for (TransferStatus streamStatus : statusService.getStreamStatusesForPlayer(player)) {
if (streamStatus.isActive()) {
streamStatus.terminate();
}
}
}
status = statusService.createStreamStatus(player);
in = new PlayQueueInputStream(player, status, maxBitRate, preferredTargetFormat, videoTranscodingSettings, transcodingService,
audioScrobblerService, mediaFileService, searchService);
OutputStream out = RangeOutputStream.wrap(response.getOutputStream(), range);
// Enabled SHOUTcast, if requested.
boolean isShoutCastRequested = "1".equals(request.getHeader("icy-metadata"));
if (isShoutCastRequested && !isSingleFile) {
response.setHeader("icy-metaint", "" + ShoutCastOutputStream.META_DATA_INTERVAL);
response.setHeader("icy-notice1", "This stream is served using Libresonic");
response.setHeader("icy-notice2", "Libresonic - Free media streamer - libresonic.org");
response.setHeader("icy-name", "Libresonic");
response.setHeader("icy-genre", "Mixed");
response.setHeader("icy-url", "http://libresonic.org/");
out = new ShoutCastOutputStream(out, player.getPlayQueue(), settingsService);
}
final int BUFFER_SIZE = 2048;
byte[] buf = new byte[BUFFER_SIZE];
while (true) {
// Check if stream has been terminated.
if (status.terminated()) {
return null;
}
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
if (isPodcast || isSingleFile) {
break;
} else {
sendDummy(buf, out);
}
} else {
int n = in.read(buf);
if (n == -1) {
if (isPodcast || isSingleFile) {
break;
} else {
sendDummy(buf, out);
}
} else {
out.write(buf, 0, n);
}
}
}
} finally {
if (status != null) {
securityService.updateUserByteCounts(user, status.getBytesTransfered(), 0L, 0L);
statusService.removeStreamStatus(status);
}
IOUtils.closeQuietly(in);
}
return null;
}
private void setContentDuration(HttpServletResponse response, MediaFile file) {
if (file.getDurationSeconds() != null) {
response.setHeader("X-Content-Duration", String.format("%.1f", file.getDurationSeconds().doubleValue()));
}
}
private MediaFile getSingleFile(HttpServletRequest request) throws ServletRequestBindingException {
String path = request.getParameter("path");
if (path != null) {
return mediaFileService.getMediaFile(path);
}
Integer id = ServletRequestUtils.getIntParameter(request, "id");
if (id != null) {
return mediaFileService.getMediaFile(id);
}
return null;
}
private long getFileLength(TranscodingService.Parameters parameters) {
MediaFile file = parameters.getMediaFile();
if (!parameters.isDownsample() && !parameters.isTranscode()) {
return file.getFileSize();
}
Integer duration = file.getDurationSeconds();
Integer maxBitRate = parameters.getMaxBitRate();
if (duration == null) {
LOG.warn("Unknown duration for " + file + ". Unable to estimate transcoded size.");
return file.getFileSize();
}
if (maxBitRate == null) {
LOG.error("Unknown bit rate for " + file + ". Unable to estimate transcoded size.");
return file.getFileSize();
}
return duration * maxBitRate * 1000L / 8L;
}
private HttpRange getRange(HttpServletRequest request, MediaFile file) {
// First, look for "Range" HTTP header.
HttpRange range = HttpRange.valueOf(request.getHeader("Range"));
if (range != null) {
return range;
}
// Second, look for "offsetSeconds" request parameter.
String offsetSeconds = request.getParameter("offsetSeconds");
range = parseAndConvertOffsetSeconds(offsetSeconds, file);
if (range != null) {
return range;
}
return null;
}
private HttpRange parseAndConvertOffsetSeconds(String offsetSeconds, MediaFile file) {
if (offsetSeconds == null) {
return null;
}
try {
Integer duration = file.getDurationSeconds();
Long fileSize = file.getFileSize();
if (duration == null || fileSize == null) {
return null;
}
float offset = Float.parseFloat(offsetSeconds);
// Convert from time offset to byte offset.
long byteOffset = (long) (fileSize * (offset / duration));
return new HttpRange(byteOffset, null);
} catch (Exception x) {
LOG.error("Failed to parse and convert time offset: " + offsetSeconds, x);
return null;
}
}
private VideoTranscodingSettings createVideoTranscodingSettings(MediaFile file, HttpServletRequest request) throws ServletRequestBindingException {
Integer existingWidth = file.getWidth();
Integer existingHeight = file.getHeight();
Integer maxBitRate = ServletRequestUtils.getIntParameter(request, "maxBitRate");
int timeOffset = ServletRequestUtils.getIntParameter(request, "timeOffset", 0);
int defaultDuration = file.getDurationSeconds() == null ? Integer.MAX_VALUE : file.getDurationSeconds() - timeOffset;
int duration = ServletRequestUtils.getIntParameter(request, "duration", defaultDuration);
boolean hls = ServletRequestUtils.getBooleanParameter(request, "hls", false);
Dimension dim = getRequestedVideoSize(request.getParameter("size"));
if (dim == null) {
dim = getSuitableVideoSize(existingWidth, existingHeight, maxBitRate);
}
return new VideoTranscodingSettings(dim.width, dim.height, timeOffset, duration, hls);
}
protected Dimension getRequestedVideoSize(String sizeSpec) {
if (sizeSpec == null) {
return null;
}
Pattern pattern = Pattern.compile("^(\\d+)x(\\d+)$");
Matcher matcher = pattern.matcher(sizeSpec);
if (matcher.find()) {
int w = Integer.parseInt(matcher.group(1));
int h = Integer.parseInt(matcher.group(2));
if (w >= 0 && h >= 0 && w <= 2000 && h <= 2000) {
return new Dimension(w, h);
}
}
return null;
}
protected Dimension getSuitableVideoSize(Integer existingWidth, Integer existingHeight, Integer maxBitRate) {
if (maxBitRate == null) {
return new Dimension(400, 224);
}
int w;
if (maxBitRate < 400) {
w = 400;
} else if (maxBitRate < 600) {
w = 480;
} else if (maxBitRate < 1800) {
w = 640;
} else {
w = 960;
}
int h = even(w * 9 / 16);
if (existingWidth == null || existingHeight == null) {
return new Dimension(w, h);
}
if (existingWidth < w || existingHeight < h) {
return new Dimension(even(existingWidth), even(existingHeight));
}
double aspectRate = existingWidth.doubleValue() / existingHeight.doubleValue();
h = (int) Math.round(w / aspectRate);
return new Dimension(even(w), even(h));
}
// Make sure width and height are multiples of two, as some versions of ffmpeg require it.
private int even(int size) {
return size + (size % 2);
}
/**
* Feed the other end with some dummy data to keep it from reconnecting.
*/
private void sendDummy(byte[] buf, OutputStream out) throws IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException x) {
LOG.warn("Interrupted in sleep.", x);
}
Arrays.fill(buf, (byte) 0xFF);
out.write(buf);
out.flush();
}
}
@@ -0,0 +1,29 @@
package org.libresonic.player.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/test")
public class TestController {
private static final Logger LOG = LoggerFactory.getLogger(TestController.class);
@RequestMapping(method = {RequestMethod.GET})
public ModelAndView test(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("test");
}
}
@@ -0,0 +1,64 @@
/*
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.controller;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the top frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/top")
public class TopController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
map.put("user", user);
map.put("showSideBar", userSettings.isShowSideBar());
map.put("showAvatar", userSettings.getAvatarScheme() != AvatarScheme.NONE);
return new ModelAndView("top","model", map);
}
}
@@ -0,0 +1,143 @@
/*
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.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.Transcoding;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.TranscodingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the set of transcoding configurations.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/transcodingSettings")
public class TranscodingSettingsController {
@Autowired
private TranscodingService transcodingService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("transcodings", transcodingService.getAllTranscodings());
map.put("transcodeDirectory", transcodingService.getTranscodeDirectory());
map.put("downsampleCommand", settingsService.getDownsamplingCommand());
map.put("hlsCommand", settingsService.getHlsCommand());
map.put("brand", settingsService.getBrand());
model.addAttribute("model", map);
return "transcodingSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
String error = handleParameters(request, redirectAttributes);
if(error != null) {
redirectAttributes.addFlashAttribute("settings_toast", true);
}
redirectAttributes.addFlashAttribute("error", error);
return "redirect:transcodingSettings.view";
}
private String handleParameters(HttpServletRequest request, RedirectAttributes redirectAttributes) {
for (Transcoding transcoding : transcodingService.getAllTranscodings()) {
Integer id = transcoding.getId();
String name = getParameter(request, "name", id);
String sourceFormats = getParameter(request, "sourceFormats", id);
String targetFormat = getParameter(request, "targetFormat", id);
String step1 = getParameter(request, "step1", id);
String step2 = getParameter(request, "step2", id);
boolean delete = getParameter(request, "delete", id) != null;
if (delete) {
transcodingService.deleteTranscoding(id);
} else if (name == null) {
return "transcodingsettings.noname";
} else if (sourceFormats == null) {
return "transcodingsettings.nosourceformat";
} else if (targetFormat == null) {
return "transcodingsettings.notargetformat";
} else if (step1 == null) {
return "transcodingsettings.nostep1";
} else {
transcoding.setName(name);
transcoding.setSourceFormats(sourceFormats);
transcoding.setTargetFormat(targetFormat);
transcoding.setStep1(step1);
transcoding.setStep2(step2);
transcodingService.updateTranscoding(transcoding);
}
}
String name = StringUtils.trimToNull(request.getParameter("name"));
String sourceFormats = StringUtils.trimToNull(request.getParameter("sourceFormats"));
String targetFormat = StringUtils.trimToNull(request.getParameter("targetFormat"));
String step1 = StringUtils.trimToNull(request.getParameter("step1"));
String step2 = StringUtils.trimToNull(request.getParameter("step2"));
boolean defaultActive = request.getParameter("defaultActive") != null;
if (name != null || sourceFormats != null || targetFormat != null || step1 != null || step2 != null) {
Transcoding transcoding = new Transcoding(null, name, sourceFormats, targetFormat, step1, step2, null, defaultActive);
String error = null;
if (name == null) {
error = "transcodingsettings.noname";
} else if (sourceFormats == null) {
error = "transcodingsettings.nosourceformat";
} else if (targetFormat == null) {
error = "transcodingsettings.notargetformat";
} else if (step1 == null) {
error = "transcodingsettings.nostep1";
} else {
transcodingService.createTranscoding(transcoding);
}
if(error != null) {
redirectAttributes.addAttribute("newTranscoding", transcoding);
return error;
}
}
settingsService.setDownsamplingCommand(StringUtils.trim(request.getParameter("downsampleCommand")));
settingsService.setHlsCommand(StringUtils.trim(request.getParameter("hlsCommand")));
settingsService.save();
return null;
}
private String getParameter(HttpServletRequest request, String name, Integer id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
}
@@ -0,0 +1,268 @@
/*
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.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.upload.MonitoredDiskFileItemFactory;
import org.libresonic.player.upload.UploadListener;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
/**
* Controller which receives uploaded files.
*
* @author Sindre Mehus
*/
@org.springframework.stereotype.Controller
@RequestMapping("/upload")
public class UploadController {
private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private SettingsService settingsService;
public static final String UPLOAD_STATUS = "uploadStatus";
@RequestMapping(method = { RequestMethod.POST })
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
List<File> uploadedFiles = new ArrayList<>();
List<File> unzippedFiles = new ArrayList<>();
TransferStatus status = null;
try {
status = statusService.createUploadStatus(playerService.getPlayer(request, response, false, false));
status.setBytesTotal(request.getContentLength());
request.getSession().setAttribute(UPLOAD_STATUS, status);
// Check that we have a file upload request
if (!ServletFileUpload.isMultipartContent(request)) {
throw new Exception("Illegal request.");
}
File dir = null;
boolean unzip = false;
UploadListener listener = new UploadListenerImpl(status);
FileItemFactory factory = new MonitoredDiskFileItemFactory(listener);
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
// First, look for "dir" and "unzip" parameters.
for (Object o : items) {
FileItem item = (FileItem) o;
if (item.isFormField() && "dir".equals(item.getFieldName())) {
dir = new File(item.getString());
} else if (item.isFormField() && "unzip".equals(item.getFieldName())) {
unzip = true;
}
}
if (dir == null) {
throw new Exception("Missing 'dir' parameter.");
}
// Look for file items.
for (Object o : items) {
FileItem item = (FileItem) o;
if (!item.isFormField()) {
String fileName = item.getName();
if (fileName.trim().length() > 0) {
File targetFile = new File(dir, new File(fileName).getName());
if (!securityService.isUploadAllowed(targetFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(targetFile.getPath()));
}
if (!dir.exists()) {
dir.mkdirs();
}
item.write(targetFile);
uploadedFiles.add(targetFile);
LOG.info("Uploaded " + targetFile);
if (unzip && targetFile.getName().toLowerCase().endsWith(".zip")) {
unzip(targetFile, unzippedFiles);
}
}
}
}
} catch (Exception x) {
LOG.warn("Uploading failed.", x);
map.put("exception", x);
} finally {
if (status != null) {
statusService.removeUploadStatus(status);
request.getSession().removeAttribute(UPLOAD_STATUS);
User user = securityService.getCurrentUser(request);
securityService.updateUserByteCounts(user, 0L, 0L, status.getBytesTransfered());
}
}
map.put("uploadedFiles", uploadedFiles);
map.put("unzippedFiles", unzippedFiles);
return new ModelAndView("upload","model",map);
}
private void unzip(File file, List<File> unzippedFiles) throws Exception {
LOG.info("Unzipping " + file);
ZipFile zipFile = new ZipFile(file);
try {
Enumeration<?> entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
File entryFile = new File(file.getParentFile(), entry.getName());
if (!entry.isDirectory()) {
if (!securityService.isUploadAllowed(entryFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(entryFile.getPath()));
}
entryFile.getParentFile().mkdirs();
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = zipFile.getInputStream(entry);
outputStream = new FileOutputStream(entryFile);
byte[] buf = new byte[8192];
while (true) {
int n = inputStream.read(buf);
if (n == -1) {
break;
}
outputStream.write(buf, 0, n);
}
LOG.info("Unzipped " + entryFile);
unzippedFiles.add(entryFile);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
}
zipFile.close();
file.delete();
} finally {
zipFile.close();
}
}
/**
* Receives callbacks as the file upload progresses.
*/
private class UploadListenerImpl implements UploadListener {
private TransferStatus status;
private long start;
private UploadListenerImpl(TransferStatus status) {
this.status = status;
start = System.currentTimeMillis();
}
public void start(String fileName) {
status.setFile(new File(fileName));
}
public void bytesRead(long bytesRead) {
// Throttle bitrate.
long byteCount = status.getBytesTransfered() + bytesRead;
long bitCount = byteCount * 8L;
float elapsedMillis = Math.max(1, System.currentTimeMillis() - start);
float elapsedSeconds = elapsedMillis / 1000.0F;
long maxBitsPerSecond = getBitrateLimit();
status.setBytesTransfered(byteCount);
if (maxBitsPerSecond > 0) {
float sleepMillis = 1000.0F * (bitCount / maxBitsPerSecond - elapsedSeconds);
if (sleepMillis > 0) {
try {
Thread.sleep((long) sleepMillis);
} catch (InterruptedException x) {
LOG.warn("Failed to sleep.", x);
}
}
}
}
private long getBitrateLimit() {
return 1024L * settingsService.getUploadBitrateLimit() / Math.max(1, statusService.getAllUploadStatuses().size());
}
}
}

Some files were not shown because too many files have changed in this diff Show More