First commit

This commit is contained in:
Sindre Mehus
2016-05-01 17:57:16 +02:00
commit a40ad1f7ef
1340 changed files with 107457 additions and 0 deletions
@@ -0,0 +1,246 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic;
import net.sourceforge.subsonic.domain.Version;
import net.sourceforge.subsonic.service.*;
import net.sourceforge.subsonic.util.*;
import org.apache.commons.lang.exception.*;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* Logger implementation which logs to SUBSONIC_HOME/subsonic.log.
* <br/>
* Note: Third party logging libraries (such as log4j and Commons logging) are intentionally not
* used. These libraries causes a lot of headache when deploying to some application servers
* (for instance Jetty and JBoss).
*
* @author Sindre Mehus
* @version $Revision: 1.1 $ $Date: 2005/05/09 19:58:26 $
*/
public class Logger {
private String category;
private static List<Entry> entries = Collections.synchronizedList(new BoundedList<Entry>(50));
private static PrintWriter writer;
private static Boolean debugEnabled;
/**
* Creates a logger for the given class.
* @param clazz The class.
* @return A logger for the class.
*/
public static Logger getLogger(Class clazz) {
return new Logger(clazz.getName());
}
/**
* Creates a logger for the given namee.
* @param name The name.
* @return A logger for the name.
*/
public static Logger getLogger(String name) {
return new Logger(name);
}
/**
* Returns the last few log entries.
* @return The last few log entries.
*/
public static Entry[] getLatestLogEntries() {
return entries.toArray(new Entry[entries.size()]);
}
private Logger(String name) {
int lastDot = name.lastIndexOf('.');
if (lastDot == -1) {
category = name;
} else {
category = name.substring(lastDot + 1);
}
}
/**
* Logs a debug message.
* @param message The log message.
*/
public void debug(Object message) {
debug(message, null);
}
/**
* Logs a debug message.
* @param message The message.
* @param error The optional exception.
*/
public void debug(Object message, Throwable error) {
if (isDebugEnabled()) {
add(Level.DEBUG, message, error);
}
}
private static boolean isDebugEnabled() {
if (debugEnabled == null) {
VersionService versionService = ServiceLocator.getVersionService();
if (versionService == null) {
return true; // versionService not yet available.
}
Version localVersion = versionService.getLocalVersion();
debugEnabled = localVersion == null || localVersion.getBeta() != 0;
}
return debugEnabled;
}
/**
* Logs an info message.
* @param message The message.
*/
public void info(Object message) {
info(message, null);
}
/**
* Logs an info message.
* @param message The message.
* @param error The optional exception.
*/
public void info(Object message, Throwable error) {
add(Level.INFO, message, error);
}
/**
* Logs a warning message.
* @param message The message.
*/
public void warn(Object message) {
warn(message, null);
}
/**
* Logs a warning message.
* @param message The message.
* @param error The optional exception.
*/
public void warn(Object message, Throwable error) {
add(Level.WARN, message, error);
}
/**
* Logs an error message.
* @param message The message.
*/
public void error(Object message) {
error(message, null);
}
/**
* Logs an error message.
* @param message The message.
* @param error The optional exception.
*/
public void error(Object message, Throwable error) {
add(Level.ERROR, message, error);
}
private void add(Level level, Object message, Throwable error) {
Entry entry = new Entry(category, level, message, error);
try {
getPrintWriter().println(entry);
} catch (IOException x) {
System.err.println("Failed to write to subsonic.log. " + x);
}
entries.add(entry);
}
private static synchronized PrintWriter getPrintWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new FileWriter(getLogFile(), false), true);
}
return writer;
}
public static File getLogFile() {
File subsonicHome = SettingsService.getSubsonicHome();
return new File(subsonicHome, "subsonic.log");
}
/**
* Log level.
*/
public enum Level {
DEBUG, INFO, WARN, ERROR
}
/**
* Log entry.
*/
public static class Entry {
private String category;
private Date date;
private Level level;
private Object message;
private Throwable error;
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
public Entry(String category, Level level, Object message, Throwable error) {
this.date = new Date();
this.category = category;
this.level = level;
this.message = message;
this.error = error;
}
public String getCategory() {
return category;
}
public Date getDate() {
return date;
}
public Level getLevel() {
return level;
}
public Object getMessage() {
return message;
}
public Throwable getError() {
return error;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append('[').append(DATE_FORMAT.format(date)).append("] ");
builder.append(level).append(' ');
builder.append(category).append(" - ");
builder.append(message);
if (error != null) {
builder.append('\n').append(ExceptionUtils.getFullStackTrace(error));
}
return builder.toString();
}
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.List;
import net.sourceforge.subsonic.domain.ArtistBio;
/**
* @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,163 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.util.BoundedList;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Provides AJAX-enabled services for the chatting.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class ChatService {
private static final Logger LOG = Logger.getLogger(ChatService.class);
private static final String CACHE_KEY = "1";
private static final int MAX_MESSAGES = 10;
private static final long TTL_MILLIS = 3L * 24L * 60L * 60L * 1000L; // 3 days.
private final LinkedList<Message> messages = new BoundedList<Message>(MAX_MESSAGES);
private SecurityService securityService;
private long revision = System.identityHashCode(this);
/**
* Invoked by Spring.
*/
public void init() {
// Delete old messages every hour.
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Runnable runnable = new Runnable() {
public void run() {
removeOldMessages();
}
};
executor.scheduleWithFixedDelay(runnable, 0L, 3600L, TimeUnit.SECONDS);
}
private synchronized void removeOldMessages() {
long now = System.currentTimeMillis();
for (Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) {
Message message = iterator.next();
if (now - message.getDate().getTime() > TTL_MILLIS) {
iterator.remove();
revision++;
}
}
}
public synchronized void addMessage(String message) {
WebContext webContext = WebContextFactory.get();
doAddMessage(message, webContext.getHttpServletRequest());
}
public synchronized void doAddMessage(String message, HttpServletRequest request) {
String user = securityService.getCurrentUsername(request);
message = StringUtils.trimToNull(message);
if (message != null && user != null) {
messages.addFirst(new Message(message, user, new Date()));
revision++;
}
}
public synchronized void clearMessages() {
messages.clear();
revision++;
}
/**
* Returns all messages, but only if the given revision is different from the
* current revision.
*/
public synchronized Messages getMessages(long revision) {
if (this.revision != revision) {
return new Messages(new ArrayList<Message>(messages), this.revision);
}
return null;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public static class Messages implements Serializable {
private static final long serialVersionUID = -752602719879818165L;
private final List<Message> messages;
private final long revision;
public Messages(List<Message> messages, long revision) {
this.messages = messages;
this.revision = revision;
}
public List<Message> getMessages() {
return messages;
}
public long getRevision() {
return revision;
}
}
public static class Message implements Serializable {
private static final long serialVersionUID = -1907101191518133712L;
private final String content;
private final String username;
private final Date date;
public Message(String content, String username, Date date) {
this.content = content;
this.username = username;
this.date = date;
}
public String getContent() {
return content;
}
public String getUsername() {
return username;
}
public Date getDate() {
return date;
}
}
}
@@ -0,0 +1,43 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,164 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* 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 = Logger.getLogger(CoverArtService.class);
private SecurityService securityService;
private MediaFileService mediaFileService;
/**
* 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;
HttpClient client = new DefaultHttpClient();
try {
HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000); // 20 seconds
HttpConnectionParams.setSoTimeout(client.getParams(), 20 * 1000); // 20 seconds
HttpGet method = new HttpGet(url);
HttpResponse 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);
client.getConnectionManager().shutdown();
}
}
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;
}
}
@@ -0,0 +1,62 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,107 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.io.IOException;
import java.io.StringReader;
import java.net.SocketException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.util.StringUtil;
/**
* 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 = Logger.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 {
HttpClient client = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000);
HttpConnectionParams.setSoTimeout(client.getParams(), 15000);
HttpGet method = new HttpGet(url);
try {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(method, responseHandler);
} finally {
client.getConnectionManager().shutdown();
}
}
}
@@ -0,0 +1,137 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.directwebremoting.WebContextFactory;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.ArtistBio;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.LastFmService;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.NetworkService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* 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 = Logger.getLogger(MultiService.class);
private NetworkService networkService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SettingsService settingsService;
/**
* Returns status for port forwarding and URL redirection.
*/
public NetworkStatus getNetworkStatus() {
NetworkService.Status portForwardingStatus = networkService.getPortForwardingStatus();
NetworkService.Status urlRedirectionStatus = networkService.getURLRedirecionStatus();
return new NetworkStatus(portForwardingStatus.getText(),
portForwardingStatus.getDate(),
urlRedirectionStatus.getText(),
urlRedirectionStatus.getDate());
}
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 setNetworkService(NetworkService networkService) {
this.networkService = networkService;
}
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,55 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.Date;
/**
* @author Sindre Mehus
*/
public class NetworkStatus {
private final String portForwardingStatusText;
private final Date portForwardingStatusDate;
private final String urlRedirectionStatusText;
private final Date urlRedirectionStatusDate;
public NetworkStatus(String portForwardingStatusText, Date portForwardingStatusDate,
String urlRedirectionStatusText, Date urlRedirectionStatusDate) {
this.portForwardingStatusText = portForwardingStatusText;
this.portForwardingStatusDate = portForwardingStatusDate;
this.urlRedirectionStatusText = urlRedirectionStatusText;
this.urlRedirectionStatusDate = urlRedirectionStatusDate;
}
public String getPortForwardingStatusText() {
return portForwardingStatusText;
}
public Date getPortForwardingStatusDate() {
return portForwardingStatusDate;
}
public String getUrlRedirectionStatusText() {
return urlRedirectionStatusText;
}
public Date getUrlRedirectionStatusDate() {
return urlRedirectionStatusDate;
}
}
@@ -0,0 +1,98 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,176 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.AvatarScheme;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.PlayStatus;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.MediaScannerService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.StatusService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* 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 = Logger.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 = request.getRequestURL().toString();
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.replaceFirst("/dwr/.*", "/stream?player=" + player.getId() + "&id=" + mediaFile.getId());
String albumUrl = url.replaceFirst("/dwr/.*", "/main.view?id=" + mediaFile.getId());
String lyricsUrl = null;
if (!mediaFile.isVideo()) {
lyricsUrl = url.replaceFirst("/dwr/.*", "/lyrics.view?artistUtf8Hex=" + StringUtil.utf8HexEncode(artist) +
"&songUtf8Hex=" + StringUtil.utf8HexEncode(title));
}
String coverArtUrl = url.replaceFirst("/dwr/.*", "/coverArt.view?size=60&id=" + mediaFile.getId());
String avatarUrl = null;
if (userSettings.getAvatarScheme() == AvatarScheme.SYSTEM) {
avatarUrl = url.replaceFirst("/dwr/.*", "/avatar.view?id=" + userSettings.getSystemAvatarId());
} else if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM && settingsService.getCustomAvatar(username) != null) {
avatarUrl = url.replaceFirst("/dwr/.*", "/avatar.view?usernameUtf8Hex=" + StringUtil.utf8HexEncode(username));
}
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
streamUrl = StringUtil.rewriteUrl(streamUrl, referer);
albumUrl = StringUtil.rewriteUrl(albumUrl, referer);
lyricsUrl = StringUtil.rewriteUrl(lyricsUrl, referer);
coverArtUrl = StringUtil.rewriteUrl(coverArtUrl, referer);
avatarUrl = StringUtil.rewriteUrl(avatarUrl, referer);
}
String tooltip = StringUtil.toHtml(artist) + " &ndash; " + StringUtil.toHtml(title);
if (StringUtils.isNotBlank(player.getName())) {
username += "@" + player.getName();
}
artist = StringUtil.toHtml(StringUtils.abbreviate(artist, 25));
title = StringUtil.toHtml(StringUtils.abbreviate(title, 25));
username = StringUtil.toHtml(StringUtils.abbreviate(username, 25));
long minutesAgo = status.getMinutesAgo();
if (minutesAgo < 60) {
result.add(new NowPlayingInfo(player.getId(),username, artist, title, tooltip, streamUrl, albumUrl, lyricsUrl,
coverArtUrl, avatarUrl, (int) minutesAgo));
}
}
return result;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
}
@@ -0,0 +1,217 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.List;
import net.sourceforge.subsonic.util.StringUtil;
/**
* 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 sendM3U;
private final float gain;
private int startPlayerAt = -1;
private long startPlayerAtPosition; // millis
public PlayQueueInfo(List<Entry> entries, boolean stopEnabled, boolean repeatEnabled, boolean sendM3U, float gain) {
this.entries = entries;
this.stopEnabled = stopEnabled;
this.repeatEnabled = repeatEnabled;
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 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,737 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.directwebremoting.WebContextFactory;
import org.springframework.web.servlet.support.RequestContextUtils;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.dao.PlayQueueDao;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.PodcastEpisode;
import net.sourceforge.subsonic.domain.PodcastStatus;
import net.sourceforge.subsonic.domain.SavedPlayQueue;
import net.sourceforge.subsonic.domain.UrlRedirectType;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.JukeboxService;
import net.sourceforge.subsonic.service.LastFmService;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.service.RatingService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* 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 net.sourceforge.subsonic.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private PlayQueueDao playQueueDao;
/**
* 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 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 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(), "Subsonic");
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);
player.getPlayQueue().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 = request.getRequestURL().toString();
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.replaceFirst("/dwr/.*", "/main.view?id=" + file.getId());
String streamUrl = url.replaceFirst("/dwr/.*", "/stream?player=" + player.getId() + "&id=" + file.getId());
String coverArtUrl = url.replaceFirst("/dwr/.*", "/coverArt.view?id=" + file.getId());
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
albumUrl = StringUtil.rewriteUrl(albumUrl, referer);
streamUrl = StringUtil.rewriteUrl(streamUrl, referer);
}
String remoteStreamUrl = settingsService.rewriteRemoteUrl(streamUrl);
String remoteCoverArtUrl = settingsService.rewriteRemoteUrl(coverArtUrl);
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(), 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 transcodingService.getSuffix(player, file, null);
}
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;
}
}
@@ -0,0 +1,96 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import java.util.List;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Playlist;
/**
* 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,266 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.i18n.SubsonicLocaleResolver;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import org.directwebremoting.WebContextFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* 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 net.sourceforge.subsonic.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private SettingsService settingsService;
private PlayerService playerService;
private SubsonicLocaleResolver 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("net.sourceforge.subsonic.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(net.sourceforge.subsonic.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(SubsonicLocaleResolver localeResolver) {
this.localeResolver = localeResolver;
}
}
@@ -0,0 +1,43 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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 Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,63 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.SecurityService;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
/**
* 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 = Logger.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,130 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.metadata.MetaData;
import net.sourceforge.subsonic.service.metadata.MetaDataParser;
import net.sourceforge.subsonic.service.metadata.MetaDataParserFactory;
/**
* 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 = Logger.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 Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,49 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.ajax;
import net.sourceforge.subsonic.domain.*;
import net.sourceforge.subsonic.controller.*;
import org.directwebremoting.*;
import javax.servlet.http.*;
/**
* 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,52 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,58 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.cache;
import java.io.File;
import org.springframework.beans.factory.InitializingBean;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.ConfigurationFactory;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Initializes Ehcache and creates caches.
*
* @author Sindre Mehus
* @version $Id$
*/
public class CacheFactory implements InitializingBean {
private static final Logger LOG = Logger.getLogger(CacheFactory.class);
private CacheManager cacheManager;
public void afterPropertiesSet() throws Exception {
Configuration configuration = ConfigurationFactory.parseConfiguration();
// Override configuration to make sure cache is stored in Subsonic home dir.
File cacheDir = new File(SettingsService.getSubsonicHome(), "cache");
configuration.getDiskStoreConfiguration().setPath(cacheDir.getPath());
cacheManager = CacheManager.create(configuration);
}
public Ehcache getCache(String name) {
return cacheManager.getCache(name);
}
}
@@ -0,0 +1,129 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.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 boolean isReloadNeeded;
private boolean toast;
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) {
isReloadNeeded = reloadNeeded;
}
public boolean isReloadNeeded() {
return isReloadNeeded;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,42 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.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,202 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.controller.GeneralSettingsController;
import net.sourceforge.subsonic.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;
private boolean isReloadNeeded;
private boolean toast;
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 boolean isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = 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;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,187 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import java.io.File;
import java.util.Date;
import java.util.List;
import net.sourceforge.subsonic.controller.MusicFolderSettingsController;
import net.sourceforge.subsonic.domain.MusicFolder;
import org.apache.commons.lang.StringUtils;
/**
* 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;
private boolean reload;
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 void setReload(boolean reload) {
this.reload = reload;
}
public boolean isReload() {
return reload;
}
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,100 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.domain.LicenseInfo;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class NetworkSettingsCommand {
private boolean portForwardingEnabled;
private boolean urlRedirectionEnabled;
private String urlRedirectFrom;
private String urlRedirectCustomUrl;
private String urlRedirectType;
private int port;
private boolean toast;
private LicenseInfo licenseInfo;
public void setPortForwardingEnabled(boolean portForwardingEnabled) {
this.portForwardingEnabled = portForwardingEnabled;
}
public boolean isPortForwardingEnabled() {
return portForwardingEnabled;
}
public boolean isUrlRedirectionEnabled() {
return urlRedirectionEnabled;
}
public void setUrlRedirectionEnabled(boolean urlRedirectionEnabled) {
this.urlRedirectionEnabled = urlRedirectionEnabled;
}
public String getUrlRedirectFrom() {
return urlRedirectFrom;
}
public void setUrlRedirectFrom(String urlRedirectFrom) {
this.urlRedirectFrom = urlRedirectFrom;
}
public String getUrlRedirectCustomUrl() {
return urlRedirectCustomUrl;
}
public void setUrlRedirectCustomUrl(String urlRedirectCustomUrl) {
this.urlRedirectCustomUrl = urlRedirectCustomUrl;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
public void setLicenseInfo(LicenseInfo licenseInfo) {
this.licenseInfo = licenseInfo;
}
public LicenseInfo getLicenseInfo() {
return licenseInfo;
}
public String getUrlRedirectType() {
return urlRedirectType;
}
public void setUrlRedirectType(String urlRedirectType) {
this.urlRedirectType = urlRedirectType;
}
}
@@ -0,0 +1,74 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.controller.*;
/**
* Command used in {@link PasswordSettingsController}.
*
* @author Sindre Mehus
*/
public class PasswordSettingsCommand {
private String username;
private String password;
private String confirmPassword;
private boolean ldapAuthenticated;
private boolean toast;
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;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,270 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import java.util.List;
import net.sourceforge.subsonic.controller.PersonalSettingsController;
import net.sourceforge.subsonic.domain.AlbumListType;
import net.sourceforge.subsonic.domain.Avatar;
import net.sourceforge.subsonic.domain.Theme;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
/**
* 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 showChatEnabled;
private boolean showArtistInfoEnabled;
private boolean nowPlayingAllowed;
private boolean autoHidePlayQueue;
private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled;
private boolean queueFollowingSongs;
private boolean lastFmEnabled;
private String lastFmUsername;
private String lastFmPassword;
private boolean isReloadNeeded;
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 isShowChatEnabled() {
return showChatEnabled;
}
public void setShowChatEnabled(boolean showChatEnabled) {
this.showChatEnabled = showChatEnabled;
}
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 isLastFmEnabled() {
return lastFmEnabled;
}
public void setLastFmEnabled(boolean lastFmEnabled) {
this.lastFmEnabled = lastFmEnabled;
}
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 isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = reloadNeeded;
}
public boolean isQueueFollowingSongs() {
return queueFollowingSongs;
}
public void setQueueFollowingSongs(boolean queueFollowingSongs) {
this.queueFollowingSongs = queueFollowingSongs;
}
}
@@ -0,0 +1,227 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import java.util.Date;
import java.util.List;
import net.sourceforge.subsonic.controller.PlayerSettingsController;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.PlayerTechnology;
import net.sourceforge.subsonic.domain.TranscodeScheme;
import net.sourceforge.subsonic.domain.Transcoding;
/**
* 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 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;
private boolean isReloadNeeded;
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 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 boolean isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = 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,75 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.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;
private boolean toast;
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;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,106 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import org.apache.commons.lang.StringUtils;
import net.sourceforge.subsonic.controller.PremiumSettingsController;
import net.sourceforge.subsonic.domain.LicenseInfo;
import net.sourceforge.subsonic.domain.User;
/**
* Command used in {@link PremiumSettingsController}.
*
* @author Sindre Mehus
*/
public class PremiumSettingsCommand {
private String path;
private String brand;
private LicenseInfo licenseInfo;
private String licenseCode;
private boolean forceChange;
private boolean submissionError;
private User user;
private boolean toast;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public LicenseInfo getLicenseInfo() {
return licenseInfo;
}
public String getLicenseCode() {
return licenseCode;
}
public void setLicenseCode(String licenseCode) {
this.licenseCode = StringUtils.trimToNull(licenseCode);
}
public void setLicenseInfo(LicenseInfo licenseInfo) {
this.licenseInfo = licenseInfo;
}
public boolean isForceChange() {
return forceChange;
}
public void setForceChange(boolean forceChange) {
this.forceChange = forceChange;
}
public boolean isSubmissionError() {
return submissionError;
}
public void setSubmissionError(boolean submissionError) {
this.submissionError = submissionError;
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
public void setToast(boolean toast) {
this.toast = toast;
}
public boolean isToast() {
return toast;
}
}
@@ -0,0 +1,135 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.domain.*;
import net.sourceforge.subsonic.controller.*;
import java.util.*;
/**
* 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,316 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.command;
import net.sourceforge.subsonic.controller.UserSettingsController;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.TranscodeScheme;
import net.sourceforge.subsonic.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;
private boolean toast;
private boolean reload;
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();
}
public void setToast(boolean toast) {
this.toast = toast;
}
public boolean isToast() {
return toast;
}
public boolean isReload() {
return reload;
}
public void setReload(boolean reload) {
this.reload = reload;
}
}
@@ -0,0 +1,60 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import org.springframework.web.servlet.support.*;
import org.springframework.web.servlet.mvc.*;
import org.springframework.ui.context.*;
import javax.servlet.http.*;
import java.awt.*;
import java.util.*;
/**
* Abstract super class for controllers which generate charts.
*
* @author Sindre Mehus
*/
public abstract class AbstractChartController implements Controller {
/**
* Returns the chart background color for the current theme.
* @param request The servlet request.
* @return The chart background color.
*/
protected Color getBackground(HttpServletRequest request) {
return getColor("backgroundColor", request);
}
/**
* Returns the chart foreground color for the current theme.
* @param request The servlet request.
* @return The chart foreground color.
*/
protected Color getForeground(HttpServletRequest request) {
return getColor("textColor", request);
}
private Color getColor(String code, HttpServletRequest request) {
Theme theme = RequestContextUtils.getTheme(request);
Locale locale = RequestContextUtils.getLocale(request);
String colorHex = theme.getMessageSource().getMessage(code, new Object[0], locale);
return new Color(Integer.parseInt(colorHex, 16));
}
}
@@ -0,0 +1,82 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.command.AdvancedSettingsCommand;
import net.sourceforge.subsonic.service.SettingsService;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to administrate advanced settings.
*
* @author Sindre Mehus
*/
public class AdvancedSettingsController extends SimpleFormController {
private SettingsService settingsService;
@Override
protected Object formBackingObject(HttpServletRequest request) 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());
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
AdvancedSettingsCommand command = (AdvancedSettingsCommand) comm;
command.setToast(true);
command.setReloadNeeded(false);
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.save();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,38 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.*;
import javax.servlet.http.*;
/**
* Controller for the page which forwards to allmusic.com.
*
* @author Sindre Mehus
*/
public class AllmusicController extends ParameterizableViewController {
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("album", request.getParameter("album"));
return result;
}
}
@@ -0,0 +1,71 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2013 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.apache.commons.lang.RandomStringUtils;
/**
* @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,92 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import net.sourceforge.subsonic.domain.Avatar;
import net.sourceforge.subsonic.domain.AvatarScheme;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller which produces avatar images.
*
* @author Sindre Mehus
*/
public class AvatarController implements Controller, LastModified {
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;
}
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);
}
return settingsService.getSystemAvatar(userSettings.getSystemAvatarId());
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,141 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.Avatar;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.StringUtil;
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.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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
*/
public class AvatarUploadController extends ParameterizableViewController {
private static final Logger LOG = Logger.getLogger(AvatarUploadController.class);
private static final int MAX_AVATAR_SIZE = 64;
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 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));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
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);
}
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,72 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
/**
* Controller for changing cover art.
*
* @author Sindre Mehus
*/
public class ChangeCoverArtController extends ParameterizableViewController {
private MediaFileService mediaFileService;
@Override
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 (artist == null) {
artist = dir.getArtist();
}
if (album == null) {
album = dir.getAlbumName();
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", id);
map.put("artist", artist);
map.put("album", album);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,731 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.dao.AlbumDao;
import net.sourceforge.subsonic.dao.ArtistDao;
import net.sourceforge.subsonic.domain.Album;
import net.sourceforge.subsonic.domain.Artist;
import net.sourceforge.subsonic.domain.CoverArtScheme;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.PodcastChannel;
import net.sourceforge.subsonic.domain.Transcoding;
import net.sourceforge.subsonic.domain.VideoTranscodingSettings;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.service.metadata.JaudiotaggerParser;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller which produces cover art images.
*
* @author Sindre Mehus
*/
public class CoverArtController implements Controller, 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 = Logger.getLogger(CoverArtController.class);
private MediaFileService mediaFileService;
private TranscodingService transcodingService;
private SettingsService settingsService;
private PlaylistService playlistService;
private PodcastService podcastService;
private ArtistDao artistDao;
private AlbumDao albumDao;
private Semaphore semaphore;
public void init() {
semaphore = new Semaphore(settingsService.getCoverArtConcurrency());
}
public long getLastModified(HttpServletRequest request) {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
long result = coverArtRequest.lastModified();
// LOG.info("getLastModified - " + coverArtRequest + ": " + new Date(result));
return result;
}
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.getSubsonicHome(), "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;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setArtistDao(ArtistDao artistDao) {
this.artistDao = artistDao;
}
public void setAlbumDao(AlbumDao albumDao) {
this.albumDao = albumDao;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
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<MediaFile>(albums);
}
}
private class PodcastCoverArtRequest extends CoverArtRequest {
private final PodcastChannel channel;
public 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 = 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;
public 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,66 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.dao.DaoHelper;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
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
*/
public class DBController extends ParameterizableViewController {
private DaoHelper daoHelper;
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());
}
}
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setDaoHelper(DaoHelper daoHelper) {
this.daoHelper = daoHelper;
}
}
@@ -0,0 +1,95 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2013 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.UPnPService;
/**
* Controller for the page used to administrate the UPnP/DLNA server settings.
*
* @author Sindre Mehus
*/
public class DLNASettingsController extends ParameterizableViewController {
private UPnPService upnpService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
handleParameters(request);
map.put("toast", true);
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("dlnaEnabled", settingsService.isDlnaEnabled());
map.put("dlnaServerName", settingsService.getDlnaServerName());
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
private void handleParameters(HttpServletRequest request) {
boolean dlnaEnabled = ServletRequestUtils.getBooleanParameter(request, "dlnaEnabled", false);
String dlnaServerName = StringUtils.trimToNull(request.getParameter("dlnaServerName"));
if (dlnaServerName == null) {
dlnaServerName = "Subsonic";
}
upnpService.setMediaServerEnabled(false);
settingsService.setDlnaEnabled(dlnaEnabled);
settingsService.setDlnaServerName(dlnaServerName);
settingsService.save();
upnpService.setMediaServerEnabled(dlnaEnabled);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setUpnpService(UPnPService upnpService) {
this.upnpService = upnpService;
}
}
@@ -0,0 +1,414 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.TransferStatus;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.io.RangeOutputStream;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.StatusService;
import net.sourceforge.subsonic.util.FileUtil;
import net.sourceforge.subsonic.util.HttpRange;
import net.sourceforge.subsonic.util.Util;
/**
* 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
*/
public class DownloadController implements Controller, LastModified {
private static final Logger LOG = Logger.getLogger(DownloadController.class);
private PlayerService playerService;
private StatusService statusService;
private SecurityService securityService;
private PlaylistService playlistService;
private SettingsService settingsService;
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;
}
}
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.
List<MediaFile> filesToDownload = new ArrayList<MediaFile>();
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();
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,197 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.metadata.JaudiotaggerParser;
import net.sourceforge.subsonic.service.metadata.MetaDataParser;
import net.sourceforge.subsonic.service.metadata.MetaDataParserFactory;
/**
* Controller for the page used to edit MP3 tags.
*
* @author Sindre Mehus
*/
public class EditTagsController extends ParameterizableViewController {
private MetaDataParserFactory metaDataParserFactory;
private MediaFileService mediaFileService;
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
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;
}
public void setMetaDataParserFactory(MetaDataParserFactory metaDataParserFactory) {
this.metaDataParserFactory = metaDataParserFactory;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
/**
* Contains information about a single song.
*/
public static class Song {
private int id;
private String fileName;
private Integer suggestedTrack;
private Integer track;
private String suggestedTitle;
private String title;
private String artist;
private String album;
private Integer year;
private String genre;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Integer getSuggestedTrack() {
return suggestedTrack;
}
public void setSuggestedTrack(Integer suggestedTrack) {
this.suggestedTrack = suggestedTrack;
}
public Integer getTrack() {
return track;
}
public void setTrack(Integer track) {
this.track = track;
}
public String getSuggestedTitle() {
return suggestedTitle;
}
public void setSuggestedTitle(String suggestedTitle) {
this.suggestedTitle = suggestedTitle;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
}
}
@@ -0,0 +1,125 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Share;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.ShareService;
/**
* Controller for the page used to play shared music (Twitter, Facebook etc).
*
* @author Sindre Mehus
*/
public class ExternalPlayerController extends ParameterizableViewController {
private SettingsService settingsService;
private PlayerService playerService;
private ShareService shareService;
private MediaFileService mediaFileService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String pathInfo = request.getPathInfo();
if (pathInfo == null || !pathInfo.startsWith("/")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
Share share = shareService.getShareByName(pathInfo.substring(1));
if (share != null && share.getExpires() != null && share.getExpires().before(new Date())) {
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(share, player.getUsername()));
map.put("redirectUrl", settingsService.getUrlRedirectUrl());
map.put("player", player.getId());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private List<MediaFile> getSongs(Share share, String username) throws IOException {
List<MediaFile> result = new ArrayList<MediaFile>();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
if (share != null) {
for (MediaFile file : shareService.getSharedFiles(share.getId(), musicFolders)) {
if (file.getFile().exists()) {
if (file.isDirectory()) {
result.addAll(mediaFileService.getChildrenOf(file, true, false, true));
} else {
result.add(file);
}
}
}
}
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setShareService(ShareService shareService) {
this.shareService = shareService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,119 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.GeneralSettingsCommand;
import net.sourceforge.subsonic.domain.Theme;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the page used to administrate general settings.
*
* @author Sindre Mehus
*/
public class GeneralSettingsController extends SimpleFormController {
private SettingsService settingsService;
protected Object formBackingObject(HttpServletRequest request) 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);
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
GeneralSettingsCommand command = (GeneralSettingsCommand) comm;
int themeIndex = Integer.parseInt(command.getThemeIndex());
Theme theme = settingsService.getAvailableThemes()[themeIndex];
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = settingsService.getAvailableLocales()[localeIndex];
command.setToast(true);
command.setReloadNeeded(!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();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,201 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.awt.Dimension;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.util.Pair;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller which produces the HLS (Http Live Streaming) playlist.
*
* @author Sindre Mehus
*/
public class HLSController implements Controller {
private static final int SEGMENT_DURATION = 10;
private static final Pattern BITRATE_PATTERN = Pattern.compile("(\\d+)(@(\\d+)x(\\d+))?");
private PlayerService playerService;
private MediaFileService mediaFileService;
private SecurityService securityService;
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);
writer.print(contextPath + "hls/hls.m3u8?id=" + id + "&player=" + player.getId() + "&bitRate=" + kbps);
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) {
StringBuilder builder = new StringBuilder();
builder.append(getContextPath(request)).append("stream/stream.ts?id=").append(id).append("&hls=true&timeOffset=").append(offset)
.append("&player=").append(player.getId()).append("&duration=").append(duration);
if (bitRate != null) {
builder.append("&maxBitRate=").append(bitRate.getFirst());
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
builder.append("&size=").append(dimension.width).append("x").append(dimension.height);
}
}
return builder.toString();
}
private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (StringUtils.isEmpty(contextPath)) {
contextPath = "/";
} else {
contextPath += "/";
}
return contextPath;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,93 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.VersionService;
/**
* Controller for the help page.
*
* @author Sindre Mehus
*/
public class HelpController extends ParameterizableViewController {
private VersionService versionService;
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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("licenseInfo", settingsService.getLicenseInfo());
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);
map.put("logEntries", Logger.getLatestLogEntries());
map.put("logFile", Logger.getLogFile());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setVersionService(VersionService versionService) {
this.versionService = versionService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,372 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.AlbumListType;
import net.sourceforge.subsonic.domain.CoverArtScheme;
import net.sourceforge.subsonic.domain.Genre;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.MediaScannerService;
import net.sourceforge.subsonic.service.RatingService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import static org.springframework.web.bind.ServletRequestUtils.getIntParameter;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* Controller for the home page.
*
* @author Sindre Mehus
*/
public class HomeController extends ParameterizableViewController {
private static final int LIST_SIZE = 40;
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
private RatingService ratingService;
private SecurityService securityService;
private MediaFileService mediaFileService;
private SearchService searchService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 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"));
if (listType == null) {
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
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<String, Object>();
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private List<Album> getHighestRated(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
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<Album>();
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<Album>();
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<Album>();
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<Album>();
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<Album>();
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<Album>();
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<Album>();
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<Integer>();
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<Album>();
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;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
/**
* Contains info for a single album.
*/
public static class Album {
private String path;
private String coverArtPath;
private String artist;
private String albumTitle;
private Date created;
private Date lastPlayed;
private Integer playCount;
private Integer rating;
private int id;
private Integer year;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getCoverArtPath() {
return coverArtPath;
}
public void setCoverArtPath(String coverArtPath) {
this.coverArtPath = coverArtPath;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbumTitle() {
return albumTitle;
}
public void setAlbumTitle(String albumTitle) {
this.albumTitle = albumTitle;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getLastPlayed() {
return lastPlayed;
}
public void setLastPlayed(Date lastPlayed) {
this.lastPlayed = lastPlayed;
}
public Integer getPlayCount() {
return playCount;
}
public void setPlayCount(Integer playCount) {
this.playCount = playCount;
}
public Integer getRating() {
return rating;
}
public void setRating(Integer rating) {
this.rating = rating;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
}
}
@@ -0,0 +1,93 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
/**
* @author Sindre Mehus
*/
public class ImportPlaylistController extends ParameterizableViewController {
private static final long MAX_PLAYLIST_SIZE_MB = 5L;
private SecurityService securityService;
private PlaylistService playlistService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 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, format, item.getInputStream(), null);
map.put("playlist", playlist);
}
}
}
} catch (Exception e) {
map.put("error", e.getMessage());
}
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,122 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.InternetRadio;
import net.sourceforge.subsonic.service.SettingsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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
*/
public class InternetRadioSettingsController extends ParameterizableViewController {
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
String error = handleParameters(request);
map.put("error", error);
if (error == null) {
map.put("reload", true);
}
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("internetRadios", settingsService.getAllInternetRadios(true));
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
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 + "]"));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,166 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import java.util.GregorianCalendar;
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 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.subsonic.restapi.Error;
import org.subsonic.restapi.ObjectFactory;
import org.subsonic.restapi.Response;
import org.subsonic.restapi.ResponseStatus;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.util.StringUtil;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class JAXBWriter {
private static final Logger LOG = Logger.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() throws JAXBException {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
}
private Marshaller createJsonMarshaller() throws JAXBException {
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;
}
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) throws Exception {
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 (Exception x) {
LOG.error("Failed to marshal JAXB", x);
throw 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,200 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.support.RequestContextUtils;
import net.sourceforge.subsonic.domain.InternetRadio;
import net.sourceforge.subsonic.domain.MediaLibraryStatistics;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.MusicFolderContent;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.MediaScannerService;
import net.sourceforge.subsonic.service.MusicIndexService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.FileUtil;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller for the left index frame.
*
* @author Sindre Mehus
*/
public class LeftController extends ParameterizableViewController {
// 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);
}
private MediaScannerService mediaScannerService;
private SettingsService settingsService;
private SecurityService securityService;
private MusicIndexService musicIndexService;
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.
*/
public 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;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
boolean musicFolderChanged = saveSelectedMusicFolder(request);
Map<String, Object> map = new HashMap<String, Object>();
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 : Arrays.asList(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));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
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;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMusicIndexService(MusicIndexService musicIndexService) {
this.musicIndexService = musicIndexService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
}
@@ -0,0 +1,46 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.HashMap;
/**
* Controller for the lyrics popup.
*
* @author Sindre Mehus
*/
public class LyricsController extends ParameterizableViewController {
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("artist", request.getParameter("artist"));
map.put("song", request.getParameter("song"));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
}
@@ -0,0 +1,122 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller which produces the M3U playlist.
*
* @author Sindre Mehus
*/
public class M3UController implements Controller {
private PlayerService playerService;
private SettingsService settingsService;
private TranscodingService transcodingService;
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 = request.getRequestURL().toString();
url = url.replaceFirst("play.m3u.*", "stream?");
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
url = StringUtil.rewriteUrl(url, referer);
}
url = settingsService.rewriteRemoteUrl(url);
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 {
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());
out.println(url + "player=" + player.getId() + "&id=" + mediaFile.getId() + "&suffix=." + transcodingService.getSuffix(player, mediaFile, null));
}
}
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;
}
out.println("#EXTM3U");
out.println("#EXTINF:-1,Subsonic");
out.println(url);
}
private String getSuffix(Player player) {
PlayQueue playQueue = player.getPlayQueue();
return playQueue.isEmpty() ? null : transcodingService.getSuffix(player, playQueue.getFile(0), null);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
}
@@ -0,0 +1,288 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.CoverArtScheme;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MediaFileComparator;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.AdService;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.RatingService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the main page.
*
* @author Sindre Mehus
*/
public class MainController extends AbstractController {
private SecurityService securityService;
private PlayerService playerService;
private SettingsService settingsService;
private RatingService ratingService;
private MediaFileService mediaFileService;
private AdService adService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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"));
}
List<MediaFile> children = mediaFiles.size() == 1 ? mediaFileService.getChildrenOf(dir, true, true, true) : getMultiFolderChildren(mediaFiles);
List<MediaFile> files = new ArrayList<MediaFile>();
List<MediaFile> subDirs = new ArrayList<MediaFile>();
for (MediaFile child : children) {
if (child.isFile()) {
files.add(child);
} else {
subDirs.add(child);
}
}
UserSettings userSettings = settingsService.getUserSettings(username);
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("showAd", !settingsService.isLicenseValid() && adService.showAd());
map.put("viewAsList", isViewAsList(request, userSettings));
if (dir.isAlbum()) {
map.put("sieblingAlbums", getSieblingAlbums(dir));
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.
}
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";
}
ModelAndView result = new ModelAndView(view);
result.addObject("model", map);
return result;
}
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<MediaFile>();
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<MediaFile>(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<MediaFile>(result);
}
private List<MediaFile> getAncestors(MediaFile dir) throws IOException {
LinkedList<MediaFile> result = new LinkedList<MediaFile>();
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>();
MediaFile parent = mediaFileService.getParentOf(dir);
if (!mediaFileService.isRoot(parent)) {
List<MediaFile> sieblings = mediaFileService.getChildrenOf(parent, false, true, true);
for (MediaFile siebling : sieblings) {
if (siebling.isAlbum() && !siebling.equals(dir)) {
result.add(siebling);
}
}
}
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setAdService(AdService adService) {
this.adService = adService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,102 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.File;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller for the "more" page.
*
* @author Sindre Mehus
*/
public class MoreController extends ParameterizableViewController {
private SettingsService settingsService;
private SecurityService securityService;
private PlayerService playerService;
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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();
}
StringBuilder jamstashUrl = new StringBuilder("http://jamstash.com/#/settings?u=" + StringUtil.urlEncode(user.getUsername()) + "&url=");
if (settingsService.isUrlRedirectionEnabled()) {
jamstashUrl.append(StringUtil.urlEncode(settingsService.getUrlRedirectUrl()));
} else {
jamstashUrl.append(StringUtil.urlEncode(request.getRequestURL().toString().replaceAll("/more.view.*", "")));
}
Player player = playerService.getPlayer(request, response);
ModelAndView result = super.handleRequestInternal(request, response);
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());
map.put("jamstashUrl", jamstashUrl);
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,265 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.StringUtil;
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
* Multi-controller used for simple pages.
*
* @author Sindre Mehus
*/
public class MultiController extends MultiActionController {
private static final Logger LOG = Logger.getLogger(MultiController.class);
private SecurityService securityService;
private SettingsService settingsService;
private PlaylistService playlistService;
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("j_acegi_security_check?j_username=" + username +
"&j_password=" + password + "&_acegi_security_remember_me=checked"));
}
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);
}
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 boolean emailPassword(String password, String username, String email) {
HttpClient client = new DefaultHttpClient();
try {
HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000);
HttpConnectionParams.setSoTimeout(client.getParams(), 10000);
HttpPost method = new HttpPost("http://subsonic.org/backend/sendMail.view");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("from", "noreply@subsonic.org"));
params.add(new BasicNameValuePair("to", email));
params.add(new BasicNameValuePair("subject", "Subsonic Password"));
params.add(new BasicNameValuePair("text",
"Hi there!\n\n" +
"You have requested to reset your Subsonic password. Please find your new login details below.\n\n" +
"Username: " + username + "\n" +
"Password: " + password + "\n\n" +
"--\n" +
"The Subsonic Team\n" +
"subsonic.org"));
method.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8));
client.execute(method);
return true;
} catch (Exception x) {
LOG.warn("Failed to send email.", x);
return false;
} finally {
client.getConnectionManager().shutdown();
}
}
private User getUserByUsernameOrEmail(String usernameOrEmail) {
if (usernameOrEmail != null) {
User user = securityService.getUserByName(usernameOrEmail);
if (user != null) {
return user;
}
return securityService.getUserByEmail(usernameOrEmail);
}
return null;
}
public ModelAndView accessDenied(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("accessDenied");
}
public ModelAndView notFound(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("notFound");
}
public ModelAndView gettingStarted(HttpServletRequest request, HttpServletResponse response) {
updatePortAndContextPath(request);
if (request.getParameter("hide") != null) {
settingsService.setGettingStartedEnabled(false);
settingsService.save();
return new ModelAndView(new RedirectView("home.view"));
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("runningAsRoot", "root".equals(System.getProperty("user.name")));
return new ModelAndView("gettingStarted", "model", map);
}
public ModelAndView index(HttpServletRequest request, HttpServletResponse response) {
updatePortAndContextPath(request);
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
Map<String, Object> map = new HashMap<String, Object>();
map.put("showRight", userSettings.isShowNowPlayingEnabled() || userSettings.isShowChatEnabled());
map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue());
map.put("showSideBar", userSettings.isShowSideBar());
map.put("brand", settingsService.getBrand());
return new ModelAndView("index", "model", map);
}
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;
}
private void updatePortAndContextPath(HttpServletRequest request) {
int port = Integer.parseInt(System.getProperty("subsonic.port", String.valueOf(request.getLocalPort())));
int httpsPort = Integer.parseInt(System.getProperty("subsonic.httpsPort", "0"));
String contextPath = request.getContextPath().replace("/", "");
if (settingsService.getPort() != port) {
settingsService.setPort(port);
settingsService.save();
}
if (settingsService.getHttpsPort() != httpsPort) {
settingsService.setHttpsPort(httpsPort);
settingsService.save();
}
if (!ObjectUtils.equals(settingsService.getUrlRedirectContextPath(), contextPath)) {
settingsService.setUrlRedirectContextPath(contextPath);
settingsService.save();
}
}
public ModelAndView test(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("test");
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,135 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.command.MusicFolderSettingsCommand;
import net.sourceforge.subsonic.dao.AlbumDao;
import net.sourceforge.subsonic.dao.ArtistDao;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.service.MediaScannerService;
import net.sourceforge.subsonic.service.SettingsService;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* Controller for the page used to administrate the set of music folders.
*
* @author Sindre Mehus
*/
public class MusicFolderSettingsController extends SimpleFormController {
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
private ArtistDao artistDao;
private AlbumDao albumDao;
private MediaFileDao mediaFileDao;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
MusicFolderSettingsCommand command = new MusicFolderSettingsCommand();
if (request.getParameter("scanNow") != null) {
settingsService.clearMusicFolderCache();
mediaScannerService.scanLibrary();
}
if (request.getParameter("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());
command.setReload(request.getParameter("reload") != null || request.getParameter("scanNow") != null);
return command;
}
private void expunge() {
artistDao.expunge();
albumDao.expunge();
mediaFileDao.expunge();
}
private List<MusicFolderSettingsCommand.MusicFolderInfo> wrap(List<MusicFolder> musicFolders) {
ArrayList<MusicFolderSettingsCommand.MusicFolderInfo> result = new ArrayList<MusicFolderSettingsCommand.MusicFolderInfo>();
for (MusicFolder musicFolder : musicFolders) {
result.add(new MusicFolderSettingsCommand.MusicFolderInfo(musicFolder));
}
return result;
}
@Override
protected ModelAndView onSubmit(Object comm) throws Exception {
MusicFolderSettingsCommand command = (MusicFolderSettingsCommand) comm;
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();
mediaScannerService.schedule();
return new ModelAndView(new RedirectView(getSuccessView() + ".view?reload"));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setArtistDao(ArtistDao artistDao) {
this.artistDao = artistDao;
}
public void setAlbumDao(AlbumDao albumDao) {
this.albumDao = albumDao;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
}
@@ -0,0 +1,83 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.NetworkSettingsCommand;
import net.sourceforge.subsonic.domain.UrlRedirectType;
import net.sourceforge.subsonic.service.NetworkService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the page used to change the network settings.
*
* @author Sindre Mehus
*/
public class NetworkSettingsController extends SimpleFormController {
private SettingsService settingsService;
private NetworkService networkService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
NetworkSettingsCommand command = new NetworkSettingsCommand();
command.setPortForwardingEnabled(settingsService.isPortForwardingEnabled());
command.setUrlRedirectionEnabled(settingsService.isUrlRedirectionEnabled());
command.setUrlRedirectType(settingsService.getUrlRedirectType().name());
command.setUrlRedirectFrom(settingsService.getUrlRedirectFrom());
command.setUrlRedirectCustomUrl(settingsService.getUrlRedirectCustomUrl());
command.setPort(settingsService.getPort());
command.setLicenseInfo(settingsService.getLicenseInfo());
return command;
}
protected void doSubmitAction(Object cmd) throws Exception {
NetworkSettingsCommand command = (NetworkSettingsCommand) cmd;
command.setToast(true);
settingsService.setPortForwardingEnabled(command.isPortForwardingEnabled());
settingsService.setUrlRedirectionEnabled(command.isUrlRedirectionEnabled());
settingsService.setUrlRedirectType(UrlRedirectType.valueOf(command.getUrlRedirectType()));
settingsService.setUrlRedirectFrom(StringUtils.lowerCase(command.getUrlRedirectFrom()));
settingsService.setUrlRedirectCustomUrl(StringUtils.trimToEmpty(command.getUrlRedirectCustomUrl()));
if (settingsService.getServerId() == null) {
Random rand = new Random(System.currentTimeMillis());
settingsService.setServerId(String.valueOf(Math.abs(rand.nextLong())));
}
settingsService.save();
networkService.initPortForwarding(0);
networkService.initUrlRedirection(true);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setNetworkService(NetworkService networkService) {
this.networkService = networkService;
}
}
@@ -0,0 +1,78 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.TransferStatus;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.StatusService;
/**
* Controller for showing what's currently playing.
*
* @author Sindre Mehus
*/
public class NowPlayingController extends AbstractController {
private PlayerService playerService;
private StatusService statusService;
private MediaFileService mediaFileService;
@Override
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));
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,59 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import org.springframework.web.servlet.mvc.*;
import net.sourceforge.subsonic.service.*;
import net.sourceforge.subsonic.command.*;
import net.sourceforge.subsonic.domain.*;
import javax.servlet.http.*;
/**
* Controller for the page used to change password.
*
* @author Sindre Mehus
*/
public class PasswordSettingsController extends SimpleFormController {
private SecurityService securityService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PasswordSettingsCommand command = new PasswordSettingsCommand();
User user = securityService.getCurrentUser(request);
command.setUsername(user.getUsername());
command.setLdapAuthenticated(user.isLdapAuthenticated());
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
PasswordSettingsCommand command = (PasswordSettingsCommand) comm;
User user = securityService.getUserByName(command.getUsername());
user.setPassword(command.getPassword());
securityService.updateUser(user);
command.setPassword(null);
command.setConfirmPassword(null);
command.setToast(true);
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,183 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.PersonalSettingsCommand;
import net.sourceforge.subsonic.domain.AlbumListType;
import net.sourceforge.subsonic.domain.AvatarScheme;
import net.sourceforge.subsonic.domain.Theme;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the page used to administrate per-user settings.
*
* @author Sindre Mehus
*/
public class PersonalSettingsController extends SimpleFormController {
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected Object formBackingObject(HttpServletRequest request) 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.setShowChatEnabled(userSettings.isShowChatEnabled());
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.setLastFmEnabled(userSettings.isLastFmEnabled());
command.setLastFmUsername(userSettings.getLastFmUsername());
command.setLastFmPassword(userSettings.getLastFmPassword());
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;
}
}
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
PersonalSettingsCommand command = (PersonalSettingsCommand) comm;
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.setShowChatEnabled(command.isShowChatEnabled());
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.setLastFmEnabled(command.isLastFmEnabled());
settings.setLastFmUsername(command.getLastFmUsername());
settings.setSystemAvatarId(getSystemAvatarId(command));
settings.setAvatarScheme(getAvatarScheme(command));
if (StringUtils.isNotBlank(command.getLastFmPassword())) {
settings.setLastFmPassword(command.getLastFmPassword());
}
settings.setChanged(new Date());
settingsService.updateUserSettings(settings);
command.setReloadNeeded(true);
}
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;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,80 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the playlist frame.
*
* @author Sindre Mehus
*/
public class PlayQueueController extends ParameterizableViewController {
private PlayerService playerService;
private SecurityService securityService;
private SettingsService settingsService;
@Override
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<String, Object>();
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());
map.put("licenseInfo", settingsService.getLicenseInfo());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,146 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.PlayerSettingsCommand;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.PlayerTechnology;
import net.sourceforge.subsonic.domain.TranscodeScheme;
import net.sourceforge.subsonic.domain.Transcoding;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.TranscodingService;
/**
* Controller for the player settings page.
*
* @author Sindre Mehus
*/
public class PlayerSettingsController extends SimpleFormController {
private PlayerService playerService;
private SecurityService securityService;
private TranscodingService transcodingService;
@Override
protected Object formBackingObject(HttpServletRequest request) 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.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());
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
PlayerSettingsCommand command = (PlayerSettingsCommand) comm;
Player player = playerService.getPlayerById(command.getPlayerId());
player.setAutoControlEnabled(command.isAutoControlEnabled());
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());
command.setReloadNeeded(true);
}
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"));
}
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
}
@@ -0,0 +1,91 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
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
*/
public class PlaylistController extends ParameterizableViewController {
private SecurityService securityService;
private PlaylistService playlistService;
private SettingsService settingsService;
private PlayerService playerService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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.view"));
}
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());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
}
@@ -0,0 +1,72 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the playlists page.
*
* @author Sindre Mehus
*/
public class PlaylistsController extends ParameterizableViewController {
private SecurityService securityService;
private PlaylistService playlistService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
User user = securityService.getCurrentUser(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(user.getUsername());
map.put("playlists", playlists);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,67 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.service.SecurityService;
/**
* Controller for the "Podcast channel" page.
*
* @author Sindre Mehus
*/
public class PodcastChannelController extends ParameterizableViewController {
private PodcastService podcastService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
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;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,82 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.PodcastChannel;
import net.sourceforge.subsonic.domain.PodcastEpisode;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the "Podcast channels" page.
*
* @author Sindre Mehus
*/
public class PodcastChannelsController extends ParameterizableViewController {
private PodcastService podcastService;
private SecurityService securityService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
Map<PodcastChannel, List<PodcastEpisode>> channels = new LinkedHashMap<PodcastChannel, List<PodcastEpisode>>();
Map<Integer, PodcastChannel> channelMap = new HashMap<Integer, PodcastChannel>();
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));
map.put("licenseInfo", settingsService.getLicenseInfo());
return result;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,147 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.StringUtil;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Controller for the page used to generate the Podcast XML file.
*
* @author Sindre Mehus
*/
public class PodcastController extends ParameterizableViewController {
private static final DateFormat RSS_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
private PlaylistService playlistService;
private SettingsService settingsService;
private SecurityService securityService;
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<Podcast>();
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);
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
url = StringUtil.rewriteUrl(url, referer);
}
String enclosureUrl = url.replaceFirst("/podcast.*", "/stream?playlist=" + playlist.getId());
enclosureUrl = settingsService.rewriteRemoteUrl(enclosureUrl);
podcasts.add(new Podcast(playlist.getName(), publishDate, enclosureUrl, length, type));
}
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
map.put("url", url);
map.put("podcasts", podcasts);
result.addObject("model", map);
return result;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* Contains information about a single Podcast.
*/
public static class Podcast {
private String name;
private String publishDate;
private String enclosureUrl;
private long length;
private String type;
public Podcast(String name, String publishDate, String enclosureUrl, long length, String type) {
this.name = name;
this.publishDate = publishDate;
this.enclosureUrl = enclosureUrl;
this.length = length;
this.type = type;
}
public String getName() {
return name;
}
public String getPublishDate() {
return publishDate;
}
public String getEnclosureUrl() {
return enclosureUrl;
}
public long getLength() {
return length;
}
public String getType() {
return type;
}
}
}
@@ -0,0 +1,98 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.PodcastEpisode;
import net.sourceforge.subsonic.domain.PodcastStatus;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Controller for the "Podcast receiver" page.
*
* @author Sindre Mehus
*/
public class PodcastReceiverAdminController extends AbstractController {
private PodcastService podcastService;
@Override
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);
}
}
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
}
@@ -0,0 +1,68 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.PodcastService;
import net.sourceforge.subsonic.command.PodcastSettingsCommand;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to administrate the Podcast receiver.
*
* @author Sindre Mehus
*/
public class PodcastSettingsController extends SimpleFormController {
private SettingsService settingsService;
private PodcastService podcastService;
protected Object formBackingObject(HttpServletRequest request) 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());
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
PodcastSettingsCommand command = (PodcastSettingsCommand) comm;
command.setToast(true);
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();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
}
@@ -0,0 +1,79 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.PremiumSettingsCommand;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the Subsonic Premium page.
*
* @author Sindre Mehus
*/
public class PremiumSettingsController extends SimpleFormController {
private SettingsService settingsService;
private SecurityService securityService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PremiumSettingsCommand command = new PremiumSettingsCommand();
command.setPath(request.getParameter("path"));
command.setForceChange(request.getParameter("change") != null);
command.setLicenseInfo(settingsService.getLicenseInfo());
command.setBrand(settingsService.getBrand());
command.setUser(securityService.getCurrentUser(request));
return command;
}
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object com, BindException errors)
throws Exception {
PremiumSettingsCommand command = (PremiumSettingsCommand) com;
Date now = new Date();
settingsService.setLicenseCode(command.getLicenseCode());
settingsService.setLicenseEmail(command.getLicenseInfo().getLicenseEmail());
settingsService.setLicenseDate(now);
settingsService.save();
settingsService.scheduleLicenseValidation();
// Reflect changes in view. The validator will validate the license asynchronously.
command.setLicenseInfo(settingsService.getLicenseInfo());
command.setToast(true);
return new ModelAndView(getSuccessView(), errors.getModel());
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,68 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* A proxy for external HTTP requests.
*
* @author Sindre Mehus
*/
public class ProxyController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = ServletRequestUtils.getRequiredStringParameter(request, "url");
HttpClient client = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000);
HttpConnectionParams.setSoTimeout(client.getParams(), 15000);
HttpGet method = new HttpGet(url);
InputStream in = null;
try {
HttpResponse 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);
client.getConnectionManager().shutdown();
}
return null;
}
}
@@ -0,0 +1,123 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.RandomSearchCriteria;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the creating a random play queue.
*
* @author Sindre Mehus
*/
public class RandomPlayQueueController extends ParameterizableViewController {
private PlayerService playerService;
private List<ReloadFrame> reloadFrames;
private SearchService searchService;
private SecurityService securityService;
private SettingsService settingsService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int size = ServletRequestUtils.getRequiredIntParameter(request, "size");
String genre = request.getParameter("genre");
if (StringUtils.equalsIgnoreCase("any", genre)) {
genre = null;
}
Integer fromYear = null;
Integer toYear = null;
String year = request.getParameter("year");
if (!StringUtils.equalsIgnoreCase("any", year)) {
String[] tmp = StringUtils.split(year);
fromYear = Integer.parseInt(tmp[0]);
toYear = Integer.parseInt(tmp[1]);
}
List<MusicFolder> musicFolders = getMusicFolders(request);
Player player = playerService.getPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
RandomSearchCriteria criteria = new RandomSearchCriteria(size, genre, fromYear, toYear, musicFolders);
playQueue.addFiles(false, searchService.getRandomSongs(criteria));
if (request.getParameter("autoRandom") != null) {
playQueue.setRandomSearchCriteria(criteria);
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("reloadFrames", reloadFrames);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
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);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setReloadFrames(List<ReloadFrame> reloadFrames) {
this.reloadFrames = reloadFrames;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,52 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
/**
* Used in subsonic-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,82 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.VersionService;
/**
* Controller for the right frame.
*
* @author Sindre Mehus
*/
public class RightController extends ParameterizableViewController {
private SettingsService settingsService;
private SecurityService securityService;
private VersionService versionService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
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("showChat", userSettings.isShowChatEnabled());
map.put("user", securityService.getCurrentUser(request));
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setVersionService(VersionService versionService) {
this.versionService = versionService;
}
}
@@ -0,0 +1,110 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import net.sourceforge.subsonic.command.SearchCommand;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.SearchCriteria;
import net.sourceforge.subsonic.domain.SearchResult;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the search page.
*
* @author Sindre Mehus
*/
public class SearchController extends SimpleFormController {
private static final int MATCH_COUNT = 25;
private SecurityService securityService;
private SettingsService settingsService;
private PlayerService playerService;
private SearchService searchService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
return new SearchCommand();
}
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object com, BindException errors)
throws Exception {
SearchCommand command = (SearchCommand) com;
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 new ModelAndView(getSuccessView(), errors.getModel());
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
}
@@ -0,0 +1,60 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller for updating music file metadata.
*
* @author Sindre Mehus
*/
public class SetMusicFileInfoController extends AbstractController {
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 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));
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,70 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.RatingService;
import net.sourceforge.subsonic.service.SecurityService;
/**
* Controller for updating music file ratings.
*
* @author Sindre Mehus
*/
public class SetRatingController extends AbstractController {
private RatingService ratingService;
private SecurityService securityService;
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 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));
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,52 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.*;
import net.sourceforge.subsonic.service.*;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.view.*;
import org.springframework.web.servlet.mvc.*;
import javax.servlet.http.*;
/**
* Controller for the main settings page.
*
* @author Sindre Mehus
*/
public class SettingsController extends AbstractController {
private SecurityService securityService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
// Redirect to music folder settings if admin.
String view = user.isAdminRole() ? "musicFolderSettings.view" : "personalSettings.view";
return new ModelAndView(new RedirectView(view));
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,147 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.Share;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.ShareService;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for sharing music on Twitter, Facebook etc.
*
* @author Sindre Mehus
*/
public class ShareManagementController extends MultiActionController {
private MediaFileService mediaFileService;
private SettingsService settingsService;
private ShareService shareService;
private PlayerService playerService;
private PlaylistService playlistService;
private SecurityService securityService;
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("urlRedirectionEnabled", settingsService.isUrlRedirectionEnabled());
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(share));
map.put("licenseInfo", settingsService.getLicenseInfo());
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<MediaFile>();
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;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setShareService(ShareService shareService) {
this.shareService = shareService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,183 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.Share;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.ShareService;
/**
* Controller for the page used to administrate the set of shared media.
*
* @author Sindre Mehus
*/
public class ShareSettingsController extends ParameterizableViewController {
private ShareService shareService;
private SecurityService securityService;
private MediaFileService mediaFileService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
handleParameters(request);
map.put("toast", true);
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("shareBaseUrl", shareService.getShareBaseUrl());
map.put("shareInfos", getShareInfos(request));
map.put("user", securityService.getCurrentUser(request));
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
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(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 void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setShareService(ShareService shareService) {
this.shareService = shareService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public static class ShareInfo {
private final Share share;
private final MediaFile dir;
public ShareInfo(Share share, MediaFile dir) {
this.share = share;
this.dir = dir;
}
public Share getShare() {
return share;
}
public MediaFile getDir() {
return dir;
}
}
}
@@ -0,0 +1,96 @@
/*
* This file is part of Subsonic.
*
* Subsonic 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.
*
* Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.SonosService;
/**
* Controller for the page used to administrate the Sonos music service settings.
*
* @author Sindre Mehus
*/
public class SonosSettingsController extends ParameterizableViewController {
private SettingsService settingsService;
private SonosService sonosService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
handleParameters(request);
map.put("toast", true);
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("sonosEnabled", settingsService.isSonosEnabled());
map.put("sonosServiceName", settingsService.getSonosServiceName());
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
private void handleParameters(HttpServletRequest request) {
boolean sonosEnabled = ServletRequestUtils.getBooleanParameter(request, "sonosEnabled", false);
String sonosServiceName = StringUtils.trimToNull(request.getParameter("sonosServiceName"));
if (sonosServiceName == null) {
sonosServiceName = "Subsonic";
}
settingsService.setSonosEnabled(sonosEnabled);
settingsService.setSonosServiceName(sonosServiceName);
settingsService.save();
sonosService.setMusicServiceEnabled(false);
sonosService.setMusicServiceEnabled(sonosEnabled);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSonosService(SonosService sonosService) {
this.sonosService = sonosService;
}
}
@@ -0,0 +1,109 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.dao.MediaFileDao;
import net.sourceforge.subsonic.domain.CoverArtScheme;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
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
*/
public class StarredController extends ParameterizableViewController {
private PlayerService playerService;
private MediaFileDao mediaFileDao;
private SecurityService securityService;
private SettingsService settingsService;
private MediaFileService mediaFileService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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<MediaFile>();
List<MediaFile> videos = new ArrayList<MediaFile>();
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,149 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.*;
import net.sourceforge.subsonic.service.*;
import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.*;
import org.jfree.data.time.*;
import org.springframework.web.servlet.*;
import javax.servlet.http.*;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Controller for generating a chart showing bitrate vs time.
*
* @author Sindre Mehus
*/
public class StatusChartController extends AbstractChartController {
private StatusService statusService;
public static final int IMAGE_WIDTH = 350;
public static final int IMAGE_HEIGHT = 150;
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;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
}
@@ -0,0 +1,141 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.TransferStatus;
import net.sourceforge.subsonic.service.StatusService;
import net.sourceforge.subsonic.util.FileUtil;
import net.sourceforge.subsonic.util.StringUtil;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.support.RequestContextUtils;
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.Locale;
import java.util.Map;
/**
* Controller for the status page.
*
* @author Sindre Mehus
*/
public class StatusController extends ParameterizableViewController {
private StatusService statusService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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<TransferStatusHolder>();
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public static class TransferStatusHolder {
private TransferStatus transferStatus;
private boolean isStream;
private boolean isDownload;
private boolean isUpload;
private int index;
private Locale locale;
public 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,447 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.awt.Dimension;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.TransferStatus;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.VideoTranscodingSettings;
import net.sourceforge.subsonic.io.PlayQueueInputStream;
import net.sourceforge.subsonic.io.RangeOutputStream;
import net.sourceforge.subsonic.io.ShoutCastOutputStream;
import net.sourceforge.subsonic.service.AudioScrobblerService;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.StatusService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.service.sonos.SonosHelper;
import net.sourceforge.subsonic.util.HttpRange;
import net.sourceforge.subsonic.util.StringUtil;
import net.sourceforge.subsonic.util.Util;
/**
* A controller which streams the content of a {@link net.sourceforge.subsonic.domain.PlayQueue} to a remote
* {@link Player}.
*
* @author Sindre Mehus
*/
public class StreamController implements Controller {
private static final Logger LOG = Logger.getLogger(StreamController.class);
private StatusService statusService;
private PlayerService playerService;
private PlaylistService playlistService;
private SecurityService securityService;
private SettingsService settingsService;
private TranscodingService transcodingService;
private AudioScrobblerService audioScrobblerService;
private MediaFileService mediaFileService;
private SearchService searchService;
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());
try {
if (!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 (!securityService.isFolderAccessAllowed(file, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + file.getId() + " is forbidden for user " + user.getUsername());
return null;
}
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.SUBSONIC_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 Subsonic");
response.setHeader("icy-notice2", "Subsonic - Free media streamer - subsonic.org");
response.setHeader("icy-name", "Subsonic");
response.setHeader("icy-genre", "Mixed");
response.setHeader("icy-url", "http://subsonic.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();
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) {
this.audioScrobblerService = audioScrobblerService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
}
@@ -0,0 +1,68 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.AvatarScheme;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Controller for the top frame.
*
* @author Sindre Mehus
*/
public class TopController extends ParameterizableViewController {
private SettingsService settingsService;
private SecurityService securityService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,145 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.domain.Transcoding;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.service.SettingsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the set of transcoding configurations.
*
* @author Sindre Mehus
*/
public class TranscodingSettingsController extends ParameterizableViewController {
private TranscodingService transcodingService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
handleParameters(request, map);
map.put("toast", true);
}
ModelAndView result = super.handleRequestInternal(request, response);
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());
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
private void handleParameters(HttpServletRequest request, Map<String, Object> map) {
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) {
map.put("error", "transcodingsettings.noname");
} else if (sourceFormats == null) {
map.put("error", "transcodingsettings.nosourceformat");
} else if (targetFormat == null) {
map.put("error", "transcodingsettings.notargetformat");
} else if (step1 == null) {
map.put("error", "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);
if (name == null) {
map.put("error", "transcodingsettings.noname");
} else if (sourceFormats == null) {
map.put("error", "transcodingsettings.nosourceformat");
} else if (targetFormat == null) {
map.put("error", "transcodingsettings.notargetformat");
} else if (step1 == null) {
map.put("error", "transcodingsettings.nostep1");
} else {
transcodingService.createTranscoding(transcoding);
}
if (map.containsKey("error")) {
map.put("newTranscoding", transcoding);
}
}
settingsService.setDownsamplingCommand(StringUtils.trim(request.getParameter("downsampleCommand")));
settingsService.setHlsCommand(StringUtils.trim(request.getParameter("hlsCommand")));
settingsService.save();
}
private String getParameter(HttpServletRequest request, String name, Integer id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,260 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.*;
import net.sourceforge.subsonic.domain.*;
import net.sourceforge.subsonic.upload.*;
import net.sourceforge.subsonic.service.*;
import net.sourceforge.subsonic.util.*;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.servlet.*;
import org.apache.commons.io.*;
import org.apache.tools.zip.*;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
/**
* Controller which receives uploaded files.
*
* @author Sindre Mehus
*/
public class UploadController extends ParameterizableViewController {
private static final Logger LOG = Logger.getLogger(UploadController.class);
private SecurityService securityService;
private PlayerService playerService;
private StatusService statusService;
private SettingsService settingsService;
public static final String UPLOAD_STATUS = "uploadStatus";
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
List<File> uploadedFiles = new ArrayList<File>();
List<File> unzippedFiles = new ArrayList<File>();
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);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
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();
}
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
/**
* 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());
}
}
}
@@ -0,0 +1,145 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.springframework.web.servlet.ModelAndView;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.SecurityService;
/**
* Controller for generating a chart showing bitrate vs time.
*
* @author Sindre Mehus
*/
public class UserChartController extends AbstractChartController {
private SecurityService securityService;
public static final int IMAGE_WIDTH = 400;
public static final int IMAGE_MIN_HEIGHT = 200;
private static final long BYTES_PER_MB = 1024L * 1024L;
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String type = request.getParameter("type");
CategoryDataset dataset = createDataset(type);
JFreeChart chart = createChart(dataset, request);
int imageHeight = Math.max(IMAGE_MIN_HEIGHT, 15 * dataset.getColumnCount());
ChartUtilities.writeChartAsPNG(response.getOutputStream(), chart, IMAGE_WIDTH, imageHeight);
return null;
}
private CategoryDataset createDataset(String type) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
List<User> users = securityService.getAllUsers();
for (User user : users) {
double value;
if ("stream".equals(type)) {
value = user.getBytesStreamed();
} else if ("download".equals(type)) {
value = user.getBytesDownloaded();
} else if ("upload".equals(type)) {
value = user.getBytesUploaded();
} else if ("total".equals(type)) {
value = user.getBytesStreamed() + user.getBytesDownloaded() + user.getBytesUploaded();
} else {
throw new RuntimeException("Illegal chart type: " + type);
}
value /= BYTES_PER_MB;
dataset.addValue(value, "Series", user.getUsername());
}
return dataset;
}
private JFreeChart createChart(CategoryDataset dataset, HttpServletRequest request) {
JFreeChart chart = ChartFactory.createBarChart(null, null, null, dataset, PlotOrientation.HORIZONTAL, false, false, false);
CategoryPlot plot = chart.getCategoryPlot();
Paint background = new GradientPaint(0, 0, Color.lightGray, 0, IMAGE_MIN_HEIGHT, Color.white);
plot.setBackgroundPaint(background);
plot.setDomainGridlinePaint(Color.white);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.white);
plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
LogarithmicAxis rangeAxis = new LogarithmicAxis(null);
rangeAxis.setStrictValuesFlag(false);
rangeAxis.setAllowNegativesFlag(true);
plot.setRangeAxis(rangeAxis);
// Disable bar outlines.
BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDrawBarOutline(false);
// Set up gradient paint for series.
GradientPaint gp0 = new GradientPaint(
0.0f, 0.0f, Color.blue,
0.0f, 0.0f, new Color(0, 0, 64)
);
renderer.setSeriesPaint(0, gp0);
// Rotate labels.
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
// Set theme-specific colors.
Color bgColor = getBackground(request);
Color fgColor = getForeground(request);
chart.setBackgroundPaint(bgColor);
domainAxis.setTickLabelPaint(fgColor);
domainAxis.setTickMarkPaint(fgColor);
domainAxis.setAxisLinePaint(fgColor);
rangeAxis.setTickLabelPaint(fgColor);
rangeAxis.setTickMarkPaint(fgColor);
rangeAxis.setAxisLinePaint(fgColor);
return chart;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,189 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import net.sourceforge.subsonic.command.UserSettingsCommand;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.TranscodeScheme;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.domain.UserSettings;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.service.TranscodingService;
import net.sourceforge.subsonic.util.Util;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Controller for the page used to administrate users.
*
* @author Sindre Mehus
*/
public class UserSettingsController extends SimpleFormController {
private SecurityService securityService;
private SettingsService settingsService;
private TranscodingService transcodingService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
UserSettingsCommand command = new UserSettingsCommand();
User user = getUser(request);
if (user != null) {
command.setUser(user);
command.setEmail(user.getEmail());
command.setAdmin(User.USERNAME_ADMIN.equals(user.getUsername()));
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
command.setTranscodeSchemeName(userSettings.getTranscodeScheme().name());
} else {
command.setNewUser(true);
command.setStreamRole(true);
command.setSettingsRole(true);
}
command.setUsers(securityService.getAllUsers());
command.setTranscodingSupported(transcodingService.isDownsamplingSupported(null));
command.setTranscodeDirectory(transcodingService.getTranscodeDirectory().getPath());
command.setTranscodeSchemes(TranscodeScheme.values());
command.setLdapEnabled(settingsService.isLdapEnabled());
command.setAllMusicFolders(settingsService.getAllMusicFolders());
command.setAllowedMusicFolderIds(Util.toIntArray(getAllowedMusicFolderIds(user)));
return command;
}
private User getUser(HttpServletRequest request) throws ServletRequestBindingException {
Integer userIndex = ServletRequestUtils.getIntParameter(request, "userIndex");
if (userIndex != null) {
List<User> allUsers = securityService.getAllUsers();
if (userIndex >= 0 && userIndex < allUsers.size()) {
return allUsers.get(userIndex);
}
}
return null;
}
private List<Integer> getAllowedMusicFolderIds(User user) {
List<Integer> result = new ArrayList<Integer>();
List<MusicFolder> allowedMusicFolders = user == null
? settingsService.getAllMusicFolders()
: settingsService.getMusicFoldersForUser(user.getUsername());
for (MusicFolder musicFolder : allowedMusicFolders) {
result.add(musicFolder.getId());
}
return result;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
UserSettingsCommand command = (UserSettingsCommand) comm;
if (command.isDeleteUser()) {
deleteUser(command);
} else if (command.isNewUser()) {
createUser(command);
} else {
updateUser(command);
}
resetCommand(command);
}
private void deleteUser(UserSettingsCommand command) {
securityService.deleteUser(command.getUsername());
}
public void createUser(UserSettingsCommand command) {
User user = new User(command.getUsername(), command.getPassword(), StringUtils.trimToNull(command.getEmail()));
user.setLdapAuthenticated(command.isLdapAuthenticated());
securityService.createUser(user);
updateUser(command);
}
public void updateUser(UserSettingsCommand command) {
User user = securityService.getUserByName(command.getUsername());
user.setEmail(StringUtils.trimToNull(command.getEmail()));
user.setLdapAuthenticated(command.isLdapAuthenticated());
user.setAdminRole(command.isAdminRole());
user.setDownloadRole(command.isDownloadRole());
user.setUploadRole(command.isUploadRole());
user.setCoverArtRole(command.isCoverArtRole());
user.setCommentRole(command.isCommentRole());
user.setPodcastRole(command.isPodcastRole());
user.setStreamRole(command.isStreamRole());
user.setJukeboxRole(command.isJukeboxRole());
user.setSettingsRole(command.isSettingsRole());
user.setShareRole(command.isShareRole());
if (command.isPasswordChange()) {
user.setPassword(command.getPassword());
}
securityService.updateUser(user);
UserSettings userSettings = settingsService.getUserSettings(command.getUsername());
userSettings.setTranscodeScheme(TranscodeScheme.valueOf(command.getTranscodeSchemeName()));
userSettings.setChanged(new Date());
settingsService.updateUserSettings(userSettings);
List<Integer> allowedMusicFolderIds = Util.toIntegerList(command.getAllowedMusicFolderIds());
settingsService.setMusicFoldersForUser(command.getUsername(), allowedMusicFolderIds);
}
private void resetCommand(UserSettingsCommand command) {
command.setUser(null);
command.setUsers(securityService.getAllUsers());
command.setDeleteUser(false);
command.setPasswordChange(false);
command.setNewUser(true);
command.setStreamRole(true);
command.setSettingsRole(true);
command.setPassword(null);
command.setConfirmPassword(null);
command.setEmail(null);
command.setTranscodeSchemeName(null);
command.setAllMusicFolders(settingsService.getAllMusicFolders());
command.setAllowedMusicFolderIds(Util.toIntArray(getAllowedMusicFolderIds(null)));
command.setToast(true);
command.setReload(true);
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
}
@@ -0,0 +1,118 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
import net.sourceforge.subsonic.util.StringUtil;
/**
* Controller for the page used to play videos.
*
* @author Sindre Mehus
*/
public class VideoPlayerController extends ParameterizableViewController {
public static final int DEFAULT_BIT_RATE = 2000;
public static final int[] BIT_RATES = {200, 300, 400, 500, 700, 1000, 1200, 1500, 2000, 3000, 5000};
private MediaFileService mediaFileService;
private SettingsService settingsService;
private PlayerService playerService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
Map<String, Object> map = new HashMap<String, Object>();
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
MediaFile file = mediaFileService.getMediaFile(id);
mediaFileService.populateStarredDate(file, user.getUsername());
Integer duration = file.getDurationSeconds();
String playerId = playerService.getPlayer(request, response).getId();
String url = request.getRequestURL().toString();
String streamUrl = url.replaceFirst("/videoPlayer.view.*", "/stream?id=" + file.getId() + "&player=" + playerId);
String coverArtUrl = url.replaceFirst("/videoPlayer.view.*", "/coverArt.view?id=" + file.getId());
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
streamUrl = StringUtil.rewriteUrl(streamUrl, referer);
coverArtUrl = StringUtil.rewriteUrl(coverArtUrl, referer);
}
String remoteStreamUrl = settingsService.rewriteRemoteUrl(streamUrl);
String remoteCoverArtUrl = settingsService.rewriteRemoteUrl(coverArtUrl);
map.put("video", file);
map.put("streamUrl", streamUrl);
map.put("remoteStreamUrl", remoteStreamUrl);
map.put("remoteCoverArtUrl", remoteCoverArtUrl);
map.put("duration", duration);
map.put("bitRates", BIT_RATES);
map.put("defaultBitRate", DEFAULT_BIT_RATE);
map.put("licenseInfo", settingsService.getLicenseInfo());
map.put("user", user);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public static Map<String, Integer> createSkipOffsets(int durationSeconds) {
LinkedHashMap<String, Integer> result = new LinkedHashMap<String, Integer>();
for (int i = 0; i < durationSeconds; i += 60) {
result.put(StringUtil.formatDuration(i), i);
}
return result;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,251 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.MusicFolder;
import net.sourceforge.subsonic.domain.MusicIndex;
import net.sourceforge.subsonic.domain.PlayQueue;
import net.sourceforge.subsonic.domain.Player;
import net.sourceforge.subsonic.domain.RandomSearchCriteria;
import net.sourceforge.subsonic.domain.SearchCriteria;
import net.sourceforge.subsonic.domain.SearchResult;
import net.sourceforge.subsonic.domain.User;
import net.sourceforge.subsonic.service.MediaFileService;
import net.sourceforge.subsonic.service.MusicIndexService;
import net.sourceforge.subsonic.service.PlayerService;
import net.sourceforge.subsonic.service.PlaylistService;
import net.sourceforge.subsonic.service.SearchService;
import net.sourceforge.subsonic.service.SecurityService;
import net.sourceforge.subsonic.service.SettingsService;
/**
* Multi-controller used for wap pages.
*
* @author Sindre Mehus
*/
public class WapController extends MultiActionController {
private SettingsService settingsService;
private PlayerService playerService;
private PlaylistService playlistService;
private SecurityService securityService;
private MusicIndexService musicIndexService;
private MediaFileService mediaFileService;
private SearchService searchService;
public ModelAndView index(HttpServletRequest request, HttpServletResponse response) throws Exception {
return wap(request, response);
}
public ModelAndView wap(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> folders = settingsService.getMusicFoldersForUser(username);
if (folders.isEmpty()) {
map.put("noMusic", true);
} else {
SortedMap<MusicIndex, List<MusicIndex.SortableArtistWithMediaFiles>> allArtists = musicIndexService.getIndexedArtists(folders, false);
// If an index is given as parameter, only show music files for this index.
String index = request.getParameter("index");
if (index != null) {
List<MusicIndex.SortableArtistWithMediaFiles> artists = allArtists.get(new MusicIndex(index));
if (artists == null) {
map.put("noMusic", true);
} else {
map.put("artists", artists);
}
}
// Otherwise, list all indexes.
else {
map.put("indexes", allArtists.keySet());
}
}
return new ModelAndView("wap/index", "model", map);
}
public ModelAndView browse(HttpServletRequest request, HttpServletResponse response) throws Exception {
String path = request.getParameter("path");
MediaFile parent = mediaFileService.getMediaFile(path);
// Create array of file(s) to display.
List<MediaFile> children;
if (parent.isDirectory()) {
children = mediaFileService.getChildrenOf(parent, true, true, true);
} else {
children = new ArrayList<MediaFile>();
children.add(parent);
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("parent", parent);
map.put("children", children);
map.put("user", securityService.getCurrentUser(request));
return new ModelAndView("wap/browse", "model", map);
}
public ModelAndView playlist(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Create array of players to control. If the "player" attribute is set for this session,
// only the player with this ID is controlled. Otherwise, all players are controlled.
List<Player> players = playerService.getAllPlayers();
String playerId = (String) request.getSession().getAttribute("player");
if (playerId != null) {
Player player = playerService.getPlayerById(playerId);
if (player != null) {
players = Arrays.asList(player);
}
}
Map<String, Object> map = new HashMap<String, Object>();
for (Player player : players) {
PlayQueue playQueue = player.getPlayQueue();
map.put("playlist", playQueue);
if (request.getParameter("play") != null) {
MediaFile file = mediaFileService.getMediaFile(request.getParameter("play"));
playQueue.addFiles(false, file);
} else if (request.getParameter("add") != null) {
MediaFile file = mediaFileService.getMediaFile(request.getParameter("add"));
playQueue.addFiles(true, file);
} else if (request.getParameter("skip") != null) {
playQueue.setIndex(Integer.parseInt(request.getParameter("skip")));
} else if (request.getParameter("clear") != null) {
playQueue.clear();
} else if (request.getParameter("load") != null) {
List<MediaFile> songs = playlistService.getFilesInPlaylist(ServletRequestUtils.getIntParameter(request, "id"));
playQueue.addFiles(false, songs);
} else if (request.getParameter("random") != null) {
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(securityService.getCurrentUsername(request));
List<MediaFile> randomFiles = searchService.getRandomSongs(new RandomSearchCriteria(20, null, null, null, musicFolders));
playQueue.addFiles(false, randomFiles);
}
}
map.put("players", players);
return new ModelAndView("wap/playlist", "model", map);
}
public ModelAndView loadPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("playlists", playlistService.getReadablePlaylistsForUser(securityService.getCurrentUsername(request)));
return new ModelAndView("wap/loadPlaylist", "model", map);
}
public ModelAndView search(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView("wap/search");
}
public ModelAndView searchResult(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = securityService.getCurrentUsername(request);
String query = request.getParameter("query");
Map<String, Object> map = new HashMap<String, Object>();
map.put("hits", search(query, username));
return new ModelAndView("wap/searchResult", "model", map);
}
public ModelAndView settings(HttpServletRequest request, HttpServletResponse response) throws Exception {
String playerId = (String) request.getSession().getAttribute("player");
List<Player> allPlayers = playerService.getAllPlayers();
User user = securityService.getCurrentUser(request);
List<Player> players = new ArrayList<Player>();
Map<String, Object> map = new HashMap<String, Object>();
for (Player player : allPlayers) {
// Only display authorized players.
if (user.isAdminRole() || user.getUsername().equals(player.getUsername())) {
players.add(player);
}
}
map.put("playerId", playerId);
map.put("players", players);
return new ModelAndView("wap/settings", "model", map);
}
public ModelAndView selectPlayer(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getSession().setAttribute("player", request.getParameter("playerId"));
return settings(request, response);
}
private List<MediaFile> search(String query, String username) throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setQuery(query);
criteria.setOffset(0);
criteria.setCount(50);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
SearchResult result = searchService.search(criteria, musicFolders, SearchService.IndexType.SONG);
return result.getMediaFiles();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMusicIndexService(MusicIndexService musicIndexService) {
this.musicIndexService = musicIndexService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
}
@@ -0,0 +1,175 @@
/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.sourceforge.subsonic.dao;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import net.sourceforge.subsonic.Logger;
/**
* Abstract superclass for all DAO's.
*
* @author Sindre Mehus
*/
public class AbstractDao {
private static final Logger LOG = Logger.getLogger(AbstractDao.class);
private DaoHelper daoHelper;
/**
* Returns a JDBC template for performing database operations.
* @return A JDBC template.
*/
public JdbcTemplate getJdbcTemplate() {
return daoHelper.getJdbcTemplate();
}
/**
* Similar to {@link #getJdbcTemplate()}, but with named parameters.
*/
public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
return daoHelper.getNamedParameterJdbcTemplate();
}
protected String questionMarks(String columns) {
int count = columns.split(", ").length;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < count; i++) {
builder.append('?');
if (i < count - 1) {
builder.append(", ");
}
}
return builder.toString();
}
protected String prefix(String columns, String prefix) {
StringBuilder builder = new StringBuilder();
for (String s : columns.split(", ")) {
builder.append(prefix).append(".").append(s).append(",");
}
if (builder.length() > 0) {
builder.setLength(builder.length() - 1);
}
return builder.toString();
}
protected int update(String sql, Object... args) {
long t = System.nanoTime();
int result = getJdbcTemplate().update(sql, args);
log(sql, t);
return result;
}
private void log(String sql, long startTimeNano) {
long millis = (System.nanoTime() - startTimeNano) / 1000000L;
// Log queries that take more than 2 seconds.
if (millis > TimeUnit.SECONDS.toMillis(2L)) {
LOG.debug(millis + " ms: " + sql);
}
}
protected <T> List<T> query(String sql, RowMapper rowMapper, Object... args) {
long t = System.nanoTime();
List<T> result = getJdbcTemplate().query(sql, args, rowMapper);
log(sql, t);
return result;
}
protected <T> List<T> namedQuery(String sql, RowMapper rowMapper, Map<String, Object> args) {
long t = System.nanoTime();
List<T> result = getNamedParameterJdbcTemplate().query(sql, args, rowMapper);
log(sql, t);
return result;
}
protected List<String> queryForStrings(String sql, Object... args) {
long t = System.nanoTime();
List<String> result = getJdbcTemplate().queryForList(sql, args, String.class);
log(sql, t);
return result;
}
protected List<Integer> queryForInts(String sql, Object... args) {
long t = System.nanoTime();
List<Integer> result = getJdbcTemplate().queryForList(sql, args, Integer.class);
log(sql, t);
return result;
}
protected List<String> namedQueryForStrings(String sql, Map<String, Object> args) {
long t = System.nanoTime();
List<String> result = getNamedParameterJdbcTemplate().queryForList(sql, args, String.class);
log(sql, t);
return result;
}
protected Integer queryForInt(String sql, Integer defaultValue, Object... args) {
long t = System.nanoTime();
List<Integer> list = getJdbcTemplate().queryForList(sql, args, Integer.class);
Integer result = list.isEmpty() ? defaultValue : list.get(0) == null ? defaultValue : list.get(0);
log(sql, t);
return result;
}
protected Integer namedQueryForInt(String sql, Integer defaultValue, Map<String, Object> args) {
long t = System.nanoTime();
List<Integer> list = getNamedParameterJdbcTemplate().queryForList(sql, args, Integer.class);
Integer result = list.isEmpty() ? defaultValue : list.get(0) == null ? defaultValue : list.get(0);
log(sql, t);
return result;
}
protected Date queryForDate(String sql, Date defaultValue, Object... args) {
long t = System.nanoTime();
List<Date> list = getJdbcTemplate().queryForList(sql, args, Date.class);
Date result = list.isEmpty() ? defaultValue : list.get(0) == null ? defaultValue : list.get(0);
log(sql, t);
return result;
}
protected Long queryForLong(String sql, Long defaultValue, Object... args) {
long t = System.nanoTime();
List<Long> list = getJdbcTemplate().queryForList(sql, args, Long.class);
Long result = list.isEmpty() ? defaultValue : list.get(0) == null ? defaultValue : list.get(0);
log(sql, t);
return result;
}
protected <T> T queryOne(String sql, RowMapper rowMapper, Object... args) {
List<T> list = query(sql, rowMapper, args);
return list.isEmpty() ? null : list.get(0);
}
protected <T> T namedQueryOne(String sql, RowMapper rowMapper, Map<String, Object> args) {
List<T> list = namedQuery(sql, rowMapper, args);
return list.isEmpty() ? null : list.get(0);
}
public void setDaoHelper(DaoHelper daoHelper) {
this.daoHelper = daoHelper;
}
}

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