From e3a420a990c329ea20836fcb72aa8ef9ec2cf6de Mon Sep 17 00:00:00 2001
From: Jan-Pascal van Best <janpascal@vanbest.org>
Date: Mon, 4 Mar 2013 11:18:13 +0100
Subject: [PATCH] Initial creation of Horizon class, completely non functional

---
 src/main/java/org/vanbest/xmltv/Horizon.java | 297 +++++++++++++++++++
 1 file changed, 297 insertions(+)
 create mode 100644 src/main/java/org/vanbest/xmltv/Horizon.java

diff --git a/src/main/java/org/vanbest/xmltv/Horizon.java b/src/main/java/org/vanbest/xmltv/Horizon.java
new file mode 100644
index 0000000..a513228
--- /dev/null
+++ b/src/main/java/org/vanbest/xmltv/Horizon.java
@@ -0,0 +1,297 @@
+package org.vanbest.xmltv;
+
+/*
+ Copyright (c) 2013 Jan-Pascal van Best <janpascal@vanbest.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ The full license text can be found in the LICENSE file.
+ */
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import net.sf.json.JSON;
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+public class Horizon extends AbstractEPGSource implements EPGSource {
+
+	static String channels_url = "https://www.horizon.tv/oesp/api/NL/nld/web/channels/";
+	static String listings_url = "https://www.horizon.tv/oesp/api/NL/nld/web/listings";
+	//  ?byStationId=28070126&sort=startTime&range=1-100&byStartTime=1362000000000~1362100000000";
+
+	private static final int MAX_PROGRAMMES_PER_DAY = 9999;
+	private static final int MAX_DAYS_AHEAD_SUPPORTED_BY_HORIZON = 3;
+
+	public static String NAME = "horizon.tv";
+
+	static Logger logger = Logger.getLogger(Horizon.class);
+
+	public Horizon(int sourceId, Config config) {
+		super(sourceId, config);
+	}
+
+	public String getName() {
+		return NAME;
+	}
+
+	public static URL programmeUrl(Channel channel, int day)
+			throws Exception {
+		StringBuilder s = new StringBuilder(listings_url);
+		s.append("?byStationId=");
+		s.append(channel.id);
+		Calendar startTime=Calendar.getInstance();
+		startTime.set(Calendar.HOUR_OF_DAY, 0);
+		startTime.set(Calendar.MINUTE, 0);
+		startTime.set(Calendar.SECOND, 0);
+		startTime.set(Calendar.MILLISECOND, 0);
+		Calendar endTime = (Calendar) startTime.clone();
+		endTime.add(Calendar.DAY_OF_MONTH,  1);
+		s.append("&byStartTime=");
+		s.append(startTime.getTimeInMillis());
+		s.append("~");
+		s.append(endTime.getTimeInMillis());
+		s.append("&sort=startTime&range=1-100");
+
+		return new URL(s.toString());
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.vanbest.xmltv.EPGSource#getChannels()
+	 */
+	@Override
+	public List<Channel> getChannels() {
+		List<Channel> result = new ArrayList<Channel>(10);
+		URL url = null;
+		try {
+			url = new URL(channels_url);
+		} catch (MalformedURLException e) {
+			logger.error("Exception creating horizon channel list url", e);
+		}
+		JSONObject channels;
+		try {
+			channels = fetchJSON(url);
+		} catch (Exception e1) {
+			// TODO Auto-generated catch block
+			e1.printStackTrace();
+			return result;
+		}
+		logger.info("horizon channels json: " + channels.toString());
+
+		int numChannels = Integer.parseInt(channels.getString("totalResults"));
+		JSONArray jsonArray = channels.getJSONArray("channels");
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JSONObject zender = jsonArray.getJSONObject(i);
+			
+			// System.out.println( "id: " + zender.getString("id"));
+			// System.out.println( "name: " + zender.getString("name"));
+			int id = zender.getInt("id");
+			String name = org.apache.commons.lang.StringEscapeUtils
+					.unescapeHtml(zender.getString("name"));
+			String icon = "http://horizonassets.nl/img/channels/53x27/" + id
+					+ ".png";
+			Channel c = Channel.getChannel(getId(), Integer.toString(id), name,
+					icon);
+			result.add(c);
+		}
+
+		return result;
+	}
+
+	private JSONObject fetchJSON(URL url) throws Exception {
+		String json = fetchURL(url);
+		logger.debug(json);
+		return JSONObject.fromObject(json);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.vanbest.xmltv.EPGSource#getProgrammes(java.util.List, int,
+	 * boolean)
+	 */
+	@Override
+	public List<Programme> getProgrammes(List<Channel> channels, int day)
+			throws Exception {
+		List<Programme> result = new ArrayList<Programme>();
+		if (day > MAX_DAYS_AHEAD_SUPPORTED_BY_HORIZON) {
+			return result; // empty list
+		}
+
+
+		for (Channel c : channels) {
+			URL url = programmeUrl(c, day);
+			JSONObject jsonObject = fetchJSON(url);
+			JSON ps = (JSON) jsonObject.get(c.id);
+			if (ps.isArray()) {
+				JSONArray programs = (JSONArray) ps;
+				for (int i = 0; i < programs.size()
+						&& i < MAX_PROGRAMMES_PER_DAY; i++) {
+					JSONObject programme = programs.getJSONObject(i);
+					Programme p = programmeFromJSON(programme,
+							config.fetchDetails);
+					p.channel = c.getXmltvChannelId();
+					result.add(p);
+				}
+			} else {
+				JSONObject programs = (JSONObject) ps;
+				int count = 0;
+				for (Object o : programs.keySet()) {
+					if (count > MAX_PROGRAMMES_PER_DAY)
+						break;
+					JSONObject programme = programs.getJSONObject(o.toString());
+					Programme p = programmeFromJSON(programme,
+							config.fetchDetails);
+					p.channel = c.getXmltvChannelId();
+					result.add(p);
+					count++;
+				}
+			}
+		}
+
+		return result;
+	}
+
+	/*
+	 * {"4": [{"db_id":"12436404", "titel":"RTL Boulevard", "genre":"Amusement",
+	 * "soort":"Amusementsprogramma", "kijkwijzer":"", "artikel_id":null,
+	 * "datum_start":"2012-03-30 23:45:00", "datum_end":"2012-03-31 00:40:00"},
+	 * {"db_id":"12436397","titel":"Teleshop 4","genre":"Overige","soort":
+	 * "Homeshopping"
+	 * ,"kijkwijzer":"","artikel_id":null,"datum_start":"2012-03-31 00:40:00"
+	 * ,"datum_end":"2012-03-31 00:41:00"},
+	 * {"db_id":"12436398","titel":"Cupido TV"
+	 * ,"genre":"Overige","soort":"","kijkwijzer"
+	 * :"","artikel_id":null,"datum_start"
+	 * :"2012-03-31 00:41:00","datum_end":"2012-03-31 04:30:00"},
+	 * {"db_id":"12436399"
+	 * ,"titel":"Morning chat","genre":"Overige","soort":"","kijkwijzer"
+	 * :"","artikel_id"
+	 * :null,"datum_start":"2012-03-31 04:30:00","datum_end":"2012-03-31 06:00:00"
+	 * }, ....... ]}
+	 */
+	private Programme programmeFromJSON(JSONObject programme,
+			boolean fetchDetails) throws Exception {
+		String id = programme.getString("db_id");
+		Programme result = cache.get(getId(), id);
+		boolean cached = (result != null);
+		if (result == null) {
+			stats.cacheMisses++;
+			result = new Programme();
+			// Do this here, because we can only add to these fields. Pity if
+			// they're updated
+			result.addTitle(programme.getString("titel"));
+			String genre = programme.getString("genre");
+			if (genre != null && !genre.isEmpty())
+				result.addCategory(config.translateCategory(genre));
+			String kijkwijzer = programme.getString("kijkwijzer");
+			if (kijkwijzer != null && !kijkwijzer.isEmpty()) {
+				List<String> list = parseKijkwijzer(kijkwijzer);
+				if (config.joinKijkwijzerRatings) {
+					// mythtv doesn't understand multiple <rating> tags
+					result.addRating("kijkwijzer", StringUtils.join(list, ","));
+				} else {
+					for (String rating : list) {
+						result.addRating("kijkwijzer", rating);
+					}
+				}
+				// TODO add icon from HTML detail page
+			}
+		} else {
+			// System.out.println("From cache: " +
+			// programme.getString("titel"));
+			stats.cacheHits++;
+		}
+		logger.trace("      titel:" + programme.getString("titel"));
+		logger.trace("datum_start:" + programme.getString("datum_start"));
+		logger.trace("  datum_end:" + programme.getString("datum_end"));
+		logger.trace("      genre:" + programme.getString("genre"));
+		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
+				new Locale("nl"));
+		result.startTime = df.parse(programme.getString("datum_start"));
+		result.endTime = df.parse(programme.getString("datum_end"));
+		// TODO other fields
+
+		if (!cached) {
+			// FIXME where to do this?
+			cache.put(getId(), id, result);
+		}
+		logger.debug(result);
+		return result;
+	}
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		Config config = Config.getDefaultConfig();
+		Horizon horizon = new Horizon(1, config);
+		try {
+			List<Channel> channels = horizon.getChannels();
+			System.out.println("Channels: " + channels);
+			XMLStreamWriter writer = XMLOutputFactory.newInstance()
+					.createXMLStreamWriter(new FileWriter("horizon.xml"));
+			writer.writeStartDocument();
+			writer.writeCharacters("\n");
+			writer.writeDTD("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">");
+			writer.writeCharacters("\n");
+			writer.writeStartElement("tv");
+			// List<Channel> my_channels = channels;
+			List<Channel> my_channels = channels.subList(0, 15);
+			for (Channel c : my_channels) {
+				c.serialize(writer);
+			}
+			writer.flush();
+			List<Programme> programmes = horizon.getProgrammes(my_channels, 2);
+			for (Programme p : programmes) {
+				p.serialize(writer);
+			}
+			writer.writeEndElement();
+			writer.writeEndDocument();
+			writer.flush();
+			if (!config.quiet) {
+				EPGSource.Stats stats = horizon.getStats();
+				System.out.println("Number of programmes from cache: "
+						+ stats.cacheHits);
+				System.out.println("Number of programmes fetched: "
+						+ stats.cacheMisses);
+				System.out.println("Number of fetch errors: "
+						+ stats.fetchErrors);
+			}
+			horizon.close();
+		} catch (Exception e) {
+			logger.error("Error in horizon testing", e);
+		}
+	}
+
+}
-- 
2.39.5