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