diff options
Diffstat (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/ajax')
18 files changed, 2127 insertions, 0 deletions
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ChatService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ChatService.java new file mode 100644 index 00000000..8905c8a6 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ChatService.java @@ -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; + } + + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtInfo.java new file mode 100644 index 00000000..c9160f26 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtInfo.java @@ -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; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtService.java new file mode 100644 index 00000000..1c3642b6 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtService.java @@ -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.ajax; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +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; + 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.backup." + suffix)); + + // Write file. + IOUtils.copy(input, new FileOutputStream(newCoverFile)); + + MediaFile mediaFile = mediaFileService.getMediaFile(path); + + // Rename existing cover file if new cover file is not the preferred. + try { + File coverFile = mediaFileService.getCoverArt(mediaFile); + if (coverFile != null) { + if (!newCoverFile.equals(coverFile)) { + coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old")); + LOG.info("Renamed old image file " + coverFile); + } + } + } catch (Exception x) { + LOG.warn("Failed to rename existing cover file.", x); + } + + mediaFileService.refreshMediaFile(mediaFile); + + } finally { + IOUtils.closeQuietly(input); + client.getConnectionManager().shutdown(); + } + } + + 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; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsInfo.java new file mode 100644 index 00000000..b84ffe1f --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsInfo.java @@ -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 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; + + 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; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsService.java new file mode 100644 index 00000000..45c039f7 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsService.java @@ -0,0 +1,105 @@ +/* + 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.util.StringUtil; + +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.DefaultHttpClient; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.params.HttpConnectionParams; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; + +import java.io.IOException; +import java.io.StringReader; + +/** + * 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 (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 = 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(); + } + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/MultiService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/MultiService.java new file mode 100644 index 00000000..0c83e30f --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/MultiService.java @@ -0,0 +1,51 @@ +/* + 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.NetworkService; + +/** + * 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; + + /** + * 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 void setNetworkService(NetworkService networkService) { + this.networkService = networkService; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NetworkStatus.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NetworkStatus.java new file mode 100644 index 00000000..8634af26 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NetworkStatus.java @@ -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; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingInfo.java new file mode 100644 index 00000000..520cfcab --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingInfo.java @@ -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 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 coverArtZoomUrl; + private final String avatarUrl; + private final int minutesAgo; + + public NowPlayingInfo(String user, String artist, String title, String tooltip, String streamUrl, String albumUrl, + String lyricsUrl, String coverArtUrl, String coverArtZoomUrl, String avatarUrl, int minutesAgo) { + 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.coverArtZoomUrl = coverArtZoomUrl; + this.avatarUrl = avatarUrl; + this.minutesAgo = minutesAgo; + } + + 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 getCoverArtZoomUrl() { + return coverArtZoomUrl; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public int getMinutesAgo() { + return minutesAgo; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingService.java new file mode 100644 index 00000000..ef7922b4 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingService.java @@ -0,0 +1,172 @@ +/* + 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.AvatarScheme; +import net.sourceforge.subsonic.domain.MediaFile; +import net.sourceforge.subsonic.domain.Player; +import net.sourceforge.subsonic.domain.TransferStatus; +import net.sourceforge.subsonic.domain.UserSettings; +import net.sourceforge.subsonic.service.MediaFileService; +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; +import org.apache.commons.lang.StringUtils; +import org.directwebremoting.WebContext; +import org.directwebremoting.WebContextFactory; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides AJAX-enabled services for retrieving the currently playing file and directory. + * This class is used by the DWR framework (http://getahead.ltd.uk/dwr/). + * + * @author Sindre Mehus + */ +public class NowPlayingService { + + private PlayerService playerService; + private StatusService statusService; + private SettingsService settingsService; + private MediaScannerService mediaScannerService; + private MediaFileService mediaFileService; + + /** + * 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()); + List<TransferStatus> statuses = statusService.getStreamStatusesForPlayer(player); + List<NowPlayingInfo> result = convert(statuses); + + return result.isEmpty() ? null : result.get(0); + } + + /** + * Returns details about what all users are currently playing. + * + * @return Details about what all users are currently playing. + */ + public List<NowPlayingInfo> getNowPlaying() throws Exception { + return convert(statusService.getAllStreamStatuses()); + } + + /** + * Returns media folder scanning status. + */ + public ScanInfo getScanningStatus() { + return new ScanInfo(mediaScannerService.isScanning(), mediaScannerService.getScanCount()); + } + + private List<NowPlayingInfo> convert(List<TransferStatus> statuses) throws Exception { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + String url = request.getRequestURL().toString(); + List<NowPlayingInfo> result = new ArrayList<NowPlayingInfo>(); + for (TransferStatus status : statuses) { + + Player player = status.getPlayer(); + File file = status.getFile(); + + if (player != null && player.getUsername() != null && file != null) { + + String username = player.getUsername(); + UserSettings userSettings = settingsService.getUserSettings(username); + if (!userSettings.isNowPlayingAllowed()) { + continue; + } + + MediaFile mediaFile = mediaFileService.getMediaFile(file); + File coverArt = mediaFileService.getCoverArt(mediaFile); + + 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 = url.replaceFirst("/dwr/.*", "/lyrics.view?artistUtf8Hex=" + StringUtil.utf8HexEncode(artist) + + "&songUtf8Hex=" + StringUtil.utf8HexEncode(title)); + String coverArtUrl = coverArt == null ? null : url.replaceFirst("/dwr/.*", "/coverArt.view?size=48&id=" + mediaFile.getId()); + String coverArtZoomUrl = coverArt == null ? null : url.replaceFirst("/dwr/.*", "/coverArt.view?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?username=" + 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); + coverArtZoomUrl = StringUtil.rewriteUrl(coverArtZoomUrl, referer); + avatarUrl = StringUtil.rewriteUrl(avatarUrl, referer); + } + + String tooltip = StringUtil.toHtml(artist) + " – " + StringUtil.toHtml(title); + + if (StringUtils.isNotBlank(player.getName())) { + username += "@" + player.getName(); + } + artist = StringUtil.toHtml(StringUtils.abbreviate(artist, 25)); + title = StringUtil.toHtml(StringUtils.abbreviate(title, 25)); + username = StringUtil.toHtml(StringUtils.abbreviate(username, 25)); + + long minutesAgo = status.getMillisSinceLastUpdate() / 1000L / 60L; + if (minutesAgo < 60) { + result.add(new NowPlayingInfo(username, artist, title, tooltip, streamUrl, albumUrl, lyricsUrl, + coverArtUrl, coverArtZoomUrl, 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; + } + + public void setMediaFileService(MediaFileService mediaFileService) { + this.mediaFileService = mediaFileService; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueInfo.java new file mode 100644 index 00000000..e95ec1c8 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueInfo.java @@ -0,0 +1,174 @@ +/* + 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; + +/** + * The playlist of a player. + * + * @author Sindre Mehus + */ +public class PlayQueueInfo { + + private final List<Entry> entries; + private final int index; + private final boolean stopEnabled; + private final boolean repeatEnabled; + private final boolean sendM3U; + private final float gain; + + public PlayQueueInfo(List<Entry> entries, int index, boolean stopEnabled, boolean repeatEnabled, boolean sendM3U, float gain) { + this.entries = entries; + this.index = index; + this.stopEnabled = stopEnabled; + this.repeatEnabled = repeatEnabled; + this.sendM3U = sendM3U; + this.gain = gain; + } + + public List<Entry> getEntries() { + return entries; + } + + public int getIndex() { + return index; + } + + public boolean isStopEnabled() { + return stopEnabled; + } + + public boolean isSendM3U() { + return sendM3U; + } + + public boolean isRepeatEnabled() { + return repeatEnabled; + } + + public float getGain() { + return gain; + } + + 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; + + 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) { + 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; + } + + 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; + } + } + +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueService.java new file mode 100644 index 00000000..94f78aba --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueService.java @@ -0,0 +1,455 @@ +/* + 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.text.DateFormat; +import java.util.ArrayList; +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 net.sourceforge.subsonic.dao.MediaFileDao; +import net.sourceforge.subsonic.domain.Playlist; +import net.sourceforge.subsonic.service.*; +import net.sourceforge.subsonic.service.PlaylistService; +import org.directwebremoting.WebContextFactory; +import org.springframework.web.servlet.support.RequestContextUtils; + +import net.sourceforge.subsonic.domain.MediaFile; +import net.sourceforge.subsonic.domain.Player; +import net.sourceforge.subsonic.domain.PlayQueue; +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 + */ +public class PlayQueueService { + + private PlayerService playerService; + private JukeboxService jukeboxService; + private TranscodingService transcodingService; + private SettingsService settingsService; + private MediaFileService mediaFileService; + private SecurityService securityService; + private MediaFileDao mediaFileDao; + private net.sourceforge.subsonic.service.PlaylistService playlistService; + + /** + * 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 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); + List<MediaFile> files = mediaFileService.getDescendantsOf(file, true); + return doPlay(request, player, files); + } + + public PlayQueueInfo playPlaylist(int id) throws Exception { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); + + List<MediaFile> files = playlistService.getFilesInPlaylist(id); + Player player = getCurrentPlayer(request, response); + return doPlay(request, player, files); + } + + private PlayQueueInfo doPlay(HttpServletRequest request, Player player, List<MediaFile> files) throws Exception { + if (player.isWeb()) { + 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 = getRandomChildren(file, count); + Player player = getCurrentPlayer(request, response); + player.getPlayQueue().addFiles(false, randomFiles); + player.getPlayQueue().setRandomSearchCriteria(null); + return convert(request, player, true); + } + + public PlayQueueInfo add(int id) throws Exception { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); + return doAdd(request, response, new int[]{id}); + } + + public PlayQueueInfo doAdd(HttpServletRequest request, HttpServletResponse response, int[] ids) 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()) { + removeVideoFiles(files); + } + 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); + + 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 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); + } + + + public String savePlaylist() { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); + Player player = getCurrentPlayer(request, response); + Locale locale = settingsService.getLocale(); + 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.setPublic(false); + playlist.setName(dateFormat.format(now)); + + playlistService.createPlaylist(playlist); + playlistService.setFilesInPlaylist(playlist.getId(), player.getPlayQueue().getFiles()); + return playlist.getName(); + } + + private List<MediaFile> getRandomChildren(MediaFile file, int count) throws IOException { + List<MediaFile> children = mediaFileService.getDescendantsOf(file, false); + removeVideoFiles(children); + + if (children.isEmpty()) { + return children; + } + Collections.shuffle(children); + return children.subList(0, Math.min(count, children.size())); + } + + private void removeVideoFiles(List<MediaFile> files) { + Iterator<MediaFile> iterator = files.iterator(); + while (iterator.hasNext()) { + MediaFile file = iterator.next(); + if (file.isVideo()) { + iterator.remove(); + } + } + } + + private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean sendM3U) throws Exception { + return convert(request, player, sendM3U, 0); + } + + private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean sendM3U, int offset) throws Exception { + String url = request.getRequestURL().toString(); + + if (sendM3U && player.isJukebox()) { + jukeboxService.updateJukebox(player, offset); + } + boolean isCurrentPlayer = player.getIpAddress() != null && player.getIpAddress().equals(request.getRemoteAddr()); + + boolean m3uSupported = player.isExternal() || player.isExternalWithPlaylist(); + sendM3U = player.isAutoControlEnabled() && m3uSupported && isCurrentPlayer && sendM3U; + 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()); + + // 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 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)); + } + boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist(); + float gain = jukeboxService.getGain(); + return new PlayQueueInfo(entries, playQueue.getIndex(), isStopEnabled, playQueue.isRepeatEnabled(), sendM3U, 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 setJukeboxService(JukeboxService jukeboxService) { + this.jukeboxService = jukeboxService; + } + + public void setTranscodingService(TranscodingService transcodingService) { + this.transcodingService = transcodingService; + } + + public void setSettingsService(SettingsService settingsService) { + this.settingsService = settingsService; + } + + public void setSecurityService(SecurityService securityService) { + this.securityService = securityService; + } + + public void setMediaFileDao(MediaFileDao mediaFileDao) { + this.mediaFileDao = mediaFileDao; + } + + public void setPlaylistService(PlaylistService playlistService) { + this.playlistService = playlistService; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistInfo.java new file mode 100644 index 00000000..3fcbfb14 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistInfo.java @@ -0,0 +1,90 @@ +/* + 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; + + public Entry(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; + } + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistService.java new file mode 100644 index 00000000..d3bf854f --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistService.java @@ -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.ajax; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import net.sourceforge.subsonic.dao.MediaFileDao; +import net.sourceforge.subsonic.service.SettingsService; +import org.directwebremoting.WebContextFactory; + +import net.sourceforge.subsonic.domain.MediaFile; +import net.sourceforge.subsonic.domain.Playlist; +import net.sourceforge.subsonic.service.MediaFileService; +import net.sourceforge.subsonic.service.SecurityService; + +/** + * 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; + + 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); + + String username = securityService.getCurrentUsername(request); + mediaFileService.populateStarredDate(files, username); + return new PlaylistInfo(playlist, createEntries(files)); + } + + public List<Playlist> createEmptyPlaylist() { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + Locale locale = settingsService.getLocale(); + 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.setPublic(false); + playlist.setName(dateFormat.format(now)); + + playlistService.createPlaylist(playlist); + return getReadablePlaylists(); + } + + public void appendToPlaylist(int playlistId, List<Integer> mediaFileIds) { + List<MediaFile> files = playlistService.getFilesInPlaylist(playlistId); + 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)); + } + + 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); + 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); + files.remove(index); + playlistService.setFilesInPlaylist(id, files); + return getPlaylist(id); + } + + public PlaylistInfo up(int id, int index) { + List<MediaFile> files = playlistService.getFilesInPlaylist(id); + if (index > 0) { + MediaFile file = files.remove(index); + files.add(index - 1, file); + playlistService.setFilesInPlaylist(id, files); + } + return getPlaylist(id); + } + + public PlaylistInfo down(int id, int index) { + List<MediaFile> files = playlistService.getFilesInPlaylist(id); + 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 isPublic) { + Playlist playlist = playlistService.getPlaylist(id); + playlist.setName(name); + playlist.setComment(comment); + playlist.setPublic(isPublic); + 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; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ScanInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ScanInfo.java new file mode 100644 index 00000000..d984069e --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ScanInfo.java @@ -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; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/StarService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/StarService.java new file mode 100644 index 00000000..15ba359b --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/StarService.java @@ -0,0 +1,64 @@ +/* + 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; + } +}
\ No newline at end of file diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TagService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TagService.java new file mode 100644 index 00000000..f7373b4e --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TagService.java @@ -0,0 +1,128 @@ +/* + 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.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; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; + +/** + * 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."; + } + + MetaData existingMetaData = parser.getRawMetaData(file.getFile()); + + if (StringUtils.equals(artist, existingMetaData.getArtist()) && + StringUtils.equals(album, existingMetaData.getAlbumName()) && + StringUtils.equals(title, existingMetaData.getTitle()) && + ObjectUtils.equals(yearNumber, existingMetaData.getYear()) && + StringUtils.equals(genre, existingMetaData.getGenre()) && + ObjectUtils.equals(trackNumber, existingMetaData.getTrackNumber())) { + return "SKIPPED"; + } + + MetaData newMetaData = new MetaData(); + 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); + 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; + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TransferService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TransferService.java new file mode 100644 index 00000000..19309348 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TransferService.java @@ -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); + } +} diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/UploadInfo.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/UploadInfo.java new file mode 100644 index 00000000..47f9de99 --- /dev/null +++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/UploadInfo.java @@ -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; + } + +} |