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