File System Renames (No content changes)
Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
@@ -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) + " – " + 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;
|
||||
}
|
||||
|
||||
}
|
||||
+179
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -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));
|
||||
}
|
||||
}
|
||||
+29
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+106
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+142
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -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;
|
||||
}
|
||||
}
|
||||
+94
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+152
@@ -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);
|
||||
}
|
||||
}
|
||||
+132
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
This file is part of Libresonic.
|
||||
|
||||
Libresonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Libresonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+120
@@ -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;
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
This file is part of Libresonic.
|
||||
|
||||
Libresonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Libresonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 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));
|
||||
}
|
||||
}
|
||||
+84
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+193
@@ -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;
|
||||
}
|
||||
}
|
||||
+150
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
This file is part of Libresonic.
|
||||
|
||||
Libresonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Libresonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Libresonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -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
+258
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+127
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+170
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+161
@@ -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);
|
||||
}
|
||||
}
|
||||
+143
@@ -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
Reference in New Issue
Block a user