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