Java tutorial
/* * RSSFeed - Azureus2 Plugin * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package org.kmallan.azureus.rssfeed; import org.apache.commons.lang.Entities; import org.eclipse.swt.graphics.Color; import org.gudy.azureus2.core3.util.Debug; import org.w3c.dom.*; import org.xml.sax.*; import javax.xml.parsers.*; import javax.swing.text.html.parser.ParserDelegator; import java.io.*; import java.util.*; import java.net.*; public class Scheduler extends TimerTask { private View view = null; public void setView(View view) { this.view = view; } private int getDelay() { int delay; delay = Plugin.getIntParameter("Delay"); if (delay < Plugin.MIN_REFRESH) { delay = Plugin.MIN_REFRESH; Plugin.setParameter("Delay", delay); } return delay; } private boolean isEnabled() { return Plugin.getBooleanParameter("Enabled"); } public void run() { for (int iLoop = 0; iLoop < view.rssfeedConfig.getUrlCount(); iLoop++) { final UrlBean urlBean = view.rssfeedConfig.getUrl(iLoop); final ListGroup listGroup = urlBean.getGroup(view.treeViewManager, getDelay()); final int delay = listGroup.getDelay(); final int elapsed = urlBean.isHitting() ? 0 : listGroup.getElapsed(); if (elapsed >= delay && (isEnabled() && urlBean.isEnabled()) || urlBean.getRefreshNow()) { urlBean.resetGroup(getDelay()); Thread t = new Thread("Fetcher-" + urlBean.getName()) { public void run() { Plugin.debugOut("hitting " + urlBean.getName() + " - " + urlBean.getLocation()); urlBean.setHitting(true); runFeed(urlBean); addBacklogElements(urlBean); urlBean.setHitting(false); } }; t.start(); } if (view.isOpen() && view.display != null && !view.display.isDisposed()) view.display.asyncExec(new Runnable() { public void run() { if (view.listTable == null || view.listTable.isDisposed()) return; ListTreeItem listGroup = view.treeViewManager.getItem(urlBean); if (urlBean.isHitting()) { String s = urlBean.getStatus() + " "; if (urlBean.getError().length() > 0 && urlBean.getStatus().equals("Error")) { s += "- " + urlBean.getError(); } else if (urlBean.getStatus().equals("Downloading")) { if (urlBean.getPercent() > 0) { s += Integer.toString(urlBean.getPercent()) + "%"; } else if (urlBean.getAmount() > 0) { s += Double.toString(Math.floor(new Integer(urlBean.getAmount()).doubleValue() / (double) 1024 * (double) 100) / (double) 100) + "KB"; } } listGroup.setText(1, s); } else if (urlBean.isEnabled()) { if (isEnabled()) { int time = delay - elapsed; int minutes = new Double(Math.floor(new Integer(time).doubleValue() / (double) 60)) .intValue(); int seconds = time - (minutes * 60); String newTime = Integer.toString(minutes) + ":" + (seconds < 10 ? "0" + Integer.toString(seconds) : Integer.toString(seconds)); listGroup.setText(1, newTime + " until reload" + (!urlBean.getError().equalsIgnoreCase("") ? " - " + urlBean.getError() : "")); } else { listGroup.setText(1, "Automatic reload disabled" + (!urlBean.getError().equalsIgnoreCase("") ? " - " + urlBean.getError() : "")); } } else { listGroup.setText(1, "Feed disabled" + (!urlBean.getError().equalsIgnoreCase("") ? " - " + urlBean.getError() : "")); } if (!urlBean.getError().equalsIgnoreCase("")) listGroup.setForeground(new Color(view.display, 255, 0, 0)); else listGroup.resetForeground(); } }); } } public synchronized void runFeed(final UrlBean urlBean) { String url = urlBean.getLocation(); String title, link, description; ListGroup listBeans = urlBean.getGroup(); DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); docFactory.setIgnoringComments(true); docFactory.setIgnoringElementContentWhitespace(true); DocumentBuilder docBuild; Document feed; File xmlTmp = null; try { docBuild = docFactory.newDocumentBuilder(); Downloader downloader = new Downloader(); downloader.addListener(new DownloaderListener() { public boolean completed = false, error = false; public void downloaderUpdate(int state, int percent, int amount, String err) { if (completed || error) return; String status = new String("Pending"); switch (state) { case Downloader.DOWNLOADER_NON_INIT: status = "Pending"; break; case Downloader.DOWNLOADER_INIT: status = "Connecting"; break; case Downloader.DOWNLOADER_START: status = "Download Starting"; break; case Downloader.DOWNLOADER_DOWNLOADING: status = "Downloading"; break; case Downloader.DOWNLOADER_FINISHED: status = "Download Finished"; completed = true; break; case Downloader.DOWNLOADER_NOTMODIFIED: status = "Not modified"; completed = true; break; case Downloader.DOWNLOADER_ERROR: status = "Error"; error = true; break; } urlBean.setStatus(status); if (percent > 0) urlBean.setPercent(percent); if (amount > 0) urlBean.setAmount(amount); if (!err.equalsIgnoreCase("")) urlBean.setError(err); if (view.isOpen() && view.display != null && !view.display.isDisposed()) view.display.asyncExec(new Runnable() { public void run() { if (view.listTable == null || view.listTable.isDisposed()) return; ListTreeItem listGroup = view.treeViewManager.getItem(urlBean); listGroup.setText(1, urlBean.getStatus() + " " + (!urlBean.getError() .equalsIgnoreCase("") && urlBean.getStatus() == "Error" ? "- " + urlBean.getError() : (urlBean.getStatus() == "Downloading" ? (urlBean.getPercent() > 0 ? Integer.toString(urlBean.getPercent()) + "%" : (urlBean.getAmount() > 0 ? Double.toString(Math.floor( new Integer(urlBean.getAmount()).doubleValue() / (double) 1024 * (double) 100) / (double) 100) + "KB" : "")) : ""))); if (!urlBean.getError().equalsIgnoreCase("")) listGroup.setForeground(new Color(view.display, 255, 0, 0)); else listGroup.resetForeground(); } }); } }); downloader.init(url, "text/xml, text/html, text/plain, application/x-httpd-php", null, (urlBean.getUseCookie() ? urlBean.getCookie() : null), urlBean.getLastModifed(), urlBean.getLastEtag()); if (downloader.getState() == Downloader.DOWNLOADER_ERROR) return; if (downloader.getState() == Downloader.DOWNLOADER_NOTMODIFIED) { // no change, add the old items again for (Iterator iter = listBeans.getPreviousItems().iterator(); iter.hasNext();) { addTableElement(urlBean, listBeans, (ListBean) iter.next()); } addBacklogElements(urlBean); downloader.notModified(); // use the last seen TTL value if available if (urlBean.getObeyTTL() && listBeans.getPreviousDelay() > 0) listBeans.setDelay(listBeans.getPreviousDelay()); return; } Plugin.debugOut( urlBean.getName() + " Last-Modified: " + downloader.lastModified + " ETag: " + downloader.etag); urlBean.setLastModifed(downloader.lastModified); urlBean.setLastEtag(downloader.etag); xmlTmp = new File(Plugin.getPluginDirectoryName(), "tmp-" + urlBean.getID() + ".xml"); xmlTmp.createNewFile(); FileOutputStream fileout = new FileOutputStream(xmlTmp, false); byte[] buf = new byte[2048]; int read = 0; do { if (downloader.getState() == Downloader.DOWNLOADER_CANCELED) break; read = downloader.read(buf); if (read > 0) { System.err.print("."); fileout.write(buf, 0, read); } else if (read == 0) { System.err.print("?"); try { long numMillisecondsToSleep = 100; Thread.sleep(numMillisecondsToSleep); } catch (InterruptedException e) { } } } while (read >= 0); fileout.flush(); fileout.close(); docBuild.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { // System.out.println( publicId + ", " + systemId ); // handle bad DTD external refs if (Plugin.getProxyOption() == Plugin.PROXY_TRY_PLUGIN) { return new InputSource( new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes())); } try { URL url = new URL(systemId); String host = url.getHost(); InetAddress.getByName(host); // try connecting too as connection-refused will also bork XML parsing InputStream is = null; try { URLConnection con = url.openConnection(); con.setConnectTimeout(15 * 1000); con.setReadTimeout(15 * 1000); is = con.getInputStream(); byte[] buffer = new byte[32]; int pos = 0; while (pos < buffer.length) { int len = is.read(buffer, pos, buffer.length - pos); if (len <= 0) { break; } pos += len; } String str = new String(buffer, "UTF-8").trim().toLowerCase(Locale.US); if (!str.contains("<?xml")) { // not straightforward to check for naked DTDs, could be lots of <!-- commentry preamble which of course can occur // in HTML too buffer = new byte[32000]; pos = 0; while (pos < buffer.length) { int len = is.read(buffer, pos, buffer.length - pos); if (len <= 0) { break; } pos += len; } str += new String(buffer, "UTF-8").trim().toLowerCase(Locale.US); if (str.contains("<html") && str.contains("<head")) { throw (new Exception("Bad DTD")); } } } catch (Throwable e) { return new InputSource( new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes())); } finally { if (is != null) { try { is.close(); } catch (Throwable e) { } } } return (null); } catch (UnknownHostException e) { return new InputSource( new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes())); } catch (Throwable e) { return (null); } } }); try { feed = docBuild.parse(xmlTmp); } catch (Exception e) { feed = null; String msg = Debug.getNestedExceptionMessage(e); if ((msg.contains("entity") && msg.contains("was referenced")) || msg.contains("entity reference")) { FileInputStream fis = new FileInputStream(xmlTmp); try { feed = docBuild.parse(new EntityFudger(fis)); } catch (Throwable f) { } finally { fis.close(); } } if (feed == null) { if (e instanceof ParserConfigurationException) { throw ((ParserConfigurationException) e); } else if (e instanceof SAXException) { throw ((SAXException) e); } else if (e instanceof IOException) { throw ((IOException) e); } else { throw (new IOException(msg)); } } } xmlTmp.delete(); downloader.done(); if (downloader.getState() == Downloader.DOWNLOADER_ERROR) return; } catch (ParserConfigurationException e) { if (xmlTmp != null) xmlTmp.delete(); urlBean.setError("Malformed RSS XML: " + e.getMessage()); return; } catch (SAXException e) { if (xmlTmp != null) xmlTmp.delete(); urlBean.setError("Malformed RSS XML: " + e.getMessage()); return; } catch (IOException e) { if (xmlTmp != null) xmlTmp.delete(); urlBean.setError("IO Exception: " + e.getMessage()); return; } if (urlBean.getObeyTTL()) { NodeList feedTTL = feed.getElementsByTagName("ttl"); if (feedTTL.getLength() == 1) { int newDelay = Integer.parseInt(getText(feedTTL.item(0))) * 60; if (newDelay > 0) urlBean.getGroup().setDelay(newDelay, true); } } // Parse the channel's "item"s NodeList feedItems = feed.getElementsByTagName("item"); int feedItemLen = feedItems.getLength(); for (int iLoop = 0; iLoop < feedItemLen; iLoop++) { Node item = feedItems.item(iLoop); NodeList params = item.getChildNodes(); int paramsLen = params.getLength(); title = link = description = ""; for (int i = 0; i < paramsLen; i++) { Node param = params.item(i); if (param.getNodeType() == Node.ELEMENT_NODE) { if (param.getNodeName().equalsIgnoreCase("title")) { title = getText(param); } else if (param.getNodeName().equalsIgnoreCase("enclosure") && param.hasAttributes()) { if ((((param.getAttributes()).getNamedItem("type")).getNodeValue()) .equalsIgnoreCase("application/x-bittorrent")) { link = ((param.getAttributes()).getNamedItem("url")).getNodeValue(); } } else if (param.getNodeName().equalsIgnoreCase("link") && link.length() == 0) { link = getText(param); } else if (param.getNodeName().equalsIgnoreCase("description")) { description = getText(param); if (description != null && description.trim().startsWith("<")) { // strip html tags and entity references from description HtmlAnalyzer parser = new HtmlAnalyzer(); try { new ParserDelegator().parse(new StringReader(description), parser, true); description = parser.getPlainText(); } catch (IOException e) { } } description += "\n"; } } } if (link.length() == 0) continue; if (link.indexOf("://") < 0 && !link.toLowerCase().startsWith("magnet")) { try { link = HtmlAnalyzer.resolveRelativeURL(urlBean.getLocation(), link); } catch (MalformedURLException e) { Plugin.debugOut("Bad link URL: " + link + " -> " + e.getMessage()); continue; } } int state = ListBean.NO_DOWNLOAD; String titleTest = title.toLowerCase(); String linkTest = link.toLowerCase(); FilterBean curFilter = null; for (int i = 0; i < view.rssfeedConfig.getFilterCount(); i++) { curFilter = view.rssfeedConfig.getFilter(i); if (curFilter == null) continue; if (curFilter.matches(urlBean.getID(), titleTest, linkTest)) { if (curFilter.getMode().equalsIgnoreCase("Pass")) { state = ListBean.DOWNLOAD_INCL; } else { state = ListBean.DOWNLOAD_EXCL; } break; } } Episode e = null; Movie m = null; final FilterBean filterBean = curFilter; if (filterBean != null) { if ("TVShow".equalsIgnoreCase(filterBean.getType())) { try { e = FilterBean.getSeason(titleTest); } catch (Exception ee) { } try { if (e == null) { e = FilterBean.getSeason(linkTest); } } catch (Exception ee) { } } else if ("Movie".equalsIgnoreCase(filterBean.getType())) { m = FilterBean.getMovie(titleTest); if (m == null) { m = FilterBean.getMovie(linkTest); } Plugin.debugOut("Download is a movie: " + m); } } if (state == ListBean.DOWNLOAD_INCL) { Plugin.debugOut("testing for download: " + linkTest); if (filterBean.getUseSmartHistory()) { for (int i = 0; i < view.rssfeedConfig.getHistoryCount(); i++) { HistoryBean histBean = view.rssfeedConfig.getHistory(i); if (linkTest.equalsIgnoreCase(histBean.getLocation())) { Plugin.debugOut("found location match: " + histBean); state = ListBean.DOWNLOAD_HIST; break; } if (e != null && histBean.getSeasonStart() >= 0 && filterBean.getUseSmartHistory()) { final String showTitle = histBean.getTitle(); // Old history beans may not have set showTitle so keep using the old way of matching if (showTitle == null ? (histBean.getFiltID() == filterBean.getID()) : showTitle.equalsIgnoreCase(e.showTitle)) { // "Proper" episode is not skipped unless history is also proper if (histBean.isProper() || !e.isProper()) { int seasonStart = histBean.getSeasonStart(); int episodeStart = histBean.getEpisodeStart(); int seasonEnd = histBean.getSeasonEnd(); int episodeEnd = histBean.getEpisodeEnd(); Plugin.debugOut(e + " vs s" + seasonStart + "e" + episodeStart + " - s" + seasonEnd + "e" + episodeEnd); if (e.inRange(seasonStart, episodeStart, seasonEnd, episodeEnd)) { Plugin.debugOut("found filter and episode match: " + e); state = ListBean.DOWNLOAD_HIST; break; } } } } else if (m != null && m.getTitle().equals(histBean.getTitle()) && m.getYear() == histBean.getYear()) { if (histBean.isProper() || !m.isProper()) { Plugin.debugOut("found movie match: " + m); state = ListBean.DOWNLOAD_HIST; } } } } else Plugin.debugOut("Filter doesn't use smart history: " + filterBean); } final ListBean listBean = addTableElement(urlBean, listBeans, title, link, description, state); if (state == ListBean.DOWNLOAD_INCL) { // Add the feed final String curLink = link; boolean success = view.torrentDownloader.addTorrent(curLink, urlBean, filterBean, listBean); if (success && filterBean.getType().equalsIgnoreCase("Other") && filterBean.getDisableAfter()) filterBean.setEnabled(false); if (view.isOpen() && view.display != null && !view.display.isDisposed()) view.display.asyncExec(new Runnable() { public void run() { ListTreeItem listItem = view.treeViewManager.getItem(listBean); if (listItem != null) listItem.update(); } }); } } } private static String getText(Node node) { StringBuffer sb = new StringBuffer(); node.normalize(); NodeList children = node.getChildNodes(); int childrenLen = children.getLength(), type; for (int iLoop = 0; iLoop < childrenLen; iLoop++) { Node child = children.item(iLoop); type = child.getNodeType(); if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { sb.append(child.getNodeValue()); sb.append(" "); } } return sb.toString().trim(); } private void addBacklogElements(UrlBean urlBean) { List backLog = urlBean.getBackLog(); for (Iterator iter = backLog.iterator(); iter.hasNext();) view.treeViewManager.addListBean((ListBean) iter.next(), urlBean, true); if (backLog.size() != urlBean.getPrevBackLogSize()) view.rssfeedConfig.storeOptions(); } private ListBean addTableElement(UrlBean urlBean, ListGroup listBeans, String title, String link, String description, int state) { ListBean listBean = new ListBean(); listBean.setName(title); listBean.setLocation(link); listBean.setState(state); listBean.setDescription(description); return addTableElement(urlBean, listBeans, listBean); } private ListBean addTableElement(UrlBean urlBean, ListGroup listBeans, ListBean listBean) { listBean.setFeed(urlBean); view.treeViewManager.addListBean(listBean, urlBean, false); listBeans.add(listBean); return listBean; } private static class EntityFudger extends InputStream { private InputStream is; char[] buffer = new char[16]; int buffer_pos = 0; char[] insertion = new char[16]; int insertion_pos = 0; int insertion_len = 0; public EntityFudger(InputStream _is) { is = _is; } @Override public int read() throws IOException { if (insertion_len > 0) { int result = insertion[insertion_pos++] & 0xff; if (insertion_pos == insertion_len) { insertion_pos = 0; insertion_len = 0; } return (result); } while (true) { int b = is.read(); if (b < 0) { // end of file if (buffer_pos == 0) { return (b); } else if (buffer_pos == 1) { buffer_pos = 0; return (buffer[0] & 0xff); } else { System.arraycopy(buffer, 1, insertion, 0, buffer_pos - 1); insertion_len = buffer_pos - 1; insertion_pos = 0; buffer_pos = 0; return (buffer[0] & 0xff); } } // normal byte if (buffer_pos == 0) { if (b == '&') { buffer[buffer_pos++] = (char) b; } else { return (b); } } else { if (buffer_pos == buffer.length - 1) { // buffer's full, give up buffer[buffer_pos++] = (char) b; System.arraycopy(buffer, 0, insertion, 0, buffer_pos); buffer_pos = 0; insertion_pos = 0; insertion_len = buffer_pos; return (insertion[insertion_pos++]); } else { if (b == ';') { // got some kind of reference mebe buffer[buffer_pos++] = (char) b; String ref = new String(buffer, 1, buffer_pos - 2).toLowerCase(Locale.US); String replacement; if (ref.equals("amp") || ref.equals("lt") || ref.equals("gt") || ref.equals("quot") || ref.equals("apos") || ref.startsWith("#")) { replacement = new String(buffer, 0, buffer_pos); } else { int num = Entities.HTML40.entityValue(ref); if (num != -1) { replacement = "&#" + num + ";"; } else { replacement = new String(buffer, 0, buffer_pos); } } char[] chars = replacement.toCharArray(); System.arraycopy(chars, 0, insertion, 0, chars.length); buffer_pos = 0; insertion_pos = 0; insertion_len = chars.length; return (insertion[insertion_pos++]); } else { buffer[buffer_pos++] = (char) b; char c = (char) b; if (!Character.isLetterOrDigit(c)) { // handle naked & if (buffer_pos == 2 && buffer[0] == '&') { char[] chars = "&".toCharArray(); System.arraycopy(chars, 0, insertion, 0, chars.length); buffer_pos = 0; insertion_pos = 0; insertion_len = chars.length; // don't forget the char we just read insertion[insertion_len++] = (char) b; return (insertion[insertion_pos++]); } else { // not a valid entity reference System.arraycopy(buffer, 0, insertion, 0, buffer_pos); buffer_pos = 0; insertion_pos = 0; insertion_len = buffer_pos; return (insertion[insertion_pos++]); } } } } } } } public void close() throws IOException { is.close(); } public long skip(long n) throws IOException { // meh, vague attempt here if (insertion_len > 0) { // buffer is currently empty, shove remaining into buffer to unify processing int rem = insertion_len - insertion_pos; System.arraycopy(insertion, insertion_pos, buffer, 0, rem); insertion_pos = 0; insertion_len = 0; buffer_pos = rem; } if (n <= buffer_pos) { // skip is <= buffer contents int rem = buffer_pos - (int) n; System.arraycopy(buffer, (int) n, insertion, 0, rem); insertion_pos = 0; insertion_len = rem; return (n); } int to_skip = buffer_pos; buffer_pos = 0; return (is.skip(n - to_skip) + to_skip); } public int available() throws IOException { return (buffer_pos + is.available()); } } }