(no commit message)
[utils] / crawler / kiss / src / org / wamblee / crawler / kiss / KissCrawler.java
index dd9ba78d1d9a16e07ce956e256ce6922eb3e0a20..fc076a5abaf7294e4fc96d747ee39674955d999e 100644 (file)
@@ -18,47 +18,98 @@ package org.wamblee.crawler.kiss;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.dom4j.Element;
 import org.wamblee.conditions.Condition;
-import org.wamblee.conditions.OrCondition;
 import org.wamblee.crawler.Action;
 import org.wamblee.crawler.Configuration;
 import org.wamblee.crawler.Crawler;
 import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
 import org.wamblee.crawler.impl.ConfigurationParser;
 import org.wamblee.crawler.impl.CrawlerImpl;
 
 /**
+ * The KiSS crawler for automatic recording of interesting TV shows. 
  * 
  */
 public class KissCrawler {
-    
+
     private static final Log LOG = LogFactory.getLog(KissCrawler.class);
 
+    /**
+     * Log file name for the crawler. 
+     */
     private static final String LOG_FILE = "kiss.log";
 
+    /**
+     * Start URL of the electronic programme guide. 
+     */
     private static final String START_URL = "http://epg.kml.kiss-technology.com/login_core.php";
 
+    /**
+     * Crawler configuration file. 
+     */
     private static final String CRAWLER_CONFIG = "config.xml";
-    
+
+    /**
+     * Configuration file describing interesting programs. 
+     */
     private static final String PROGRAM_CONFIG = "programs.xml";
 
+    /**
+     * Regular expression for matching time interval strings in the 
+     * retrieved pages. 
+     */
     private static final String TIME_REGEX = "([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
 
+    /**
+     * Compiled pattern for the time regular expression. 
+     */
     private Pattern _pattern;
 
-    public KissCrawler(String aStartUrl, String aCrawlerConfig, String aProgramConfig) throws Exception {
+    /**
+     * Runs the KiSS crawler.  
+     * @param aArgs Arguments, currently all ignored because they are hardcoded.
+     * @throws Exception In case of problems. 
+     */
+    public static void main(String[] aArgs) throws Exception {
+        new KissCrawler(START_URL, CRAWLER_CONFIG, PROGRAM_CONFIG);
+    }
+    
+    /**
+     * Constructs the crawler. This retrieves the TV guide by crawling the 
+     * KiSS EPG guide, filters the guide for interesting programs, tries to 
+     * record them, and sends a summary mail to the user. 
+     * @param aStartUrl Start URL of the electronic programme guide. 
+     * @param aCrawlerConfig Configuration file for the  crawler. 
+     * @param aProgramConfig Configuration file describing interesting shows. 
+     * @throws IOException In case of problems reading files. 
+     * @throws MessagingException In case of problems sending a mail notification. 
+     */
+    public KissCrawler(String aStartUrl, String aCrawlerConfig,
+            String aProgramConfig) throws IOException, MessagingException {
 
         _pattern = Pattern.compile(TIME_REGEX);
 
@@ -66,33 +117,21 @@ public class KissCrawler {
         PrintStream os = new PrintStream(fos);
 
         try {
-            ConfigurationParser parser = new ConfigurationParser(os);
-            InputStream crawlerConfigFile = new FileInputStream(new File(aCrawlerConfig));
-            Configuration config = parser.parse(crawlerConfigFile);
-            
-            InputStream programConfigFile = new FileInputStream(new File(aProgramConfig)); 
-            Condition<Program> programCondition = new ProgramConfigurationParser().parse(programConfigFile); 
-           
-
             HttpClient client = new HttpClient();
-            // client.getHostConfiguration().setProxy("localhost", 3128);
+            client.getHostConfiguration().setProxy("127.0.0.1", 3128);
 
-            Crawler crawler = new CrawlerImpl(client, config);
+            Crawler crawler = createCrawler(aCrawlerConfig, os, client);
 
-            Page page = crawler.getPage(aStartUrl);
-            showPage(page);
-            page = page.getAction("channels-favorites").execute();
+            Page page = getStartPage(aStartUrl, crawler);
             TVGuide guide = createGuide(page);
             PrintVisitor printer = new PrintVisitor(System.out);
             guide.accept(printer);
-            
-            MatchVisitor matcher = new MatchVisitor(programCondition);
-            guide.accept(matcher);
-            List<Program> programs = matcher.getMatches(); 
-            for (Program program: programs) { 
-                System.out.println("Found: " + program + " record: " + program.record() );
-            }
-            
+
+            InputStream programConfigFile = new FileInputStream(new File(
+                    aProgramConfig));
+            Condition<Program> programCondition = new ProgramConfigurationParser()
+                    .parse(programConfigFile);
+            recordInterestingShows(programCondition, guide);
         } finally {
             os.flush();
             os.close();
@@ -100,31 +139,115 @@ public class KissCrawler {
         }
     }
 
-    public static void main(String[] args) throws Exception {
-        new KissCrawler(START_URL, CRAWLER_CONFIG, PROGRAM_CONFIG);
+    /**
+     * Records interesting shows. 
+     * @param aProgramCondition Condition determining which shows are interesting. 
+     * @param aGuide Television guide. 
+     * @throws MessagingException In case of problems sending a summary mail. 
+     */
+    private void recordInterestingShows(Condition<Program> aProgramCondition,
+            TVGuide aGuide) throws MessagingException {
+        MatchVisitor matcher = new MatchVisitor(aProgramCondition);
+        aGuide.accept(matcher);
+        List<Program> programs = matcher.getMatches();
+        String recorded = "";
+        String notRecorded = "";
+        String failures = "";
+        for (Program program : programs) {
+            try {
+                boolean result = program.record();
+                if (result) {
+                    recorded += "\n" + program;
+                } else {
+                    notRecorded += "\n" + program;
+                }
+            } catch (PageException e) {
+                LOG.info("Attempt to record " + program + " failed.");
+                failures += "\n" + program.toString() + ": " + e.getMessage();
+            }
+        }
+        String msg = "Summary of KiSS crawler: \n\n\n";
+
+        if (recorded.length() > 0) {
+            msg += "Recorded programs:\n\n" + recorded + "\n\n";
+        }
+        if (notRecorded.length() > 0) {
+            msg += "Not recorded programs:\n\n" + notRecorded + "\n\n";
+        }
+        if (recorded.length() == 0 && notRecorded.length() == 0) {
+            msg += "No suitable programs found";
+        }
+        if (failures.length() > 0) {
+            msg += "Failures:\n\n" + failures;
+        }
+        System.out.println(msg);
+        sendMail(msg);
+    }
+
+    /**
+     * Creates the crawler. 
+     * @param aCrawlerConfig Crawler configuration file. 
+     * @param aOs Logging output stream for the crawler. 
+     * @param aClient HTTP Client to use. 
+     * @return Crawler. 
+     * @throws FileNotFoundException In case configuration files cannot be found. 
+     */
+    private Crawler createCrawler(String aCrawlerConfig, PrintStream aOs,
+            HttpClient aClient) throws FileNotFoundException {
+        ConfigurationParser parser = new ConfigurationParser(aOs);
+        InputStream crawlerConfigFile = new FileInputStream(new File(
+                aCrawlerConfig));
+        Configuration config = parser.parse(crawlerConfigFile);
+        Crawler crawler = new CrawlerImpl(aClient, config);
+        return crawler;
     }
 
-    private void showPage(Page aPage) {
-        Action[] links = aPage.getActions();
-        for (Action link : links) {
-            System.out.println("Link found '" + link.getName() + "'");
+    /**
+     * Gets the start page of the electronic programme guide. This involves login and
+     * navigation to a suitable start page after logging in. 
+     * @param aStartUrl URL of the electronic programme guide. 
+     * @param aCrawler Crawler to use. 
+     * @return Starting page. 
+     */
+    private Page getStartPage(String aStartUrl, Crawler aCrawler) {
+        try {
+            Page page = aCrawler.getPage(aStartUrl);
+            return page.getAction("channels-favorites").execute();
+        } catch (PageException e) {
+            throw new RuntimeException(
+                    "Could not login to electronic program guide", e);
         }
-        Element element = aPage.getContent();
-        System.out.println("Retrieved content: " + element.asXML());
     }
 
-    private TVGuide createGuide(Page page) {
+    /**
+     * Creates the TV guide by web crawling. 
+     * @param aPage Starting page. 
+     * @return TV guide. 
+     */
+    private TVGuide createGuide(Page aPage) {
         LOG.info("Obtaining full TV guide");
-        Action[] actions = page.getActions();
+        Action[] actions = aPage.getActions();
         List<Channel> channels = new ArrayList<Channel>();
         for (Action action : actions) {
-            Channel channel = createChannel(action.getName(), action.execute()
-                    .getAction("right-now").execute());
-            channels.add(channel);
+            try {
+                LOG.info("Getting channel info for '" + action.getName() + "'");
+                Channel channel = createChannel(action.getName(), action
+                        .execute().getAction("right-now").execute());
+                channels.add(channel);
+            } catch (PageException e) {
+                LOG.error("Could not create channel information for '"
+                        + action.getName() + "'", e);
+            }
         }
         return new TVGuide(channels);
     }
 
+    /**
+     * Create channel information for a specific channel. 
+     * @param aChannel Channel name. 
+     * @param aPage Starting page for the channel. 
+     * @return Channel. 
+     */
     private Channel createChannel(String aChannel, Page aPage) {
         LOG.info("Obtaining program for " + aChannel);
         Action[] programActions = aPage.getActions();
@@ -133,22 +256,48 @@ public class KissCrawler {
             String time = action.getContent().element("time").getText().trim();
             Matcher matcher = _pattern.matcher(time);
             if (matcher.matches()) {
-                Time begin = new Time(Integer.parseInt(matcher.group(1)), 
-                                      Integer.parseInt(matcher.group(2)));
-                Time end = new Time(Integer.parseInt(matcher.group(3)), 
-                        Integer.parseInt(matcher.group(4)));
+                Time begin = new Time(Integer.parseInt(matcher.group(1)),
+                        Integer.parseInt(matcher.group(2)));
+                Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
+                        .parseInt(matcher.group(4)));
                 TimeInterval interval = new TimeInterval(begin, end);
-                //Page programInfo = action.execute();
-                //String description = programInfo.getContent().element("description").getText().trim();
-                //String keywords = programInfo.getContent().element("keywords").getText().trim();
+                // Page programInfo = action.execute();
+                // String description =
+                // programInfo.getContent().element("description").getText().trim();
+                // String keywords =
+                // programInfo.getContent().element("keywords").getText().trim();
                 String description = "";
                 String keywords = "";
-                Program program = new Program(aChannel, action.getName(), description, keywords, interval, action);
-                
+                Program program = new Program(aChannel, action.getName(),
+                        description, keywords, interval, action);
+
                 LOG.debug("Got program " + program);
                 programs.add(program);
             }
         }
         return new Channel(aChannel, programs);
     }
+
+    /**
+     * Sends a summary mail to the user. 
+     * @param aText Text of the mail. 
+     * @throws MessagingException In case of problems sending mail. 
+     */
+    private void sendMail(String aText) throws MessagingException {
+        Properties props = new Properties();
+        props.put("mail.transport.protocol", "smtp");
+        props.put("mail.smtp.host", "falcon");
+        props.put("mail.smtp.port", "25");
+
+        Session mailSession = Session.getInstance(props);
+        Message message = new MimeMessage(mailSession);
+
+        message.setFrom(new InternetAddress("erik@brakkee.org"));
+        message.setRecipient(Message.RecipientType.TO, new InternetAddress(
+                "erik@brakkee.org"));
+        message.setSentDate(new Date());
+        message.setSubject("KiSS crawler update");
+        message.setText(aText);
+        Transport.send(message);
+    }
 }