org.vanbest.xmltv.Main.java Source code

Java tutorial

Introduction

Here is the source code for org.vanbest.xmltv.Main.java

Source

package org.vanbest.xmltv;

/*
 Copyright (c) 2012,2013,2014 Jan-Pascal van Best <janpascal@vanbest.org>
    
 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 2 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.
    
 The full license text can be found in the LICENSE file.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class Main {
    private static final int ACTION_DEFAULT = 0;
    private static final int ACTION_LICENSE = 1;
    private static final int ACTION_DESCRIPTION = 2;
    private static final int ACTION_HELP = 3;
    private static final int ACTION_CAPABILITIES = 4;
    private static final int ACTION_CONFIGURE = 5;
    private File configFile;
    private Config config;
    private PrintStream outputWriter;
    private int days = 5;
    private int offset = 0;
    private boolean clearCache = false;

    static Logger logger = Logger.getLogger(Main.class);

    /**
     * @param args
     */

    public Main() {
        this.configFile = defaultConfigFile();
        this.outputWriter = System.out;
        // PropertyConfigurator.configure(args[0]);
    }

    public void showHeader() {
        logger.info("tv_grab_nl_java version " + config.project_version + " (built " + config.build_time + ")");
        logger.info("Copyright (C) 2012-2014 Jan-Pascal van Best <janpascal@vanbest.org>");
        logger.info(
                "tv_grab_nl_java comes with ABSOLUTELY NO WARRANTY. It is free software, and you are welcome to redistribute it");
        logger.info("under certain conditions; `tv_grab_nl_java --license' for details.");
    }

    public void fetchData() throws FactoryConfigurationError, Exception {
        if (!config.quiet) {
            showHeader();
            logger.info("Fetching programme data for " + this.days + " days starting from day " + this.offset);
            int enabledCount = 0;
            for (Channel c : config.channels) {
                if (c.enabled)
                    enabledCount++;
            }
            logger.info("... from " + enabledCount + " channels");
            logger.info("... using cache at " + config.cacheDbHandle);
        }
        if (clearCache) {
            ProgrammeCache cache = new ProgrammeCache(config);
            cache.clear();
            cache.close();
        }
        Map<String, EPGSource> guides = new HashMap<String, EPGSource>();
        EPGSourceFactory factory = EPGSourceFactory.newInstance();
        // EPGSource gids = new TvGids(config);
        // if (clearCache) gids.clearCache();

        XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outputWriter);
        writer.writeStartDocument();
        writer.writeCharacters("\n");
        writer.writeDTD("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">");
        writer.writeCharacters("\n");
        writer.writeStartElement("tv");
        writer.writeAttribute("generator-info-url", "http://github.com/janpascal/tv_grab_nl_java");
        writer.writeAttribute("source-info-url", "http://tvgids.nl/");
        writer.writeAttribute("source-info-name", "TvGids.nl");
        writer.writeAttribute("generator-info-name",
                "tv_grab_nl_java release " + config.project_version + ", built " + config.build_time);
        writer.writeCharacters(System.getProperty("line.separator"));

        for (Channel c : config.channels)
            if (c.enabled)
                c.serialize(writer, config.fetchLogos);

        for (int day = offset; day < offset + days; day++) {
            if (!config.quiet)
                System.out.print("Fetching information for day " + day);
            for (Channel c : config.channels) {
                if (!c.enabled)
                    continue;
                if (!config.quiet)
                    System.out.print(".");
                if (!guides.containsKey(c.source)) {
                    guides.put(c.source, factory.createEPGSource(c.source, config));
                }
                List<Programme> programmes = guides.get(c.source).getProgrammes(c, day);
                for (Programme p : programmes)
                    p.serialize(writer);
                writer.flush();
            }
            if (!config.quiet)
                System.out.println();
        }

        writer.writeEndElement();
        writer.writeEndDocument();
        writer.flush();
        writer.close();

        for (String source : guides.keySet()) {
            guides.get(source).close();
        }

        if (!config.quiet) {
            EPGSource.Stats stats = new EPGSource.Stats();
            for (String source : guides.keySet()) {
                EPGSource.Stats part = guides.get(source).getStats();
                stats.cacheHits += part.cacheHits;
                stats.cacheMisses += part.cacheMisses;
                stats.fetchErrors += part.fetchErrors;
            }
            logger.info("Number of programmes from cache: " + stats.cacheHits);
            logger.info("Number of programmes fetched: " + stats.cacheMisses);
            logger.warn("Number of fetch errors: " + stats.fetchErrors);
        }
    }

    public void configure() throws IOException {
        showHeader();

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        System.out.print("Delay between each request to the server (in milliseconds, default="
                + config.niceMilliseconds + "):");
        if (config.configYes) {
            System.out.println();
        } else {
            while (true) {
                String s = reader.readLine().toLowerCase();
                if (s.isEmpty()) {
                    break;
                }
                try {
                    config.niceMilliseconds = Integer.parseInt(s);
                    break;
                } catch (NumberFormatException e) {
                    System.out.println("\"" + s + "\" is not a valid number, please try again");
                    continue;
                }
            }
        }

        // TODO configure cache location
        // public String cacheDbHandle;
        // public String cacheDbUser;
        // public String cacheDbPassword;

        Set<String> oldChannels = new HashSet<String>();
        Set<String> oldChannelNames = new HashSet<String>();
        Set<String> oldGuides = new HashSet<String>();
        for (Channel c : config.channels) {
            if (c.enabled) {
                oldChannels.add(c.source + "::" + c.id);
                oldChannelNames.add(c.source + "::" + c.defaultName());
                oldGuides.add(c.source);
            }
        }

        EPGSourceFactory factory = EPGSourceFactory.newInstance();
        List<? extends EPGSource> allSources = factory.getAll(config);

        System.out.println("Please select the TV programme information sources to use");
        List<EPGSource> guides = new ArrayList<EPGSource>();
        for (EPGSource source : allSources) {
            boolean selected = oldGuides.contains(source.getName());
            System.out.print("    Use \"" + source.getName() + "\" (Y/N, default=" + (selected ? "Y" : "N") + "):");
            if (config.configYes) {
                guides.add(source);
                System.out.println("Y");
                continue;
            }

            while (true) {
                String s = reader.readLine().toLowerCase();
                if (s.isEmpty()) {
                    if (selected)
                        guides.add(source);
                    break;
                } else if (s.startsWith("y")) {
                    guides.add(source);
                    break;
                } else if (s.startsWith("n")) {
                    break;
                }
            }
        }

        System.out.println("Please wait, fetching channel information...");
        List<Channel> channels = new ArrayList<Channel>();
        for (EPGSource guide : guides) {
            System.out.print(guide.getName() + "... ");
            channels.addAll(guide.getChannels());
            System.out.println("OK");
        }
        System.out.println();

        boolean all = false;
        boolean none = false;
        boolean keep = false;
        for (Channel c : channels) {
            boolean selected = oldChannels.contains(c.source + "::" + c.id)
                    || oldChannelNames.contains(c.source + "::" + c.defaultName());
            System.out.print("add channel " + c.getXmltvChannelId() + " (" + c.defaultName()
                    + ") [[y]es,[n]o,[a]ll,[none],[k]eep selection (default=" + (selected ? "yes" : "no") + ")] ");
            if (keep) {
                c.enabled = selected;
                System.out.println(selected ? "Y" : "N");
                continue;
            }
            if (all || config.configYes) {
                c.enabled = true;
                System.out.println("Y");
                continue;
            }
            if (none) {
                c.enabled = false;
                System.out.println("N");
                continue;
            }
            while (true) {
                String s = reader.readLine().toLowerCase();
                if (s.isEmpty()) {
                    c.enabled = selected;
                    break;
                } else if (s.startsWith("k")) {
                    c.enabled = selected;
                    keep = true;
                    break;
                } else if (s.startsWith("y")) {
                    c.enabled = true;
                    break;
                } else if (s.startsWith("a")) {
                    c.enabled = true;
                    all = true;
                    break;
                } else if (s.startsWith("none")) {
                    c.enabled = false;
                    none = true;
                    break;
                } else if (s.startsWith("n")) {
                    c.enabled = false;
                    break;
                }
            }
        }

        config.setChannels(channels);
        try {
            config.writeConfig(configFile);
            logger.info("Configuration file written to " + configFile.getPath());
        } catch (FileNotFoundException e) {
            logger.warn("File not found trying to write config file to " + configFile.getPath(), e);
        } catch (IOException e) {
            logger.warn("IO Exception trying to write config file to " + configFile.getPath(), e);
        }
    }

    static String copyright = "Copyright (c) 2012-2014 Jan-Pascal van Best <janpascal@vanbest.org>"
            + System.getProperty("line.separator") + "" + System.getProperty("line.separator")
            + "This program is free software; you can redistribute it and/or modify"
            + System.getProperty("line.separator")
            + "it under the terms of the GNU General Public License as published by"
            + System.getProperty("line.separator")
            + "the Free Software Foundation; either version 2 of the License, or"
            + System.getProperty("line.separator") + "(at your option) any later version."
            + System.getProperty("line.separator") + "" + System.getProperty("line.separator")
            + "This program is distributed in the hope that it will be useful,"
            + System.getProperty("line.separator")
            + "but WITHOUT ANY WARRANTY; without even the implied warranty of"
            + System.getProperty("line.separator") + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"
            + System.getProperty("line.separator") + "GNU General Public License for more details."
            + System.getProperty("line.separator") + "" + System.getProperty("line.separator")
            + "You should have received a copy of the GNU General Public License along"
            + System.getProperty("line.separator")
            + "with this program; if not, write to the Free Software Foundation, Inc.,"
            + System.getProperty("line.separator") + "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.";

    public void showLicense() {
        System.out.println(copyright);
    }

    public Options createOptions() {
        Options options = new Options();
        options.addOption(OptionBuilder.withLongOpt("description")
                .withDescription("Display a description to identify this grabber").create())
                .addOption(OptionBuilder.withLongOpt("capabilities").withDescription("Show grabber capabilities")
                        .create())
                .addOption(OptionBuilder.withLongOpt("quiet")
                        .withDescription("Disable progress information. Also set log-level to ERROR").create())
                .addOption(OptionBuilder.withLongOpt("output").hasArg().withDescription("Set xlmtv output filename")
                        .create())
                .addOption(OptionBuilder.withLongOpt("days").hasArg().withDescription("Number of days to grab")
                        .create())
                .addOption(OptionBuilder.withLongOpt("offset").hasArg()
                        .withDescription("Start day for grabbing (0=today)").create())
                .addOption(OptionBuilder.withLongOpt("configure").withDescription("Interactive configuration")
                        .create())
                .addOption(OptionBuilder.withLongOpt("config-yes")
                        .withDescription("Answer 'yes' to all configuration questions").create())
                .addOption(OptionBuilder.withLongOpt("config-file").hasArg()
                        .withDescription("Configuration file location").create())
                .addOption(
                        OptionBuilder.withLongOpt("cache").hasArg().withDescription("Cache file location").create())
                .addOption(OptionBuilder.withLongOpt("clear-cache")
                        .withDescription("Clear cache, remove all cached info").create())
                .addOption(OptionBuilder.withLongOpt("help").withDescription("Show this help").create())
                .addOption(OptionBuilder.withLongOpt("log-level").hasArg()
                        .withDescription("Set log level (ERROR,WARN,INFO,DEBUG,TRACE)").create())
                .addOption(
                        OptionBuilder.withLongOpt("license").withDescription("Show license information").create());
        // .addOption(OptionBuilder.withLongOpt("preferredmethod").withDescription("Show preferred method").create();
        return options;
    }

    public int processOptions(Options options, String[] args) throws FileNotFoundException {
        int action = ACTION_DEFAULT;

        CommandLine line = null;
        try {
            line = new GnuParser().parse(options, args);
        } catch (ParseException e) {
            logger.error("Error parsing command-line options", e);
        }

        if (line.hasOption("license")) {
            action = ACTION_LICENSE;
            return action; // do not read config file
        }
        if (line.hasOption("description")) {
            action = ACTION_DESCRIPTION;
            return action; // do not read config file
        }
        if (line.hasOption("capabilities")) {
            action = ACTION_CAPABILITIES;
            return action; // do not try to read config file
        }
        if (line.hasOption("config-file")) {
            configFile = new File(line.getOptionValue("config-file"));
        }
        config = Config.readConfig(configFile);
        if (line.hasOption("quiet")) {
            config.quiet = true;
            Logger.getRootLogger().setLevel(Level.ERROR);
        }
        if (line.hasOption("help")) {
            action = ACTION_HELP;
        }
        if (line.hasOption("output")) {
            this.outputWriter = new PrintStream(new FileOutputStream(line.getOptionValue("output")));
        }
        if (line.hasOption("log-level")) {
            String arg = line.getOptionValue("log-level");
            // TODO: make distinction between levels for console and
            // file appenders
            Logger.getRootLogger().setLevel(Level.toLevel(arg, Level.INFO));
        }
        if (line.hasOption("cache")) {
            config.setCacheFile(line.getOptionValue("cache"));
        }
        if (line.hasOption("clear-cache")) {
            clearCache = true;
        }
        if (line.hasOption("days")) {
            this.days = Integer.parseInt(line.getOptionValue("days"));
        }
        if (line.hasOption("offset")) {
            this.offset = Integer.parseInt(line.getOptionValue("offset"));
        }
        if (line.hasOption("configure")) {
            action = ACTION_CONFIGURE;
        }
        if (line.hasOption("config-yes")) {
            config.configYes = true;
        }
        return action;
    }

    public static File defaultConfigFile() {
        return FileUtils.getFile(FileUtils.getUserDirectory(), ".xmltv", "tv_grab_nl_java.conf");
    }

    public void run(String[] args) throws FactoryConfigurationError, Exception {
        Options options = createOptions();
        int action = processOptions(options, args);
        switch (action) {
        case ACTION_DEFAULT:
            fetchData();
            break;
        case ACTION_LICENSE:
            showLicense();
            break;
        case ACTION_DESCRIPTION:
            System.out.println("Dutch TV listings using tvgids.nl, rtl.nl and horizon.tv");
            break;
        case ACTION_HELP:
            // automatically generate the help statement
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("tv_grab_nl_java", options);
            break;
        case ACTION_CAPABILITIES:
            System.out.println("baseline");
            System.out.println("manualconfig");
            System.out.println("cache");
            // System.out.println("preferredmethod");
            break;
        case ACTION_CONFIGURE:
            try {
                configure();
            } catch (IOException e) {
                logger.warn("Exception during configure");
                logger.debug("Stack trace: ", e);
            }
            break;
        default:
            logger.error("Unknown main action value " + action);
        }
    }

    public static void main(String[] args) {
        Main main = new Main();
        try {
            main.run(args);
        } catch (Exception e) {
            logger.error("Error in tv_grab_nl_java application", e);
        }
    }

}