7a0d421d294e50a777c9fe02d6186924960ffbe5
[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.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.PrintStream;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Properties;
30 import java.util.Set;
31 import java.util.TreeSet;
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
42 import org.apache.commons.httpclient.HttpClient;
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.wamblee.crawler.Action;
46 import org.wamblee.crawler.Configuration;
47 import org.wamblee.crawler.Crawler;
48 import org.wamblee.crawler.Page;
49 import org.wamblee.crawler.PageException;
50 import org.wamblee.crawler.impl.ConfigurationParser;
51 import org.wamblee.crawler.impl.CrawlerImpl;
52
53 /**
54  * The KiSS crawler for automatic recording of interesting TV shows.
55  * 
56  */
57 public class KissCrawler {
58
59     private static final Log LOG = LogFactory.getLog(KissCrawler.class);
60
61     /**
62      * Log file name for the crawler.
63      */
64     private static final String LOG_FILE = "kiss.log";
65
66     /**
67      * Start URL of the electronic programme guide.
68      */
69     private static final String START_URL = "http://epg.kml.kiss-technology.com/login_core.php";
70
71     /**
72      * Crawler configuration file.
73      */
74     private static final String CRAWLER_CONFIG = "config.xml";
75
76     /**
77      * Configuration file describing interesting programs.
78      */
79     private static final String PROGRAM_CONFIG = "programs.xml";
80
81     /**
82      * Regular expression for matching time interval strings in the retrieved
83      * pages.
84      */
85     private static final String TIME_REGEX = "([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
86
87     /**
88      * Compiled pattern for the time regular expression.
89      */
90     private Pattern _pattern;
91
92     /**
93      * Runs the KiSS crawler.
94      * 
95      * @param aArgs
96      *            Arguments, currently all ignored because they are hardcoded.
97      * @throws Exception
98      *             In case of problems.
99      */
100     public static void main(String[] aArgs) throws Exception {
101         new KissCrawler(START_URL, CRAWLER_CONFIG, PROGRAM_CONFIG);
102     }
103
104     /**
105      * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
106      * EPG guide, filters the guide for interesting programs, tries to record
107      * them, and sends a summary mail to the user.
108      * 
109      * @param aStartUrl
110      *            Start URL of the electronic programme guide.
111      * @param aCrawlerConfig
112      *            Configuration file for the crawler.
113      * @param aProgramConfig
114      *            Configuration file describing interesting shows.
115      * @throws IOException
116      *             In case of problems reading files.
117      * @throws MessagingException
118      *             In case of problems sending a mail notification.
119      */
120     public KissCrawler(String aStartUrl, String aCrawlerConfig,
121             String aProgramConfig) throws IOException, MessagingException {
122
123         _pattern = Pattern.compile(TIME_REGEX);
124
125         FileOutputStream fos = new FileOutputStream(new File(LOG_FILE));
126         PrintStream os = new PrintStream(fos);
127
128         try {
129             HttpClient client = new HttpClient();
130             // client.getHostConfiguration().setProxy("127.0.0.1", 3128);
131
132             Crawler crawler = createCrawler(aCrawlerConfig, os, client);
133             InputStream programConfigFile = new FileInputStream(new File(
134                     aProgramConfig));
135             List<ProgramFilter> programFilters = new ProgramConfigurationParser()
136                     .parse(programConfigFile);
137
138             Page page = getStartPage(aStartUrl, crawler);
139             TVGuide guide = createGuide(page);
140             PrintVisitor printer = new PrintVisitor(System.out);
141             guide.accept(printer);
142             processResults(programFilters, guide);
143         } finally {
144             os.flush();
145             os.close();
146             System.out.println("Output written on '" + LOG_FILE + "'");
147         }
148     }
149
150     /**
151      * Records interesting shows.
152      * 
153      * @param aProgramCondition
154      *            Condition determining which shows are interesting.
155      * @param aGuide
156      *            Television guide.
157      * @throws MessagingException
158      *             In case of problems sending a summary mail.
159      */
160     private void processResults(List<ProgramFilter> aProgramCondition,
161             TVGuide aGuide) throws MessagingException {
162         ProgramActionExecutor executor = new ProgramActionExecutor();
163         for (ProgramFilter filter : aProgramCondition) {
164             List<Program> programs = filter.apply(aGuide);
165             ProgramAction action = filter.getAction(); 
166             for (Program program: programs) { 
167                 action.execute(program, executor);
168             }
169         }
170         executor.commit();
171         String msg = executor.getReport();
172         System.out.println(msg);
173         sendMail(msg);
174     }
175
176     /**
177      * Creates the crawler.
178      * 
179      * @param aCrawlerConfig
180      *            Crawler configuration file.
181      * @param aOs
182      *            Logging output stream for the crawler.
183      * @param aClient
184      *            HTTP Client to use.
185      * @return Crawler.
186      * @throws FileNotFoundException
187      *             In case configuration files cannot be found.
188      */
189     private Crawler createCrawler(String aCrawlerConfig, PrintStream aOs,
190             HttpClient aClient) throws FileNotFoundException {
191         ConfigurationParser parser = new ConfigurationParser(aOs);
192         InputStream crawlerConfigFile = new FileInputStream(new File(
193                 aCrawlerConfig));
194         Configuration config = parser.parse(crawlerConfigFile);
195         Crawler crawler = new CrawlerImpl(aClient, config);
196         return crawler;
197     }
198
199     /**
200      * Gets the start page of the electronic programme guide. This involves
201      * login and navigation to a suitable start page after logging in.
202      * 
203      * @param aStartUrl
204      *            URL of the electronic programme guide.
205      * @param aCrawler
206      *            Crawler to use.
207      * @return Starting page.
208      */
209     private Page getStartPage(String aStartUrl, Crawler aCrawler) {
210         try {
211             Page page = aCrawler.getPage(aStartUrl);
212             return page.getAction("channels-favorites").execute();
213         } catch (PageException e) {
214             throw new RuntimeException(
215                     "Could not login to electronic program guide", e);
216         }
217     }
218
219     /**
220      * Creates the TV guide by web crawling.
221      * 
222      * @param aPage
223      *            Starting page.
224      * @return TV guide.
225      */
226     private TVGuide createGuide(Page aPage) {
227         LOG.info("Obtaining full TV guide");
228         Action[] actions = aPage.getActions();
229         List<Channel> channels = new ArrayList<Channel>();
230         for (Action action : actions) {
231             try {
232                 LOG.info("Getting channel info for '" + action.getName() + "'");
233                 Channel channel = createChannel(action.getName(), action
234                         .execute().getAction("right-now").execute());
235                 channels.add(channel);
236                 if (SystemProperties.isDebugMode()) {
237                     break; // Only one channel is crawled.
238                 }
239             } catch (PageException e) {
240                 LOG.error("Could not create channel information for '"
241                         + action.getName() + "'", e);
242             }
243         }
244         return new TVGuide(channels);
245     }
246
247     /**
248      * Create channel information for a specific channel.
249      * 
250      * @param aChannel
251      *            Channel name.
252      * @param aPage
253      *            Starting page for the channel.
254      * @return Channel.
255      */
256     private Channel createChannel(String aChannel, Page aPage) {
257         LOG.info("Obtaining program for " + aChannel);
258         Action[] programActions = aPage.getActions();
259         List<Program> programs = new ArrayList<Program>();
260         for (Action action : programActions) {
261             String time = action.getContent().element("time").getText().trim();
262             Matcher matcher = _pattern.matcher(time);
263             if (matcher.matches()) {
264                 Time begin = new Time(Integer.parseInt(matcher.group(1)),
265                         Integer.parseInt(matcher.group(2)));
266                 Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
267                         .parseInt(matcher.group(4)));
268                 TimeInterval interval = new TimeInterval(begin, end);
269                 String description = "";
270                 String keywords = "";
271                 if (!SystemProperties.isNoProgramDetailsRequired()) {
272                     try {
273                         Page programInfo = action.execute();
274                         description = programInfo.getContent().element(
275                                 "description").getText().trim();
276                         keywords = programInfo.getContent().element("keywords")
277                                 .getText().trim();
278                     } catch (PageException e) {
279                         LOG
280                                 .warn("Program details could not be determined for '"
281                                         + action.getName() + "'", e);
282                     }
283                 }
284                 Program program = new Program(aChannel, action.getName(),
285                         description, keywords, interval, action);
286
287                 LOG.info("Got program " + program);
288                 programs.add(program);
289             }
290         }
291         return new Channel(aChannel, programs);
292     }
293
294     /**
295      * Sends a summary mail to the user.
296      * 
297      * @param aText
298      *            Text of the mail.
299      * @throws MessagingException
300      *             In case of problems sending mail.
301      */
302     private void sendMail(String aText) throws MessagingException {
303         Properties props = new Properties();
304         props.put("mail.transport.protocol", "smtp");
305         props.put("mail.smtp.host", "falcon");
306         props.put("mail.smtp.port", "25");
307
308         Session mailSession = Session.getInstance(props);
309         Message message = new MimeMessage(mailSession);
310
311         message.setFrom(new InternetAddress("erik@brakkee.org"));
312         message.setRecipient(Message.RecipientType.TO, new InternetAddress(
313                 "erik@brakkee.org"));
314         message.setSentDate(new Date());
315         message.setSubject("KiSS crawler update");
316         message.setText(aText);
317         Transport.send(message);
318     }
319
320 }