(no commit message)
[utils] / crawler / kiss / src / org / wamblee / crawler / kiss / KissCrawler.java
1 /*
2  * Copyright 2005 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.wamblee.crawler.kiss;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.PrintStream;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.Properties;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import javax.mail.Message;
36 import javax.mail.MessagingException;
37 import javax.mail.Session;
38 import javax.mail.Transport;
39 import javax.mail.internet.InternetAddress;
40 import javax.mail.internet.MimeMessage;
41 import javax.xml.transform.TransformerException;
42
43 import org.apache.commons.httpclient.HttpClient;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.apache.commons.mail.EmailException;
47 import org.apache.commons.mail.HtmlEmail;
48 import org.apache.xml.serialize.OutputFormat;
49 import org.apache.xml.serialize.XMLSerializer;
50 import org.w3c.dom.Document;
51 import org.wamblee.crawler.Action;
52 import org.wamblee.crawler.Configuration;
53 import org.wamblee.crawler.Crawler;
54 import org.wamblee.crawler.Page;
55 import org.wamblee.crawler.PageException;
56 import org.wamblee.crawler.impl.ConfigurationParser;
57 import org.wamblee.crawler.impl.CrawlerImpl;
58 import org.wamblee.io.FileResource;
59 import org.wamblee.xml.XSLT;
60
61 /**
62  * The KiSS crawler for automatic recording of interesting TV shows.
63  * 
64  */
65 public class KissCrawler {
66
67     private static final Log LOG = LogFactory.getLog(KissCrawler.class);
68
69     /**
70      * Log file name for the crawler.
71      */
72     private static final String LOG_FILE = "kiss.log";
73
74     /**
75      * Start URL of the electronic programme guide.
76      */
77     private static final String START_URL = "http://epg.kml.kiss-technology.com/login_core.php";
78
79     /**
80      * Crawler configuration file.
81      */
82     private static final String CRAWLER_CONFIG = "config.xml";
83
84     /**
85      * Configuration file describing interesting programs.
86      */
87     private static final String PROGRAM_CONFIG = "programs.xml";
88
89     /**
90      * Regular expression for matching time interval strings in the retrieved
91      * pages.
92      */
93     private static final String TIME_REGEX = "([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
94
95     /**
96      * Compiled pattern for the time regular expression.
97      */
98     private Pattern _pattern;
99
100     /**
101      * Runs the KiSS crawler.
102      * 
103      * @param aArgs
104      *            Arguments, currently all ignored because they are hardcoded.
105      * @throws Exception
106      *             In case of problems.
107      */
108     public static void main(String[] aArgs) throws Exception {
109         new KissCrawler(START_URL, CRAWLER_CONFIG, PROGRAM_CONFIG);
110     }
111
112     /**
113      * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
114      * EPG guide, filters the guide for interesting programs, tries to record
115      * them, and sends a summary mail to the user.
116      * 
117      * @param aStartUrl
118      *            Start URL of the electronic programme guide.
119      * @param aCrawlerConfig
120      *            Configuration file for the crawler.
121      * @param aProgramConfig
122      *            Configuration file describing interesting shows.
123      * @throws IOException
124      *             In case of problems reading files.
125      * @throws MessagingException
126      *             In case of problems sending a mail notification.
127      */
128     public KissCrawler(String aStartUrl, String aCrawlerConfig,
129             String aProgramConfig) throws IOException, MessagingException {
130
131         _pattern = Pattern.compile(TIME_REGEX);
132
133         FileOutputStream fos = new FileOutputStream(new File(LOG_FILE));
134         PrintStream os = new PrintStream(fos);
135
136         try {
137             HttpClient client = new HttpClient();
138             //client.getHostConfiguration().setProxy("127.0.0.1", 3128);
139
140             Crawler crawler = createCrawler(aCrawlerConfig, os, client);
141             InputStream programConfigFile = new FileInputStream(new File(
142                     aProgramConfig));
143             ProgramConfigurationParser parser = new ProgramConfigurationParser();
144             parser.parse(programConfigFile);
145             List<ProgramFilter> programFilters = parser.getFilters(); 
146             
147             Page page = getStartPage(aStartUrl, crawler);
148             TVGuide guide = createGuide(page);
149             PrintVisitor printer = new PrintVisitor(System.out);
150             guide.accept(printer);
151             processResults(programFilters, guide, parser.getNotifier());
152         } finally {
153             os.flush();
154             os.close();
155             System.out.println("Output written on '" + LOG_FILE + "'");
156         }
157     }
158
159     /**
160      * Records interesting shows.
161      * 
162      * @param aProgramCondition
163      *            Condition determining which shows are interesting.
164      * @param aGuide
165      *            Television guide.
166      * @throws MessagingException
167      *             In case of problems sending a summary mail.
168      */
169     private void processResults(List<ProgramFilter> aProgramCondition,
170             TVGuide aGuide, Notifier aNotifier) throws MessagingException {
171         ProgramActionExecutor executor = new ProgramActionExecutor();
172         for (ProgramFilter filter : aProgramCondition) {
173             List<Program> programs = filter.apply(aGuide);
174             ProgramAction action = filter.getAction();
175             for (Program program : programs) {
176                 action.execute(program, executor);
177             }
178         }
179         executor.commit();
180         try {
181             aNotifier.send(executor.getXmlReport());
182         } catch (NotificationException e) { 
183             throw new RuntimeException(e);
184         }
185         sendMail(executor);
186     }
187
188     /**
189      * Creates the crawler.
190      * 
191      * @param aCrawlerConfig
192      *            Crawler configuration file.
193      * @param aOs
194      *            Logging output stream for the crawler.
195      * @param aClient
196      *            HTTP Client to use.
197      * @return Crawler.
198      * @throws FileNotFoundException
199      *             In case configuration files cannot be found.
200      */
201     private Crawler createCrawler(String aCrawlerConfig, PrintStream aOs,
202             HttpClient aClient) throws FileNotFoundException {
203         ConfigurationParser parser = new ConfigurationParser(aOs);
204         InputStream crawlerConfigFile = new FileInputStream(new File(
205                 aCrawlerConfig));
206         Configuration config = parser.parse(crawlerConfigFile);
207         Crawler crawler = new CrawlerImpl(aClient, config);
208         return crawler;
209     }
210
211     /**
212      * Gets the start page of the electronic programme guide. This involves
213      * login and navigation to a suitable start page after logging in.
214      * 
215      * @param aStartUrl
216      *            URL of the electronic programme guide.
217      * @param aCrawler
218      *            Crawler to use.
219      * @return Starting page.
220      */
221     private Page getStartPage(String aStartUrl, Crawler aCrawler) {
222         try {
223             Page page = aCrawler.getPage(aStartUrl);
224             return page.getAction("channels-favorites").execute();
225         } catch (PageException e) {
226             throw new RuntimeException(
227                     "Could not login to electronic program guide", e);
228         }
229     }
230
231     /**
232      * Creates the TV guide by web crawling.
233      * 
234      * @param aPage
235      *            Starting page.
236      * @return TV guide.
237      */
238     private TVGuide createGuide(Page aPage) {
239         LOG.info("Obtaining full TV guide");
240         Action[] actions = aPage.getActions();
241         List<Channel> channels = new ArrayList<Channel>();
242         for (Action action : actions) {
243             try {
244                 LOG.info("Getting channel info for '" + action.getName() + "'");
245                 Channel channel = createChannel(action.getName(), action
246                         .execute().getAction("right-now").execute());
247                 channels.add(channel);
248                 if (SystemProperties.isDebugMode()) {
249                     break; // Only one channel is crawled.
250                 }
251             } catch (PageException e) {
252                 LOG.error("Could not create channel information for '"
253                         + action.getName() + "'", e);
254             }
255         }
256         return new TVGuide(channels);
257     }
258
259     /**
260      * Create channel information for a specific channel.
261      * 
262      * @param aChannel
263      *            Channel name.
264      * @param aPage
265      *            Starting page for the channel.
266      * @return Channel.
267      */
268     private Channel createChannel(String aChannel, Page aPage) {
269         LOG.info("Obtaining program for " + aChannel);
270         Action[] programActions = aPage.getActions();
271         List<Program> programs = new ArrayList<Program>();
272         for (Action action : programActions) {
273             String time = action.getContent().element("time").getText().trim();
274             Matcher matcher = _pattern.matcher(time);
275             if (matcher.matches()) {
276                 Time begin = new Time(Integer.parseInt(matcher.group(1)),
277                         Integer.parseInt(matcher.group(2)));
278                 Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
279                         .parseInt(matcher.group(4)));
280                 TimeInterval interval = new TimeInterval(begin, end);
281                 String description = "";
282                 String keywords = "";
283                 if (!SystemProperties.isNoProgramDetailsRequired()) {
284                     try {
285                         Page programInfo = action.execute();
286                         description = programInfo.getContent().element(
287                                 "description").getText().trim();
288                         keywords = programInfo.getContent().element("keywords")
289                                 .getText().trim();
290                     } catch (PageException e) {
291                         LOG.warn(
292                                 "Program details could not be determined for '"
293                                         + action.getName() + "'", e);
294                     }
295                 }
296                 Program program = new Program(aChannel, action.getName(),
297                         description, keywords, interval, action);
298
299                 LOG.info("Got program " + program);
300                 programs.add(program);
301             }
302         }
303         return new Channel(aChannel, programs);
304     }
305
306     /**
307      * Sends a summary mail to the user.
308      * 
309      * @param aText
310      *            Text of the mail.
311      * @throws MessagingException
312      *             In case of problems sending mail.
313      */
314     private void sendMail(ProgramActionExecutor aExecutor) throws MessagingException {
315         String textReport = aExecutor.getReport();
316         System.out.println("Text report: \n" + textReport);
317         System.out.println("XML report:\n" + aExecutor.getXmlReport().asXML());
318         
319         
320         Properties props = new Properties();
321         props.put("mail.transport.protocol", "smtp");
322         props.put("mail.smtp.host", "falcon");
323         props.put("mail.smtp.port", "25");
324
325         Session mailSession = Session.getInstance(props);
326         InternetAddress from = new InternetAddress("erik@brakkee.org");
327     
328         HtmlEmail mail = new HtmlEmail();
329         mail.setMailSession(mailSession);
330         try {
331             mail.setFrom("erik@brakkee.org");
332             mail.setTo(Arrays.asList(new InternetAddress[] { from }));
333             mail.setSentDate(new Date());
334             mail.setSubject("KiSS Crawler Update");
335             String html = aExecutor.getXmlReport().asXML(); 
336             Document document = new XSLT().transform(html.getBytes(), new FileResource(new File("reportToHtml.xsl")));
337             ByteArrayOutputStream xhtml = new ByteArrayOutputStream();
338             XMLSerializer serializer = new XMLSerializer(xhtml, new OutputFormat());
339             serializer.serialize(document);
340             mail.setHtmlMsg(xhtml.toString());
341             mail.setTextMsg(textReport);
342             mail.send();
343         } catch (EmailException e) {
344             throw new RuntimeException(e);
345         } catch (TransformerException e) { 
346             throw new RuntimeException(e);
347         } catch (IOException e) { 
348             throw new RuntimeException(e);
349         }
350     }
351
352 }