aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/java/net/sourceforge/subsonic/ajax
diff options
context:
space:
mode:
Diffstat (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/ajax')
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ChatService.java163
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtInfo.java43
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/CoverArtService.java145
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsInfo.java53
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/LyricsService.java105
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/MultiService.java51
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NetworkStatus.java55
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingInfo.java98
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/NowPlayingService.java172
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueInfo.java174
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlayQueueService.java455
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistInfo.java90
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/PlaylistService.java187
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/ScanInfo.java43
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/StarService.java64
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TagService.java128
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/TransferService.java49
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/ajax/UploadInfo.java52
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) + " &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.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;
+ }
+
+}