net.urlgrey.mythpodcaster.transcode.IndividualFeedTranscodeTaskImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.urlgrey.mythpodcaster.transcode.IndividualFeedTranscodeTaskImpl.java

Source

/*
 * IndividualFeedTranscodeTaskImpl.java
 * 
 * Created: May 21, 2010
 * 
 * Copyright (C) 2010 Scott Kidder
 * 
 * This file is part of mythpodcaster
 * 
 * This program 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.
 * 
 * This program 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 this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */
package net.urlgrey.mythpodcaster.transcode;

import java.io.File;
import java.io.FileWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.urlgrey.mythpodcaster.dao.MythRecordingsDAO;
import net.urlgrey.mythpodcaster.dao.TranscodingProfilesDAO;
import net.urlgrey.mythpodcaster.domain.Channel;
import net.urlgrey.mythpodcaster.domain.RecordedProgram;
import net.urlgrey.mythpodcaster.domain.RecordedSeries;
import net.urlgrey.mythpodcaster.jobs.StatusBean;
import net.urlgrey.mythpodcaster.xml.FeedSubscriptionItem;
import net.urlgrey.mythpodcaster.xml.ScopeEnum;
import net.urlgrey.mythpodcaster.xml.TranscodingProfile;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Required;

import com.sun.syndication.feed.synd.SyndEnclosure;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedOutput;

/**
 * @author scottkidder
 * 
 */
public class IndividualFeedTranscodeTaskImpl implements Runnable {

    private static final Logger LOGGER = Logger.getLogger(IndividualFeedTranscodeTaskImpl.class);
    private FeedSubscriptionItem subscription;
    private SyndFeed feed;

    private Comparator<SyndEntry> entryComparator = new FeedEntryComparator();
    private TranscodingProfilesDAO transcodingProfilesDao;
    private MythRecordingsDAO recordingsDao;
    private FeedFileAccessor feedFileAccessor;
    private String feedFilePath;
    private String feedFileExtension;
    private StatusBean status;

    public IndividualFeedTranscodeTaskImpl(FeedSubscriptionItem subscription, SyndFeed feed) {
        this.subscription = subscription;
        this.feed = feed;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public void run() {
        if (subscription.isActive()) {
            // identify series recordings not represented in the RSS Feed (transcode)
            final List entries = feed.getEntries();
            final RecordedSeries series;
            if (ScopeEnum.MOST_RECENT.equals(subscription.getScope())) {
                series = recordingsDao.findRecordedSeries(subscription.getSeriesId(),
                        subscription.getNumberOfMostRecentToKeep());
            } else {
                series = recordingsDao.findRecordedSeries(subscription.getSeriesId());
            }

            if (series == null) {
                // if not occurrences of the recorded series are found, then continue
                LOGGER.debug("No recordings found for recordId[" + subscription.getSeriesId() + "]");
            }

            if (series != null) {
                for (RecordedProgram program : series.getRecordedPrograms()) {
                    if (program.getEndTime() == null) {
                        LOGGER.debug("Skipping program, end-time is not set (possibly still recording): programId["
                                + program.getProgramId() + "]");
                        continue;
                    }

                    // We should determine whether the program is finished.
                    // This can be determine by comparing the program end-time with the current time (the
                    // comparison must use the same timezone for both dates)
                    // See http://code.google.com/p/mythpodcaster/issues/detail?id=65
                    final GregorianCalendar programCal = new GregorianCalendar();
                    programCal.setTimeInMillis(program.getEndTime().getTime());
                    final GregorianCalendar localTimeCal = new GregorianCalendar();
                    if (programCal.after(localTimeCal)) {
                        LOGGER.debug(
                                "Skipping recorded program, end-time is in future (still recording): programId["
                                        + program.getProgramId() + "]");
                        continue;
                    }

                    if (ScopeEnum.SPECIFIC_RECORDINGS.equals(this.subscription.getScope())
                            && (false == this.subscription.getRecordedProgramKeys().contains(program.getKey()))) {
                        LOGGER.debug(
                                "Skipping recorded program, not specified as a recording to transcode for this subscription: key["
                                        + program.getKey() + "]");
                        continue;
                    }

                    boolean found = false;
                    LOGGER.debug("Locating program in existing feed entries: programId[" + program.getProgramId()
                            + "], key[" + program.getKey() + "]");
                    if (entries != null && entries.size() > 0) {
                        final Iterator it = entries.iterator();
                        while (it.hasNext()) {
                            final SyndEntry entry = (SyndEntry) it.next();
                            if (entry.getUri() == null) {
                                continue;
                            }

                            String entryKey = entry.getUri();
                            if (program.getKey().equalsIgnoreCase(entryKey)) {
                                found = true;
                                break;
                            }
                        }
                    }

                    if (found) {
                        LOGGER.debug("Program was found in feed, continuing");
                    } else {
                        status.setTranscodingProfileName(transcodingProfilesDao.findAllProfiles()
                                .get(subscription.getTranscodeProfile()).getDisplayName());
                        status.setTranscodingProgramEpisodeName(program.getKey());
                        status.setTranscodingSeriesTitle(series.getTitle());
                        status.setTranscodingProgramName(program.getProgramTitle());
                        status.setCurrentTranscodeStart(new Date());

                        final Channel channel = this.recordingsDao
                                .findChannel(program.getRecordedProgramKey().getChannelId());
                        feedFileAccessor.addProgramToFeed(series, program, channel, feed,
                                subscription.getTranscodeProfile());
                        writeFeedFile(entries);
                        status.setCurrentTranscodeStart(null);
                    }
                }
            }

            // identify RSS Feed entries no longer in the database (delete)
            if (entries != null && entries.size() > 0) {
                LOGGER.debug("Identifying series recordings no longer in database but still in feed, recordId["
                        + subscription.getSeriesId() + "]");
                final Map<String, TranscodingProfile> transcodingProfiles = transcodingProfilesDao
                        .findAllProfiles();
                Set<SyndEntry> entryRemovalSet = new HashSet<SyndEntry>();
                final Iterator it = entries.iterator();
                while (it.hasNext()) {
                    final SyndEntry entry = (SyndEntry) it.next();
                    if (entry.getUri() == null) {
                        entryRemovalSet.add(entry);
                        LOGGER.debug("Feed entry has null URI (GUID), removing because it cannot be identified");
                        continue;
                    }

                    // locate the feed entry in the list of recorded programs
                    String episodeKey = entry.getUri();
                    boolean found = false;
                    if (series != null) {
                        for (RecordedProgram program : series.getRecordedPrograms()) {
                            if (program.getKey().equalsIgnoreCase(episodeKey)) {
                                found = true;
                                break;
                            }
                        }
                    }

                    // for a feed using specific recordings, delete those entries that are no longer required
                    if (ScopeEnum.SPECIFIC_RECORDINGS.equals(subscription.getScope())
                            && false == subscription.getRecordedProgramKeys().contains(episodeKey)) {
                        found = false;
                    }

                    // if the feed entry is no longer in the list of recorded programs, then remove
                    if (found) {
                        LOGGER.debug("Feed entry is current, continuing: uid[" + entry.getUri() + "]");
                    } else {
                        LOGGER.debug("Feed entry will be deleted: uid[" + entry.getUri() + "]");
                        entryRemovalSet.add(entry);

                        final List enclosures = entry.getEnclosures();
                        if (enclosures.size() > 0) {
                            final SyndEnclosure enclosure = (SyndEnclosure) enclosures.get(0);

                            final TranscodingProfile transcodingProfile = transcodingProfiles
                                    .get(subscription.getTranscodeProfile());
                            transcodingProfile.deleteEncoding(this.feedFilePath, enclosure.getUrl(),
                                    entry.getUri());
                        } else {
                            LOGGER.info("No enclosures specified in the entry, removing from feed and continuing");
                        }
                    }
                }

                // remove all of the entries flagged for removal
                entries.removeAll(entryRemovalSet);
                writeFeedFile(entries);
            }
        }
    }

    /**
     * Write the syndication feed entries to the file.
     * 
     * @param entries Entry list
     * 
     */
    private void writeFeedFile(List<SyndEntry> entries) {
        // write the updated RSS feed for the series
        final File encodingDirectory = new File(feedFilePath, subscription.getTranscodeProfile());
        final File feedFile = new File(encodingDirectory, subscription.getSeriesId() + feedFileExtension);
        LOGGER.debug("Changes made to feed, updating feed file: path[" + feedFile.getAbsolutePath() + "]");

        // sort the feed entries by published-date
        Collections.sort(entries, entryComparator);

        File transformedFeedFile = null;
        try {
            FileWriter writer = new FileWriter(feedFile);
            SyndFeedOutput output = new SyndFeedOutput();
            output.output(feed, writer);

            transformedFeedFile = feedFileAccessor.generateTransformationFromFeed(feedFile, feed,
                    subscription.getSeriesId());
        } catch (Exception e) {
            LOGGER.error("Error rendering feed", e);
            if (feedFile.canWrite()) {
                feedFile.delete();
            }

            if (transformedFeedFile != null && transformedFeedFile.canWrite()) {
                transformedFeedFile.delete();
            }
        }
    }

    private class FeedEntryComparator implements Comparator<SyndEntry> {

        /*
         * (non-Javadoc)
         * 
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        @Override
        public int compare(SyndEntry entry1, SyndEntry entry2) {
            if (entry1 == null || entry1.getPublishedDate() == null) {
                return 1;
            }

            if (entry2 == null) {
                return -1;
            }
            return ((-1) * entry1.getPublishedDate().compareTo(entry2.getPublishedDate()));
        }
    }

    @Required
    public void setStatus(StatusBean status) {
        this.status = status;
    }

    @Required
    public void setRecordingsDao(MythRecordingsDAO recordingsDao) {
        this.recordingsDao = recordingsDao;
    }

    @Required
    public void setTranscodingProfilesDao(TranscodingProfilesDAO transcodingProfilesDao) {
        this.transcodingProfilesDao = transcodingProfilesDao;
    }

    @Required
    public void setFeedFileAccessor(FeedFileAccessor feedFileAccessor) {
        this.feedFileAccessor = feedFileAccessor;
    }

    @Required
    public void setFeedFilePath(String feedFilePath) {
        this.feedFilePath = feedFilePath;
    }

    @Required
    public void setFeedFileExtension(String feedFileExtension) {
        this.feedFileExtension = feedFileExtension;
    }

}