From 4738428c2c205f42200386ae09b44b9ec07b9144 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 27 Jul 2013 14:33:25 -0700 Subject: Move subsonic-android to root --- .../subsonic/service/PodcastService.java | 599 --------------------- 1 file changed, 599 deletions(-) delete mode 100644 subsonic-main/src/main/java/net/sourceforge/subsonic/service/PodcastService.java (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/service/PodcastService.java') diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/PodcastService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/PodcastService.java deleted file mode 100644 index 09184df6..00000000 --- a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/PodcastService.java +++ /dev/null @@ -1,599 +0,0 @@ -/* - 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 . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.service; - -import net.sourceforge.subsonic.Logger; -import net.sourceforge.subsonic.dao.PodcastDao; -import net.sourceforge.subsonic.domain.MediaFile; -import net.sourceforge.subsonic.domain.PodcastChannel; -import net.sourceforge.subsonic.domain.PodcastEpisode; -import net.sourceforge.subsonic.domain.PodcastStatus; -import net.sourceforge.subsonic.util.StringUtil; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -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 org.jdom.Document; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.input.SAXBuilder; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -/** - * Provides services for Podcast reception. - * - * @author Sindre Mehus - */ -public class PodcastService { - - private static final Logger LOG = Logger.getLogger(PodcastService.class); - private static final DateFormat[] RSS_DATE_FORMATS = {new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US), - new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.US)}; - - private static final Namespace[] ITUNES_NAMESPACES = {Namespace.getNamespace("http://www.itunes.com/DTDs/Podcast-1.0.dtd"), - Namespace.getNamespace("http://www.itunes.com/dtds/podcast-1.0.dtd")}; - - private final ExecutorService refreshExecutor; - private final ExecutorService downloadExecutor; - private final ScheduledExecutorService scheduledExecutor; - private ScheduledFuture scheduledRefresh; - private PodcastDao podcastDao; - private SettingsService settingsService; - private SecurityService securityService; - private MediaFileService mediaFileService; - - public PodcastService() { - ThreadFactory threadFactory = new ThreadFactory() { - public Thread newThread(Runnable r) { - Thread t = Executors.defaultThreadFactory().newThread(r); - t.setDaemon(true); - return t; - } - }; - refreshExecutor = Executors.newFixedThreadPool(5, threadFactory); - downloadExecutor = Executors.newFixedThreadPool(3, threadFactory); - scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory); - } - - public synchronized void init() { - // Clean up partial downloads. - for (PodcastChannel channel : getAllChannels()) { - for (PodcastEpisode episode : getEpisodes(channel.getId(), false)) { - if (episode.getStatus() == PodcastStatus.DOWNLOADING) { - deleteEpisode(episode.getId(), false); - LOG.info("Deleted Podcast episode '" + episode.getTitle() + "' since download was interrupted."); - } - } - } - - schedule(); - } - - public synchronized void schedule() { - Runnable task = new Runnable() { - public void run() { - LOG.info("Starting scheduled Podcast refresh."); - refreshAllChannels(true); - LOG.info("Completed scheduled Podcast refresh."); - } - }; - - if (scheduledRefresh != null) { - scheduledRefresh.cancel(true); - } - - int hoursBetween = settingsService.getPodcastUpdateInterval(); - - if (hoursBetween == -1) { - LOG.info("Automatic Podcast update disabled."); - return; - } - - long periodMillis = hoursBetween * 60L * 60L * 1000L; - long initialDelayMillis = 5L * 60L * 1000L; - - scheduledRefresh = scheduledExecutor.scheduleAtFixedRate(task, initialDelayMillis, periodMillis, TimeUnit.MILLISECONDS); - Date firstTime = new Date(System.currentTimeMillis() + initialDelayMillis); - LOG.info("Automatic Podcast update scheduled to run every " + hoursBetween + " hour(s), starting at " + firstTime); - } - - /** - * Creates a new Podcast channel. - * - * @param url The URL of the Podcast channel. - */ - public void createChannel(String url) { - url = sanitizeUrl(url); - PodcastChannel channel = new PodcastChannel(url); - int channelId = podcastDao.createChannel(channel); - - refreshChannels(Arrays.asList(getChannel(channelId)), true); - } - - private String sanitizeUrl(String url) { - return url.replace(" ", "%20"); - } - - private PodcastChannel getChannel(int channelId) { - for (PodcastChannel channel : getAllChannels()) { - if (channelId == channel.getId()) { - return channel; - } - } - return null; - } - - /** - * Returns all Podcast channels. - * - * @return Possibly empty list of all Podcast channels. - */ - public List getAllChannels() { - return podcastDao.getAllChannels(); - } - - /** - * Returns all Podcast episodes for a given channel. - * - * @param channelId The Podcast channel ID. - * @param includeDeleted Whether to include logically deleted episodes in the result. - * @return Possibly empty list of all Podcast episodes for the given channel, sorted in - * reverse chronological order (newest episode first). - */ - public List getEpisodes(int channelId, boolean includeDeleted) { - List all = podcastDao.getEpisodes(channelId); - addMediaFileIdToEpisodes(all); - if (includeDeleted) { - return all; - } - - List filtered = new ArrayList(); - for (PodcastEpisode episode : all) { - if (episode.getStatus() != PodcastStatus.DELETED) { - filtered.add(episode); - } - } - return filtered; - } - - public PodcastEpisode getEpisode(int episodeId, boolean includeDeleted) { - PodcastEpisode episode = podcastDao.getEpisode(episodeId); - if (episode == null) { - return null; - } - if (episode.getStatus() == PodcastStatus.DELETED && !includeDeleted) { - return null; - } - addMediaFileIdToEpisodes(Arrays.asList(episode)); - return episode; - } - - private void addMediaFileIdToEpisodes(List episodes) { - for (PodcastEpisode episode : episodes) { - if (episode.getPath() != null) { - MediaFile mediaFile = mediaFileService.getMediaFile(episode.getPath()); - if (mediaFile != null) { - episode.setMediaFileId(mediaFile.getId()); - } - } - } - } - - private PodcastEpisode getEpisode(int channelId, String url) { - if (url == null) { - return null; - } - - for (PodcastEpisode episode : getEpisodes(channelId, true)) { - if (url.equals(episode.getUrl())) { - return episode; - } - } - return null; - } - - public void refreshAllChannels(boolean downloadEpisodes) { - refreshChannels(getAllChannels(), downloadEpisodes); - } - - private void refreshChannels(final List channels, final boolean downloadEpisodes) { - for (final PodcastChannel channel : channels) { - Runnable task = new Runnable() { - public void run() { - doRefreshChannel(channel, downloadEpisodes); - } - }; - refreshExecutor.submit(task); - } - } - - @SuppressWarnings({"unchecked"}) - private void doRefreshChannel(PodcastChannel channel, boolean downloadEpisodes) { - InputStream in = null; - HttpClient client = new DefaultHttpClient(); - - try { - channel.setStatus(PodcastStatus.DOWNLOADING); - channel.setErrorMessage(null); - podcastDao.updateChannel(channel); - - HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes - HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes - HttpGet method = new HttpGet(channel.getUrl()); - - HttpResponse response = client.execute(method); - in = response.getEntity().getContent(); - - Document document = new SAXBuilder().build(in); - Element channelElement = document.getRootElement().getChild("channel"); - - channel.setTitle(channelElement.getChildTextTrim("title")); - channel.setDescription(channelElement.getChildTextTrim("description")); - channel.setStatus(PodcastStatus.COMPLETED); - channel.setErrorMessage(null); - podcastDao.updateChannel(channel); - - refreshEpisodes(channel, channelElement.getChildren("item")); - - } catch (Exception x) { - LOG.warn("Failed to get/parse RSS file for Podcast channel " + channel.getUrl(), x); - channel.setStatus(PodcastStatus.ERROR); - channel.setErrorMessage(x.toString()); - podcastDao.updateChannel(channel); - } finally { - IOUtils.closeQuietly(in); - client.getConnectionManager().shutdown(); - } - - if (downloadEpisodes) { - for (final PodcastEpisode episode : getEpisodes(channel.getId(), false)) { - if (episode.getStatus() == PodcastStatus.NEW && episode.getUrl() != null) { - downloadEpisode(episode); - } - } - } - } - - public void downloadEpisode(final PodcastEpisode episode) { - Runnable task = new Runnable() { - public void run() { - doDownloadEpisode(episode); - } - }; - downloadExecutor.submit(task); - } - - private void refreshEpisodes(PodcastChannel channel, List episodeElements) { - - List episodes = new ArrayList(); - - for (Element episodeElement : episodeElements) { - - String title = episodeElement.getChildTextTrim("title"); - String duration = getITunesElement(episodeElement, "duration"); - String description = episodeElement.getChildTextTrim("description"); - if (StringUtils.isBlank(description)) { - description = getITunesElement(episodeElement, "summary"); - } - - Element enclosure = episodeElement.getChild("enclosure"); - if (enclosure == null) { - LOG.debug("No enclosure found for episode " + title); - continue; - } - - String url = enclosure.getAttributeValue("url"); - url = sanitizeUrl(url); - if (url == null) { - LOG.debug("No enclosure URL found for episode " + title); - continue; - } - - if (getEpisode(channel.getId(), url) == null) { - Long length = null; - try { - length = new Long(enclosure.getAttributeValue("length")); - } catch (Exception x) { - LOG.warn("Failed to parse enclosure length.", x); - } - - Date date = parseDate(episodeElement.getChildTextTrim("pubDate")); - PodcastEpisode episode = new PodcastEpisode(null, channel.getId(), url, null, title, description, date, - duration, length, 0L, PodcastStatus.NEW, null); - episodes.add(episode); - LOG.info("Created Podcast episode " + title); - } - } - - // Sort episode in reverse chronological order (newest first) - Collections.sort(episodes, new Comparator() { - public int compare(PodcastEpisode a, PodcastEpisode b) { - long timeA = a.getPublishDate() == null ? 0L : a.getPublishDate().getTime(); - long timeB = b.getPublishDate() == null ? 0L : b.getPublishDate().getTime(); - - if (timeA < timeB) { - return 1; - } - if (timeA > timeB) { - return -1; - } - return 0; - } - }); - - // Create episodes in database, skipping the proper number of episodes. - int downloadCount = settingsService.getPodcastEpisodeDownloadCount(); - if (downloadCount == -1) { - downloadCount = Integer.MAX_VALUE; - } - - for (int i = 0; i < episodes.size(); i++) { - PodcastEpisode episode = episodes.get(i); - if (i >= downloadCount) { - episode.setStatus(PodcastStatus.SKIPPED); - } - podcastDao.createEpisode(episode); - } - } - - private Date parseDate(String s) { - for (DateFormat dateFormat : RSS_DATE_FORMATS) { - try { - return dateFormat.parse(s); - } catch (Exception x) { - // Ignored. - } - } - LOG.warn("Failed to parse publish date: '" + s + "'."); - return null; - } - - private String getITunesElement(Element element, String childName) { - for (Namespace ns : ITUNES_NAMESPACES) { - String value = element.getChildTextTrim(childName, ns); - if (value != null) { - return value; - } - } - return null; - } - - private void doDownloadEpisode(PodcastEpisode episode) { - InputStream in = null; - OutputStream out = null; - - if (getEpisode(episode.getId(), false) == null) { - LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download."); - return; - } - - LOG.info("Starting to download Podcast from " + episode.getUrl()); - - HttpClient client = new DefaultHttpClient(); - try { - PodcastChannel channel = getChannel(episode.getChannelId()); - - HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes - HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes - HttpGet method = new HttpGet(episode.getUrl()); - - HttpResponse response = client.execute(method); - in = response.getEntity().getContent(); - - File file = getFile(channel, episode); - out = new FileOutputStream(file); - - episode.setStatus(PodcastStatus.DOWNLOADING); - episode.setBytesDownloaded(0L); - episode.setErrorMessage(null); - episode.setPath(file.getPath()); - podcastDao.updateEpisode(episode); - - byte[] buffer = new byte[4096]; - long bytesDownloaded = 0; - int n; - long nextLogCount = 30000L; - - while ((n = in.read(buffer)) != -1) { - out.write(buffer, 0, n); - bytesDownloaded += n; - - if (bytesDownloaded > nextLogCount) { - episode.setBytesDownloaded(bytesDownloaded); - nextLogCount += 30000L; - if (getEpisode(episode.getId(), false) == null) { - break; - } - podcastDao.updateEpisode(episode); - } - } - - if (getEpisode(episode.getId(), false) == null) { - LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download."); - IOUtils.closeQuietly(out); - file.delete(); - } else { - episode.setBytesDownloaded(bytesDownloaded); - podcastDao.updateEpisode(episode); - LOG.info("Downloaded " + bytesDownloaded + " bytes from Podcast " + episode.getUrl()); - IOUtils.closeQuietly(out); - episode.setStatus(PodcastStatus.COMPLETED); - podcastDao.updateEpisode(episode); - deleteObsoleteEpisodes(channel); - } - - } catch (Exception x) { - LOG.warn("Failed to download Podcast from " + episode.getUrl(), x); - episode.setStatus(PodcastStatus.ERROR); - episode.setErrorMessage(x.toString()); - podcastDao.updateEpisode(episode); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(out); - client.getConnectionManager().shutdown(); - } - } - - private synchronized void deleteObsoleteEpisodes(PodcastChannel channel) { - int episodeCount = settingsService.getPodcastEpisodeRetentionCount(); - if (episodeCount == -1) { - return; - } - - List episodes = getEpisodes(channel.getId(), false); - - // Don't do anything if other episodes of the same channel is currently downloading. - for (PodcastEpisode episode : episodes) { - if (episode.getStatus() == PodcastStatus.DOWNLOADING) { - return; - } - } - - // Reverse array to get chronological order (oldest episodes first). - Collections.reverse(episodes); - - int episodesToDelete = Math.max(0, episodes.size() - episodeCount); - for (int i = 0; i < episodesToDelete; i++) { - deleteEpisode(episodes.get(i).getId(), true); - LOG.info("Deleted old Podcast episode " + episodes.get(i).getUrl()); - } - } - - private synchronized File getFile(PodcastChannel channel, PodcastEpisode episode) { - - File podcastDir = new File(settingsService.getPodcastFolder()); - File channelDir = new File(podcastDir, StringUtil.fileSystemSafe(channel.getTitle())); - - if (!channelDir.exists()) { - boolean ok = channelDir.mkdirs(); - if (!ok) { - throw new RuntimeException("Failed to create directory " + channelDir); - } - - MediaFile mediaFile = mediaFileService.getMediaFile(channelDir); - mediaFile.setComment(channel.getDescription()); - mediaFileService.updateMediaFile(mediaFile); - } - - String filename = StringUtil.getUrlFile(episode.getUrl()); - if (filename == null) { - filename = episode.getTitle(); - } - filename = StringUtil.fileSystemSafe(filename); - String extension = FilenameUtils.getExtension(filename); - filename = FilenameUtils.removeExtension(filename); - if (StringUtils.isBlank(extension)) { - extension = "mp3"; - } - - File file = new File(channelDir, filename + "." + extension); - for (int i = 0; file.exists(); i++) { - file = new File(channelDir, filename + i + "." + extension); - } - - if (!securityService.isWriteAllowed(file)) { - throw new SecurityException("Access denied to file " + file); - } - return file; - } - - /** - * Deletes the Podcast channel with the given ID. - * - * @param channelId The Podcast channel ID. - */ - public void deleteChannel(int channelId) { - // Delete all associated episodes (in case they have files that need to be deleted). - List episodes = getEpisodes(channelId, false); - for (PodcastEpisode episode : episodes) { - deleteEpisode(episode.getId(), false); - } - podcastDao.deleteChannel(channelId); - } - - /** - * Deletes the Podcast episode with the given ID. - * - * @param episodeId The Podcast episode ID. - * @param logicalDelete Whether to perform a logical delete by setting the - * episode status to {@link PodcastStatus#DELETED}. - */ - public void deleteEpisode(int episodeId, boolean logicalDelete) { - PodcastEpisode episode = podcastDao.getEpisode(episodeId); - if (episode == null) { - return; - } - - // Delete file. - if (episode.getPath() != null) { - File file = new File(episode.getPath()); - if (file.exists()) { - file.delete(); - // TODO: Delete directory if empty? - } - } - - if (logicalDelete) { - episode.setStatus(PodcastStatus.DELETED); - episode.setErrorMessage(null); - podcastDao.updateEpisode(episode); - } else { - podcastDao.deleteEpisode(episodeId); - } - } - - public void setPodcastDao(PodcastDao podcastDao) { - this.podcastDao = podcastDao; - } - - public void setSettingsService(SettingsService settingsService) { - this.settingsService = settingsService; - } - - public void setSecurityService(SecurityService securityService) { - this.securityService = securityService; - } - - public void setMediaFileService(MediaFileService mediaFileService) { - this.mediaFileService = mediaFileService; - } -} -- cgit v1.2.3