0c01a9bcc3e9a712df3a3553a54c265b1e4b7cc0
[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, NotificationException {
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(
124                     new ClasspathUriResolver());
125
126             Crawler crawler = createCrawler(aCrawlerConfig, client, transformer);
127             InputStream programConfigFile = new FileInputStream(new File(
128                     aProgramConfig));
129             ProgramConfigurationParser parser = new ProgramConfigurationParser(
130                     transformer);
131             parser.parse(programConfigFile);
132             List<ProgramFilter> programFilters = parser.getFilters();
133
134             Report report = new Report();
135
136             try {
137                 Page page = getStartPage(aStartUrl, crawler, report);
138                 TVGuide guide = createGuide(page, report);
139                 PrintVisitor printer = new PrintVisitor(System.out);
140                 guide.accept(printer);
141                 processResults(programFilters, guide, parser.getNotifier(),
142                         report);
143             } catch (PageException e) {
144                 report.addMessage("Problem getting TV guide", e);
145                 LOG.info("Problem getting TV guide", e);
146             }
147             parser.getNotifier().send(report.asXml());
148         } finally {
149             System.out.println("Crawler finished");
150         }
151     }
152
153     /**
154      * Records interesting shows.
155      * 
156      * @param aProgramCondition
157      *            Condition determining which shows are interesting.
158      * @param aGuide
159      *            Television guide.
160      * @throws MessagingException
161      *             In case of problems sending a summary mail.
162      */
163     private void processResults(List<ProgramFilter> aProgramCondition,
164             TVGuide aGuide, Notifier aNotifier, Report aReport) {
165         ProgramActionExecutor executor = new ProgramActionExecutor(aReport);
166         for (ProgramFilter filter : aProgramCondition) {
167             List<Program> programs = filter.apply(aGuide);
168             ProgramAction action = filter.getAction();
169             for (Program program : programs) {
170                 action.execute(program, executor);
171             }
172         }
173         executor.commit();
174
175     }
176
177     /**
178      * Creates the crawler.
179      * 
180      * @param aCrawlerConfig
181      *            Crawler configuration file.
182      * @param aOs
183      *            Logging output stream for the crawler.
184      * @param aClient
185      *            HTTP Client to use.
186      * @return Crawler.
187      * @throws FileNotFoundException
188      *             In case configuration files cannot be found.
189      */
190     private Crawler createCrawler(String aCrawlerConfig, HttpClient aClient,
191             XslTransformer aTransformer) throws FileNotFoundException {
192         ConfigurationParser parser = new ConfigurationParser(aTransformer);
193         InputStream crawlerConfigFile = new FileInputStream(new File(
194                 aCrawlerConfig));
195         Configuration config = parser.parse(crawlerConfigFile);
196         Crawler crawler = new CrawlerImpl(aClient, config);
197         return crawler;
198     }
199
200     /**
201      * Gets the start page of the electronic programme guide. This involves
202      * login and navigation to a suitable start page after logging in.
203      * 
204      * @param aStartUrl
205      *            URL of the electronic programme guide.
206      * @param aCrawler
207      *            Crawler to use.
208      * @param aReport
209      *            Report to use.
210      * @return Starting page.
211      */
212     private Page getStartPage(String aStartUrl, Crawler aCrawler, Report aReport)
213             throws PageException {
214         try {
215             Page page = aCrawler.getPage(aStartUrl);
216             Action favorites = page.getAction("channels-favorites");
217             if (favorites == null) {
218                 String msg = "Channels favorites action not found on start page";
219                 throw new PageException(msg);
220             }
221             return favorites.execute();
222         } catch (PageException e) {
223             String msg = "Could not login to electronic programme guide.";
224             throw new PageException(msg, e);
225         }
226     }
227
228     /**
229      * Creates the TV guide by web crawling.
230      * 
231      * @param aPage
232      *            Starting page.
233      * @param aReport
234      *            Report to use.
235      * @return TV guide.
236      */
237     private TVGuide createGuide(Page aPage, Report aReport) {
238         LOG.info("Obtaining full TV guide");
239         Action[] actions = aPage.getActions();
240         List<Channel> channels = new ArrayList<Channel>();
241         for (Action action : actions) {
242             try {
243                 LOG.info("Getting channel info for '" + action.getName() + "'");
244                 Action rightNow = action.execute().getAction("right-now");
245                 if (rightNow == null) {
246                     throw new PageException("Channel summary page for '"
247                             + action.getName()
248                             + "' does not contain required information");
249                 }
250                 Channel channel = createChannel(action.getName(), rightNow
251                         .execute(), aReport);
252                 channels.add(channel);
253                 if (SystemProperties.isDebugMode()) {
254                     break; // Only one channel is crawled.
255                 }
256             } catch (PageException e) {
257                 aReport.addMessage("Could not create channel information for '"
258                         + action.getName() + "'");
259                 LOG.error("Could not create channel information for '"
260                         + action.getName() + "'", e);
261             }
262         }
263         return new TVGuide(channels);
264     }
265
266     /**
267      * Create channel information for a specific channel.
268      * 
269      * @param aChannel
270      *            Channel name.
271      * @param aPage
272      *            Starting page for the channel.
273      * @return Channel.
274      */
275     private Channel createChannel(String aChannel, Page aPage, Report aReport) {
276         LOG.info("Obtaining program for " + aChannel);
277         Action[] programActions = aPage.getActions();
278         List<Program> programs = new ArrayList<Program>();
279         for (Action action : programActions) {
280             String time = action.getContent().element("time").getText().trim();
281             Matcher matcher = _pattern.matcher(time);
282             if (matcher.matches()) {
283                 Time begin = new Time(Integer.parseInt(matcher.group(1)),
284                         Integer.parseInt(matcher.group(2)));
285                 Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
286                         .parseInt(matcher.group(4)));
287                 TimeInterval interval = new TimeInterval(begin, end);
288                 String description = "";
289                 String keywords = "";
290                 if (!SystemProperties.isNoProgramDetailsRequired()) {
291                     try {
292                         Page programInfo = action.execute();
293                         description = programInfo.getContent().element(
294                                 "description").getText().trim();
295                         keywords = programInfo.getContent().element("keywords")
296                                 .getText().trim();
297                     } catch (PageException e) {
298                         String msg =   "Program details could not be determined for '"
299                             + action.getName() + "'";
300                         aReport.addMessage(msg, e);
301                         LOG.warn(msg, e);
302                     }
303                 }
304                 Program program = new Program(aChannel, action.getName(),
305                         description, keywords, interval, action);
306
307                 LOG.info("Got program " + program);
308                 programs.add(program);
309             }
310         }
311         return new Channel(aChannel, programs);
312     }
313 }