From: Owen Dunn Date: Sun, 6 Dec 2009 15:13:48 +0000 (+0000) Subject: Initial commit of Yarrg code X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?p=jarrg-owen.git;a=commitdiff_plain;h=3cff51a7f2bf5a5d9b78482f408da1b70037994c Initial commit of Yarrg code --- diff --git a/src/PCTB.xml b/src/PCTB.xml new file mode 100644 index 0000000..72a093b --- /dev/null +++ b/src/PCTB.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/myjavatools/web/ClientHttpRequest.java b/src/com/myjavatools/web/ClientHttpRequest.java new file mode 100644 index 0000000..f16c71c --- /dev/null +++ b/src/com/myjavatools/web/ClientHttpRequest.java @@ -0,0 +1,511 @@ +package com.myjavatools.web; + +import java.net.URLConnection; +import java.net.URL; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.io.File; +import java.io.InputStream; +import java.util.Random; +import java.io.OutputStream; +import java.io.FileInputStream; +import java.util.Iterator; + +/** + *

Title: Client HTTP Request class

+ *

Description: this class helps to send POST HTTP requests with various form data, + * including files. Cookies can be added to be included in the request.

+ *

Modified by Ted Pearson(ted@tedpearson.com) 5/4/09

+ * + * @author Vlad Patryshev + * @version 1.0 + */ +public class ClientHttpRequest { + URLConnection connection; + OutputStream os = null; + Map cookies = new HashMap(); + + protected void connect() throws IOException { + if (os == null) os = connection.getOutputStream(); + } + + protected void write(char c) throws IOException { + connect(); + os.write(c); + } + + protected void write(String s) throws IOException { + connect(); + os.write(s.getBytes()); + } + + protected void newline() throws IOException { + connect(); + write("\r\n"); + } + + protected void writeln(String s) throws IOException { + connect(); + write(s); + newline(); + } + + private static Random random = new Random(); + + protected static String randomString() { + return Long.toString(random.nextLong(), 36); + } + + String boundary = "---------------------------" + randomString() + randomString() + randomString(); + + private void boundary() throws IOException { + write("--"); + write(boundary); + } + + /** + * Creates a new multipart POST HTTP request on a freshly opened URLConnection + * + * @param connection an already open URL connection + * @throws IOException + */ + public ClientHttpRequest(URLConnection connection) throws IOException { + this.connection = connection; + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", + "multipart/form-data; boundary=" + boundary); + } + + /** + * Creates a new multipart POST HTTP request for a specified URL + * + * @param url the URL to send request to + * @throws IOException + */ + public ClientHttpRequest(URL url) throws IOException { + this(url.openConnection()); + } + + /** + * Creates a new multipart POST HTTP request for a specified URL string + * + * @param urlString the string representation of the URL to send request to + * @throws IOException + */ + public ClientHttpRequest(String urlString) throws IOException { + this(new URL(urlString)); + } + + + private void postCookies() { + StringBuffer cookieList = new StringBuffer(); + + for (Iterator i = cookies.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)(i.next()); + cookieList.append(entry.getKey().toString() + "=" + entry.getValue()); + + if (i.hasNext()) { + cookieList.append("; "); + } + } + if (cookieList.length() > 0) { + connection.setRequestProperty("Cookie", cookieList.toString()); + } + } + + /** + * adds a cookie to the requst + * @param name cookie name + * @param value cookie value + * @throws IOException + */ + public void setCookie(String name, String value) throws IOException { + cookies.put(name, value); + } + + /** + * adds cookies to the request + * @param cookies the cookie "name-to-value" map + * @throws IOException + */ + public void setCookies(Map cookies) throws IOException { + if (cookies == null) return; + this.cookies.putAll(cookies); + } + + /** + * adds cookies to the request + * @param cookies array of cookie names and values (cookies[2*i] is a name, cookies[2*i + 1] is a value) + * @throws IOException + */ + public void setCookies(String[] cookies) throws IOException { + if (cookies == null) return; + for (int i = 0; i < cookies.length - 1; i+=2) { + setCookie(cookies[i], cookies[i+1]); + } + } + + private void writeName(String name) throws IOException { + newline(); + write("Content-Disposition: form-data; name=\""); + write(name); + write('"'); + } + + /** + * adds a string parameter to the request + * @param name parameter name + * @param value parameter value + * @throws IOException + */ + public void setParameter(String name, String value) throws IOException { + boundary(); + writeName(name); + newline(); newline(); + writeln(value); + } + + private static void pipe(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[500000]; + int nread; + int navailable; + int total = 0; + synchronized (in) { + while((nread = in.read(buf, 0, buf.length)) >= 0) { + out.write(buf, 0, nread); + total += nread; + } + } + out.flush(); + buf = null; + } + + /** + * adds a file parameter to the request + * @param name parameter name + * @param filename the name of the file + * @param is input stream to read the contents of the file from + * @throws IOException + */ + public void setParameter(String name, String filename, InputStream is) throws IOException { + String type = connection.guessContentTypeFromName(filename); + if (type == null) type = "application/octet-stream"; + setParameter(name, filename, is, type); + } + + /** + * adds a file parameter to the request + * @param name parameter name + * @param filename the name of the file + * @param is input stream to read the contents of the file from + * @param type Content-Type of the file + * @throws IOException + */ + public void setParameter(String name, String filename, InputStream is, String type) throws IOException { + boundary(); + writeName(name); + write("; filename=\""); + write(filename); + write('"'); + newline(); + write("Content-Type: "); + writeln(type); + newline(); + pipe(is, os); + newline(); + } + + /** + * adds a file parameter to the request + * @param name parameter name + * @param file the file to upload + * @throws IOException + */ + public void setParameter(String name, File file) throws IOException { + setParameter(name, file.getPath(), new FileInputStream(file)); + } + + /** + * adds a parameter to the request; if the parameter is a File, the file is uploaded, otherwise the string value of the parameter is passed in the request + * @param name parameter name + * @param object parameter value, a File or anything else that can be stringified + * @throws IOException + */ + public void setParameter(String name, Object object) throws IOException { + if (object instanceof File) { + setParameter(name, (File) object); + } else { + setParameter(name, object.toString()); + } + } + + /** + * adds parameters to the request + * @param parameters "name-to-value" map of parameters; if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request + * @throws IOException + */ + public void setParameters(Map parameters) throws IOException { + if (parameters == null) return; + for (Iterator i = parameters.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)i.next(); + setParameter(entry.getKey().toString(), entry.getValue()); + } + } + + /** + * adds parameters to the request + * @param parameters array of parameter names and values (parameters[2*i] is a name, parameters[2*i + 1] is a value); if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request + * @throws IOException + */ + public void setParameters(Object[] parameters) throws IOException { + if (parameters == null) return; + for (int i = 0; i < parameters.length - 1; i+=2) { + setParameter(parameters[i].toString(), parameters[i+1]); + } + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added + * @return input stream with the server response + * @throws IOException + */ + public InputStream post() throws IOException { + boundary(); + writeln("--"); + os.close(); + InputStream tis; + try { + tis = connection.getInputStream(); + } catch (IOException e) { + tis = ((java.net.HttpURLConnection) connection).getErrorStream(); + } + return tis; + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public InputStream post(Map parameters) throws IOException { + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public InputStream post(Object[] parameters) throws IOException { + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + * @see setCookies + */ + public InputStream post(Map cookies, Map parameters) throws IOException { + setCookies(cookies); + setParameters(parameters); + return post(); + } + + /** + * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + * @see setCookies + */ + public InputStream post(String[] cookies, Object[] parameters) throws IOException { + setCookies(cookies); + setParameters(parameters); + return post(); + } + + /** + * post the POST request to the server, with the specified parameter + * @param name parameter name + * @param value parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name, Object value) throws IOException { + setParameter(name, value); + return post(); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2) throws IOException { + setParameter(name1, value1); + return post(name2, value2); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException { + setParameter(name1, value1); + return post(name2, value2, name3, value3); + } + + /** + * post the POST request to the server, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @param name4 fourth parameter name + * @param value4 fourth parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException { + setParameter(name1, value1); + return post(name2, value2, name3, value3, name4, value4); + } + + /** + * posts a new request to specified URL, with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public static InputStream post(URL url, Map parameters) throws IOException { + return new ClientHttpRequest(url).post(parameters); + } + + /** + * posts a new request to specified URL, with parameters that are passed in the argument + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setParameters + */ + public static InputStream post(URL url, Object[] parameters) throws IOException { + return new ClientHttpRequest(url).post(parameters); + } + + /** + * posts a new request to specified URL, with cookies and parameters that are passed in the argument + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setCookies + * @see setParameters + */ + public static InputStream post(URL url, Map cookies, Map parameters) throws IOException { + return new ClientHttpRequest(url).post(cookies, parameters); + } + + /** + * posts a new request to specified URL, with cookies and parameters that are passed in the argument + * @param cookies request cookies + * @param parameters request parameters + * @return input stream with the server response + * @throws IOException + * @see setCookies + * @see setParameters + */ + public static InputStream post(URL url, String[] cookies, Object[] parameters) throws IOException { + return new ClientHttpRequest(url).post(cookies, parameters); + } + + /** + * post the POST request specified URL, with the specified parameter + * @param name parameter name + * @param value parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1) throws IOException { + return new ClientHttpRequest(url).post(name1, value1); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3); + } + + /** + * post the POST request to specified URL, with the specified parameters + * @param name1 first parameter name + * @param value1 first parameter value + * @param name2 second parameter name + * @param value2 second parameter value + * @param name3 third parameter name + * @param value3 third parameter value + * @param name4 fourth parameter name + * @param value4 fourth parameter value + * @return input stream with the server response + * @throws IOException + * @see setParameter + */ + public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException { + return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3, name4, value4); + } +} diff --git a/src/com/sun/java/accessibility/util/AWTEventMonitor$AWTEventsListener.class b/src/com/sun/java/accessibility/util/AWTEventMonitor$AWTEventsListener.class new file mode 100644 index 0000000..167bb3f Binary files /dev/null and b/src/com/sun/java/accessibility/util/AWTEventMonitor$AWTEventsListener.class differ diff --git a/src/com/sun/java/accessibility/util/AWTEventMonitor.class b/src/com/sun/java/accessibility/util/AWTEventMonitor.class new file mode 100644 index 0000000..a3c4736 Binary files /dev/null and b/src/com/sun/java/accessibility/util/AWTEventMonitor.class differ diff --git a/src/com/sun/java/accessibility/util/AccessibilityEventMonitor$AccessibilityEventListener.class b/src/com/sun/java/accessibility/util/AccessibilityEventMonitor$AccessibilityEventListener.class new file mode 100644 index 0000000..d96c32f Binary files /dev/null and b/src/com/sun/java/accessibility/util/AccessibilityEventMonitor$AccessibilityEventListener.class differ diff --git a/src/com/sun/java/accessibility/util/AccessibilityEventMonitor.class b/src/com/sun/java/accessibility/util/AccessibilityEventMonitor.class new file mode 100644 index 0000000..205df7f Binary files /dev/null and b/src/com/sun/java/accessibility/util/AccessibilityEventMonitor.class differ diff --git a/src/com/sun/java/accessibility/util/AccessibilityListenerList.class b/src/com/sun/java/accessibility/util/AccessibilityListenerList.class new file mode 100644 index 0000000..6fcb895 Binary files /dev/null and b/src/com/sun/java/accessibility/util/AccessibilityListenerList.class differ diff --git a/src/com/sun/java/accessibility/util/ComponentEvtDispatchThread.class b/src/com/sun/java/accessibility/util/ComponentEvtDispatchThread.class new file mode 100644 index 0000000..94f7f54 Binary files /dev/null and b/src/com/sun/java/accessibility/util/ComponentEvtDispatchThread.class differ diff --git a/src/com/sun/java/accessibility/util/EventID.class b/src/com/sun/java/accessibility/util/EventID.class new file mode 100644 index 0000000..4690b9c Binary files /dev/null and b/src/com/sun/java/accessibility/util/EventID.class differ diff --git a/src/com/sun/java/accessibility/util/EventQueueMonitor.class b/src/com/sun/java/accessibility/util/EventQueueMonitor.class new file mode 100644 index 0000000..a593c75 Binary files /dev/null and b/src/com/sun/java/accessibility/util/EventQueueMonitor.class differ diff --git a/src/com/sun/java/accessibility/util/EventQueueMonitorItem.class b/src/com/sun/java/accessibility/util/EventQueueMonitorItem.class new file mode 100644 index 0000000..f4f33aa Binary files /dev/null and b/src/com/sun/java/accessibility/util/EventQueueMonitorItem.class differ diff --git a/src/com/sun/java/accessibility/util/GUIInitializedListener.class b/src/com/sun/java/accessibility/util/GUIInitializedListener.class new file mode 100644 index 0000000..35fb998 Binary files /dev/null and b/src/com/sun/java/accessibility/util/GUIInitializedListener.class differ diff --git a/src/com/sun/java/accessibility/util/GUIInitializedMulticaster.class b/src/com/sun/java/accessibility/util/GUIInitializedMulticaster.class new file mode 100644 index 0000000..45a6006 Binary files /dev/null and b/src/com/sun/java/accessibility/util/GUIInitializedMulticaster.class differ diff --git a/src/com/sun/java/accessibility/util/SwingEventMonitor$SwingEventListener.class b/src/com/sun/java/accessibility/util/SwingEventMonitor$SwingEventListener.class new file mode 100644 index 0000000..945cd7f Binary files /dev/null and b/src/com/sun/java/accessibility/util/SwingEventMonitor$SwingEventListener.class differ diff --git a/src/com/sun/java/accessibility/util/SwingEventMonitor.class b/src/com/sun/java/accessibility/util/SwingEventMonitor.class new file mode 100644 index 0000000..cd87091 Binary files /dev/null and b/src/com/sun/java/accessibility/util/SwingEventMonitor.class differ diff --git a/src/com/sun/java/accessibility/util/TopLevelWindowListener.class b/src/com/sun/java/accessibility/util/TopLevelWindowListener.class new file mode 100644 index 0000000..99b4c5b Binary files /dev/null and b/src/com/sun/java/accessibility/util/TopLevelWindowListener.class differ diff --git a/src/com/sun/java/accessibility/util/TopLevelWindowMulticaster.class b/src/com/sun/java/accessibility/util/TopLevelWindowMulticaster.class new file mode 100644 index 0000000..f92650c Binary files /dev/null and b/src/com/sun/java/accessibility/util/TopLevelWindowMulticaster.class differ diff --git a/src/com/sun/java/accessibility/util/Translator.class b/src/com/sun/java/accessibility/util/Translator.class new file mode 100644 index 0000000..e288f0a Binary files /dev/null and b/src/com/sun/java/accessibility/util/Translator.class differ diff --git a/src/com/sun/java/accessibility/util/java/awt/ButtonTranslator.class b/src/com/sun/java/accessibility/util/java/awt/ButtonTranslator.class new file mode 100644 index 0000000..e8743fb Binary files /dev/null and b/src/com/sun/java/accessibility/util/java/awt/ButtonTranslator.class differ diff --git a/src/com/sun/java/accessibility/util/java/awt/CheckboxTranslator.class b/src/com/sun/java/accessibility/util/java/awt/CheckboxTranslator.class new file mode 100644 index 0000000..4cd7741 Binary files /dev/null and b/src/com/sun/java/accessibility/util/java/awt/CheckboxTranslator.class differ diff --git a/src/com/sun/java/accessibility/util/java/awt/LabelTranslator.class b/src/com/sun/java/accessibility/util/java/awt/LabelTranslator.class new file mode 100644 index 0000000..c58915f Binary files /dev/null and b/src/com/sun/java/accessibility/util/java/awt/LabelTranslator.class differ diff --git a/src/com/sun/java/accessibility/util/java/awt/ListTranslator.class b/src/com/sun/java/accessibility/util/java/awt/ListTranslator.class new file mode 100644 index 0000000..e3a7d9f Binary files /dev/null and b/src/com/sun/java/accessibility/util/java/awt/ListTranslator.class differ diff --git a/src/com/sun/java/accessibility/util/java/awt/TextComponentTranslator.class b/src/com/sun/java/accessibility/util/java/awt/TextComponentTranslator.class new file mode 100644 index 0000000..5d2e709 Binary files /dev/null and b/src/com/sun/java/accessibility/util/java/awt/TextComponentTranslator.class differ diff --git a/src/com/tedpearson/util/update/Updater.java b/src/com/tedpearson/util/update/Updater.java new file mode 100644 index 0000000..cd396c5 --- /dev/null +++ b/src/com/tedpearson/util/update/Updater.java @@ -0,0 +1,189 @@ +package com.tedpearson.util.update; + +import java.net.*; +import java.io.*; +import java.util.prefs.*; +import java.awt.Component; +import javax.swing.JOptionPane; +import javax.swing.*; + +/* + TODO: + show update window with progress bar when updating (optionally) +*/ + +public class Updater { + private int version = -1; + private URL downloadURL; + private static Updater updater = new Updater(); + + public static Updater getUpdater() { + return updater; + } + + public boolean checkForUpdate( + Component parentComponent, + String updateUrl, + int currentVersion) { + return checkForUpdate(parentComponent, updateUrl, "this program", currentVersion, true); + } + + + /** + * Just check to see if there is an update. + */ + public boolean checkForUpdate( + Component parentComponent, + String updateUrl, + String program, + int currentVersion, + boolean promptUser) { + String node = "com/tedpearson/update"; + Preferences prefs = Preferences.userRoot().node(node); + String update = prefs.get(updateUrl,""); + if(update.equals("Never")) { + return false; + } + if(!update.equals("Yes")) { + if(!promptUser) { + return false; + } + // first, confirm user wants to allow updates + int option = JOptionPane.showOptionDialog(parentComponent, + "Do you want to let " + program + " check for updates when it opens?", + "Check for Updates?", + 0, JOptionPane.QUESTION_MESSAGE, + null, new String[] {"Yes","This Time","Not Now","Never"},"Yes"); + switch(option) { + case 2: + return false; + case 3: + prefs.put(updateUrl,"Never"); + return false; + case 0: + prefs.put(updateUrl,"Yes"); + case 1: + } + } + // query the server to check the version number + accessVersion(parentComponent, updateUrl); + // System.out.println(version+","+currentVersion); + if(version > currentVersion) { + return true; + } else { + return false; + } + } + + public void checkAndUpdate( + Component parentComponent, + String updateUrl, + String program, + int currentVersion, + boolean promptUser, + String jar) { + if(checkForUpdate(parentComponent, updateUrl, program, currentVersion, promptUser)) { + updateJar(parentComponent, updateUrl, currentVersion, promptUser, jar); + } + } + + public void checkAndUpdate( + Component parentComponent, + String updateUrl, + int currentVersion) { + if(checkForUpdate(parentComponent, updateUrl, currentVersion)) { + updateJar(parentComponent, updateUrl, currentVersion); + } + } + + public void updateJar( + Component parentComponent, + String updateUrl, + int currentVersion) { + updateJar(parentComponent, updateUrl, currentVersion, true, null); + } + + public void updateJar( + Component parentComponent, + String updateUrl, + int currentVersion, + boolean promptUser, + String jar) { + try { + accessVersion(parentComponent, updateUrl); + // download new version + File temp = File.createTempFile("com.tedpearson.updater.download",null); + temp.deleteOnExit(); + URLConnection conn = downloadURL.openConnection(); + ProgressMonitorInputStream pmis = new ProgressMonitorInputStream( + parentComponent, + "Downloading program update", + conn.getInputStream() + ); + ProgressMonitor pm = pmis.getProgressMonitor(); + pm.setMillisToDecideToPopup(0); + pm.setMillisToPopup(0); + if(!promptUser) { + pm.setMillisToDecideToPopup(Integer.MAX_VALUE); + pm.setMillisToPopup(Integer.MAX_VALUE); + } + pm.setMaximum(conn.getHeaderFieldInt("Content-Length",0)); + copyFile(pmis, new FileOutputStream(temp)); + // replace old with new. + if(jar == null) { + jar = getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); + } + jar = jar.replaceAll("%20"," "); + copyFile(new FileInputStream(temp), new FileOutputStream(new File(jar))); + // remove temp file + temp.delete(); + // launch new version and quit this one. + ProcessBuilder pb = new ProcessBuilder("java","-jar",jar); + pb.start(); + System.exit(0); + // no new version + } catch(Exception e) { + // errors. + e.printStackTrace(); + // show error here. + if(promptUser) { + JOptionPane.showMessageDialog(parentComponent, "There was an error while trying to update.", + "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void accessVersion(Component parentComponent, String updateUrl) { + if(version != -1) return; + try { + URL url = new URL(updateUrl); + BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); + version = Integer.parseInt(br.readLine()); + downloadURL = new URL(br.readLine()); + } catch(Exception e) { + // errors. + e.printStackTrace(); + // show error here. + JOptionPane.showMessageDialog(parentComponent, "There was an error while trying to update.", + "Error", JOptionPane.ERROR_MESSAGE); + } + } + + /** + * Utility method to copy a file. I can't believe java doesn't have anything + * built-in to do something this simple with less code. Other than channels, that is. + * + * @param in stream to read file from + * @param out stream to write file to + */ + public static void copyFile(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + + } + in.close(); + out.close(); + } +} \ No newline at end of file diff --git a/src/com/tedpearson/ypp/market/ControlPanel.java b/src/com/tedpearson/ypp/market/ControlPanel.java new file mode 100644 index 0000000..a00e734 --- /dev/null +++ b/src/com/tedpearson/ypp/market/ControlPanel.java @@ -0,0 +1,59 @@ +package com.tedpearson.ypp.market; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.prefs.*; + +/** +* ControlPanel is a simple management utility that sets +* a preference for whether the PCTB client is to launch or not. +*/ +public class ControlPanel extends JFrame { + public static void main(String[] args) { + new ControlPanel(); + } + + public ControlPanel() { + super("PCTB Control Panel"); + final Preferences prefs = Preferences.userNodeForPackage(getClass()); + final JCheckBox cb = new JCheckBox("Launch PCTB Uploader when YPP starts?", prefs.getBoolean("launchAtStartup", true)); + final JCheckBox toPCTB = new JCheckBox("Upload to PCTB?", prefs.getBoolean("uploadToPCTB", true)); + final JCheckBox toYarrg = new JCheckBox("Upload to Yarrg?", prefs.getBoolean("uploadToYarrg", true)); + + final JRadioButton live = new JRadioButton("Use live servers"); + final JRadioButton testing = new JRadioButton("Use testing servers"); + + live.setSelected(prefs.getBoolean("useLiveServers", false)); + testing.setSelected(!prefs.getBoolean("useLiveServers", false)); + + ButtonGroup liveortest = new ButtonGroup(); + liveortest.add(live); + liveortest.add(testing); + + setLayout(new GridLayout(6,1)); + add(cb); + add(toPCTB); + add(toYarrg); + add(live); + add(testing); + + JButton but = new JButton("Save"); + add(but); + but.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + prefs.putBoolean("launchAtStartup", cb.isSelected()); + prefs.putBoolean("uploadToPCTB", toPCTB.isSelected()); + prefs.putBoolean("uploadToYarrg", toYarrg.isSelected()); + prefs.putBoolean("useLiveServers", live.isSelected()); + System.exit(0); + } + }); + pack(); + setLocationRelativeTo(null); + setVisible(true); + setSize(getWidth() + 10, getHeight() + 10); + setDefaultCloseOperation(EXIT_ON_CLOSE); + getRootPane().setDefaultButton(but); + } +} diff --git a/src/com/tedpearson/ypp/market/Installer.java b/src/com/tedpearson/ypp/market/Installer.java new file mode 100644 index 0000000..a7621b8 --- /dev/null +++ b/src/com/tedpearson/ypp/market/Installer.java @@ -0,0 +1,345 @@ +package com.tedpearson.ypp.market; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.border.*; +import java.io.*; +import com.tedpearson.util.update.*; +import java.util.Properties; +import java.net.URLDecoder; + +/* + allow adding islands/oceans + implement uploads to YAARG +*/ + +/** +* An Installer for PCTB Java Client. +*/ +public class Installer extends JFrame { + public final static int VERSION = 8; + private JLabel label; + private JProgressBar progress; + private JButton install, uninstall; + private static String os = System.getProperty("os.name"); + private static String home = System.getProperty("java.home"); + private static String user_home = System.getProperty("user.home"); + private boolean installed = false; + private boolean uninstalled = false; + private static PrintWriter pw; + + public static void debug(String str) { + try { + pw.println(str); + pw.flush(); + }catch(Exception e) { + + } + } + + public static void main(String[] args) { + /* + try{ + pw = new PrintWriter("C:/Users/Public/ERRORS"); + }catch(Exception e) { + + } + */ + new Installer(args); + } + + /** + * Checks if we have permission to write to the Java Home folder + * that runs YPP. Pops up an error message and exits if we don't have access, + * or on Mac OS X, re-runs the installer using applescript to authenticate. + */ + private void checkPermission(String[] args) { + File a11y = null; + a11y = new File(getJavaHome(),"lib"); + if(os.equals("Mac OS X")) { + if(!a11y.canWrite()) { + JOptionPane.showMessageDialog(null,"Please authenticate as an Administrator, when prompted, to continue installation."); + try { + String installer = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation() + .getPath(), "URT-8"); + Runtime.getRuntime().exec(new String[]{"osascript","-e","do shell script \"java -jar " + + installer + "\" with administrator privileges"}); + } catch(Exception e) { + e.printStackTrace(); + } + System.exit(0); + } + } else { + // clean up after myself + if(os.contains("Vista")) { + File tempDir = new File(System.getProperty("java.io.tmpdir")); + for(File f : tempDir.listFiles()) { + if(f.getName().startsWith("PCTB-lib")) { + for(File g : f.listFiles()) { + g.delete(); + } + f.delete(); + } + } + } + // first check for YPP java + boolean canWrite = true; + File test = new File(a11y, "test_pctb"); + try { + test.createNewFile(); + test.delete(); + } catch(IOException e) { + canWrite = false; + } + if(!canWrite || !a11y.canWrite()) { + if(os.contains("Vista")) { + if(args.length == 1 && args[0].equals("--elevate")) { + JOptionPane.showMessageDialog(null,"Please run this installer while logged in as an Administrator."); + System.exit(0); + } + try { + String installer = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation() + .getPath(), "UTF-8").replaceFirst("/",""); + ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/C", "elevate javaw -jar \"" + installer + + "\" --elevate"); + // create temp lib directory + File temp = File.createTempFile("PCTB-lib",null); + temp.delete(); + temp.mkdir(); + File elevate = new File(temp, "elevate.cmd"); + OutputStream out = new FileOutputStream(elevate); + InputStream in = getClass().getResourceAsStream("/lib/elevate.cmd"); + Updater.copyFile(in, out); + + elevate = new File(temp, "elevate.vbs"); + out = new FileOutputStream(elevate); + in = getClass().getResourceAsStream("/lib/elevate.vbs"); + Updater.copyFile(in, out); + + pb.directory(temp); + Process p = pb.start(); + } catch(Exception e) { + e.printStackTrace(); + } + System.exit(0); + } else { + JOptionPane.showMessageDialog(null,"Please run this installer while logged in as an Administrator."); + System.exit(0); + } + } + } + } + + /** + * Create the installer GUI + */ + public Installer(String[] args) { + super("PCTB Installer"); + + Updater.getUpdater().checkAndUpdate(null, "http://tedpearson.com/market/version", + "the PCTB Uploader client", VERSION, true, + getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); + + // gui: simple window with status area and progress bar + checkPermission(args); + label = new JLabel("Ready to install!"); + add(label,BorderLayout.NORTH); + progress = new JProgressBar(); + progress.setBorder(new EmptyBorder(10,0,10,0)); + add(progress,BorderLayout.CENTER); + String buttonText = "Install"; + install = new JButton(buttonText); + JPanel buttons = new JPanel(); + add(buttons,BorderLayout.SOUTH); + install.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if(installed) { + System.exit(0); + } + try { + install.setEnabled(false); + uninstall.setEnabled(false); + install(); + } catch(IOException err) { + err.printStackTrace(); + JOptionPane.showMessageDialog(Installer.this, "Error during installation."); + setButtonStatus(false,false); + label.setText("Install failed!"); + } + } + }); + uninstall = new JButton("Uninstall"); + buttons.add(uninstall); + buttons.add(install); + uninstall.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if(uninstalled) { + System.exit(0); + } + try { + install.setEnabled(false); + uninstall.setEnabled(false); + uninstall(); + } catch(IOException err) { + err.printStackTrace(); + JOptionPane.showMessageDialog(Installer.this, "Error during installation."); + setButtonStatus(false,false); + label.setText("Uninstall failed!"); + } + } + }); + getRootPane().setBorder(new EmptyBorder(10,10,10,10)); + getRootPane().setDefaultButton(install); + pack(); + setSize(300,getHeight()); + setLocationRelativeTo(null); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setVisible(true); + } + + /** + * Uninstalls the client. Throws an IOException ir errors occur. + * (removes the PCTB class from assistive tech, deletes the jar) + */ + private void uninstall() throws IOException { + label.setText("Uninstalling..."); + progress.setIndeterminate(true); + // remove from assistive tech + File java_home = getJavaHome(); + File a11y = new File(java_home,"lib/accessibility.properties"); + if(a11y.exists()) { + Properties p = new Properties(); + p.load(new FileInputStream(a11y)); + String tech = p.getProperty("assistive_technologies"); + tech = tech.replace("com.tedpearson.ypp.market.MarketUploader", ""); + p.setProperty("assistive_technologies", tech); + p.store(new FileOutputStream(a11y),"Last Modified by PCTB-Installer"); + } + // remove jar + File jar = new File(java_home, "lib/ext/PCTB-Uploader.jar"); + jar.delete(); + setButtonStatus(false,true); + } + + /** + * Utility method to find the Java Home folder that runs YPP. On Windows, it could be + * inside the YPP folder. + */ + private File getJavaHome() { + File java_home = new File(home); + if(os.contains("Windows")) { + File defaultLocation = null; + // check for javavm inside YPP folder, otherwise default location + if(os.contains("Vista")) { + String user = System.getProperty("user.name"); + defaultLocation = new File("C:\\Users\\" + user + "\\AppData\\Roaming\\Three Rings Design\\Puzzle Pirates\\"); + } else if(os.contains("XP")) { + defaultLocation = new File("C:\\Program Files\\Three Rings Design\\Puzzle Pirates\\"); + } + if(defaultLocation != null && defaultLocation.exists()) { + File java_vm = new File(defaultLocation, "java_vm"); + if(java_vm.exists()) { + // this is where we want to install instead + java_home = new File(defaultLocation, "java_vm"); + } + } + } + return java_home; + } + + /** + * Installs the client. + */ + private void install() throws IOException { + label.setText("Installing..."); + progress.setIndeterminate(true); + File java_home = getJavaHome(); + File controlPanel = new File(user_home); + JFileChooser chooser = new JFileChooser(controlPanel); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setDialogTitle("Choose location for Control Panel"); + chooser.setApproveButtonText("Install Control Panel here"); + int state = chooser.showOpenDialog(this); + if(state == JFileChooser.APPROVE_OPTION) { + controlPanel = chooser.getSelectedFile(); + } else { + setButtonStatus(false,false); + label.setText("Install failed!"); + return; + } + InputStream in = getClass().getResourceAsStream("/PCTB-ControlPanel.jar"); + File newFile = new File(controlPanel, "PCTB-ControlPanel.jar"); + newFile.createNewFile(); + OutputStream out = new FileOutputStream(newFile); + Updater.copyFile(in, out); + String search = "assistive_technologies"; + String value = "com.tedpearson.ypp.market.MarketUploader"; + File a11y = new File(java_home,"lib/accessibility.properties"); + boolean skipA11y = false; + Properties p = new Properties(); + if(a11y.exists()) { + // if already contains our modification, ignore + // else add our modification + p.load(new FileInputStream(a11y)); + String tech = p.getProperty(search); + if(tech == null || tech.trim().equals("")) { + // add it! + p.setProperty(search, value); + p.store(new FileOutputStream(a11y),"Last Modified by PCTB-Installer"); + } else if(!tech.contains(value)) { + p.setProperty(search, tech+","+value); + p.store(new FileOutputStream(a11y),"Last Modified by PCTB-Installer"); + } + } else { + // create file with our modification + a11y.createNewFile(); + p.setProperty(search, value); + p.store(new FileOutputStream(a11y),"Last Modified by PCTB-Installer"); + } + + // install program + // copy jar from resource to new file in ext + //String installer = getClass().getProtectionDomain().getCodeSource().getLocation() + // .getPath().replaceAll("%20"," "); + //new FileInputStream(installer); + in = getClass().getResourceAsStream("/PCTB-Uploader.jar"); + newFile = new File(java_home, "lib/ext/PCTB-Uploader.jar"); + newFile.createNewFile(); + out = new FileOutputStream(newFile); + Updater.copyFile(in, out); + JOptionPane.showMessageDialog(this, "Install successful!\n\nWhen you open a new YPP client, you'll see\n" + + "the upload client window alongside YPP.\n\n" + + "To stop the window from appearing, use\n" + + "the Control Panel to disable it, or uninstall.", "Success!", JOptionPane.INFORMATION_MESSAGE); + setButtonStatus(true,false); + } + + /** + * Cleanup after an install, uninstall, or error. Buttons, statuses, and such + */ + private void setButtonStatus(boolean installQ, boolean uninstallQ) { + progress.setIndeterminate(false); + progress.setValue(100); + install.setEnabled(true); + uninstall.setEnabled(true); + String q = "Quit"; + if(installQ) { + install.setText(q); + installed = true; + label.setText("Install complete!"); + } else { + install.setText("Install"); + installed = false; + } + + if(uninstallQ) { + uninstall.setText(q); + uninstalled = true; + label.setText("Uninstall successful."); + } else { + uninstall.setText("Uninstall"); + uninstalled = false; + } + } +} \ No newline at end of file diff --git a/src/com/tedpearson/ypp/market/MarketUploader.java b/src/com/tedpearson/ypp/market/MarketUploader.java new file mode 100644 index 0000000..8e0c109 --- /dev/null +++ b/src/com/tedpearson/ypp/market/MarketUploader.java @@ -0,0 +1,967 @@ +package com.tedpearson.ypp.market; + +import java.awt.*; +import java.awt.event.*; + +import javax.accessibility.*; +import javax.swing.*; + +import com.sun.java.accessibility.util.*; + +import java.util.*; +import java.io.*; +import java.util.*; +import java.net.URL; +import org.w3c.dom.*; +import javax.xml.parsers.DocumentBuilderFactory; +import org.xml.sax.InputSource; +import java.util.zip.GZIPOutputStream; +import com.myjavatools.web.ClientHttpRequest; +import java.util.regex.*; +import java.util.prefs.Preferences; +import java.beans.*; +import com.tedpearson.util.update.*; + +/* + TODO: + allow adding new islands + allow adding new oceans +*/ + +/** +* MarketUploader is a class that handles the uploading of market data from +* Yohoho! Puzzle Pirates. Currently, it must be launched in the save Java +* Virtual Machine as YPP. This is handled by a sister "helper" class, +* {@link MarketUploaderRunner}. +*

+* MarketUploader initializes after the main YPP window has initialized. It +* provides a simple window with a "Capture Market Data" button displayed. +* Upon clicking this button, a progress dialog is displayed, and the data +* is processed and submitted to the Pirate Commodities Trader with Bleach (PCTB) +* web server. If any errors occur, an error dialog is shown, and processing +* returns, the button becoming re-enabled. +* +* @see MarketUploaderRunner +*/ +public class MarketUploader implements TopLevelWindowListener, GUIInitializedListener { + private JFrame frame = null; + private Window window = null; + private JButton findMarket = null; + + private final static String PCTB_LIVE_HOST_URL = "http://pctb.crabdance.com/"; + private final static String PCTB_TEST_HOST_URL = "http://pctb.ilk.org/"; + private String PCTB_HOST_URL; + + // Yarrg protocol parameters + private final static String YARRG_CLIENTNAME = "jpctb greenend"; + private final static String YARRG_CLIENTVERSION = "0.1"; + private final static String YARRG_CLIENTFIXES = ""; + private final static String YARRG_LIVE_URL = "http://upload.yarrg.chiark.net/commod-update-receiver"; + private final static String YARRG_TEST_URL = "http://upload.yarrg.chiark.net/test/commod-update-receiver"; + private String YARRG_URL; + + private boolean uploadToYarrg; + private boolean uploadToPCTB; + + private String islandName = null; + private String oceanName = null; + private java.util.concurrent.CountDownLatch latch = null; + + private AccessibleContext sidePanel; + private HashMap commodMap; + private HashMap islandNumbers = new HashMap(); + { + String[] nums = new String[] + {"","Viridian","Midnight","Hunter","Cobalt","Sage","Ice","Malachite","Crimson","Opal"}; + for(int i=1;irecord, determining the shoppe Id from + * stallMap and the commodity Id from commodMap. + * priceIndex should be the index of the price in the record + * (the quantity will be priceIndex + 1). + * + * @param record the record with data to create the offer from + * @param stallMap a map containing the ids of the various stalls + * @param commodMap a map containing the ids of the various commodities + * @param priceIndex the index of the price in the record + */ + public Offer(ArrayList record, LinkedHashMap stallMap, HashMap commodMap, + int priceIndex) { + Integer commodId = commodMap.get(record.get(0)); + if(commodId == null) { + throw new IllegalArgumentException(); + } + commodity = commodId.intValue(); + price = Integer.parseInt(record.get(priceIndex)); + String qty = record.get(priceIndex+1); + if(qty.equals(">1000")) { + quantity = 1001; + } else { + quantity = Integer.parseInt(record.get(priceIndex+1)); + } + shoppe = stallMap.get(record.get(1)).intValue(); + } + + /** + * Returns a human-readable version of this offer, useful for debugging + * + * @return human-readable offer + */ + public String toString() { + return "[C:" + commodity + ",$" + price + ",Q:" + quantity + ",S:" + shoppe + "]"; + } + } + + /** + * An offer from a shoppe or stall to buy a certain quantity of a commodity + * for a certain price. If placed in an ordered Set, sorts by commodity index ascending, + * then by buy price descending, and finally by stall id ascending. + */ + class Buy extends Offer implements Comparable { + /** + * Creates a new Buy offer from the given record + * using the other parameters to determine stall id and commodity id of the offer. + * + * @param record the record with data to create the offer from + * @param stallMap a map containing the ids of the various stalls + * @param commodMap a map containing the ids of the various commodities + */ + public Buy(ArrayList record, LinkedHashMap stallMap, HashMap commodMap) { + super(record,stallMap,commodMap,2); + } + + /** + * Sorts by commodity index ascending, then price descending, then stall id ascending. + */ + public int compareTo(Buy buy) { + // organize by: commodity index, price, stall index + if(commodity == buy.commodity) { + // organize by price, then by stall index + if(price == buy.price) { + // organize by stall index + return shoppe>buy.shoppe ? 1 : -1; + } else if(price > buy.price) { + return -1; + } else { + return 1; + } + } else if(commodity > buy.commodity) { + return 1; + } else { + return -1; + } + } + } + + /** + * An offer from a shoppe or stall to sell a certain quantity of a commodity + * for a certain price. If placed in an ordered Set, sorts by commodity index ascending, + * then by sell price ascending, and finally by stall id ascending. + */ + class Sell extends Offer implements Comparable { + /** + * Creates a new Sell offer from the given record + * using the other parameters to determine stall id and commodity id of the offer. + * + * @param record the record with data to create the offer from + * @param stallMap a map containing the ids of the various stalls + * @param commodMap a map containing the ids of the various commodities + */ + public Sell(ArrayList record, LinkedHashMap stallMap, HashMap commodMap) { + super(record,stallMap,commodMap,4); + } + + /** + * Sorts by commodity index ascending, then price ascending, then stall id ascending. + */ + public int compareTo(Sell sell) { + // organize by: commodity index, price, stall index + if(commodity == sell.commodity) { + // organize by price, then by stall index + if(price == sell.price) { + // organize by stall index + return shoppe>sell.shoppe ? 1 : -1; + } else if(price > sell.price) { + return 1; + } else { + return -1; + } + } else if(commodity > sell.commodity) { + return 1; + } else { + return -1; + } + } + } + + /** + * Entry point. Remove modified files and replace with backups. + * Register the jar file we are running from to be deleted upon quit. + * Finally, conditionally set up the GUI. + */ + public MarketUploader() { + // check if we've been turned off in the control panel + Preferences prefs = Preferences.userNodeForPackage(getClass()); + boolean launch = prefs.getBoolean("launchAtStartup", true); + if(!launch) { + return; + } + + if (prefs.getBoolean("useLiveServers", false)) { + YARRG_URL = YARRG_LIVE_URL; + PCTB_HOST_URL = PCTB_LIVE_HOST_URL; + } else { + YARRG_URL = YARRG_TEST_URL; + PCTB_HOST_URL = PCTB_TEST_HOST_URL; + } + + uploadToYarrg=prefs.getBoolean("uploadToYarrg", true); + uploadToPCTB=prefs.getBoolean("uploadToPCTB", true); + + EventQueueMonitor.addTopLevelWindowListener(this); + if (EventQueueMonitor.isGUIInitialized()) { + createGUI(); + } else { + EventQueueMonitor.addGUIInitializedListener(this); + } + } + + /** + * Set up the GUI, with its window and one-button interface. Only initialize + * if we're running alongside a Window named "Puzzle Pirates" though. + */ + private void createGUI() { + if (frame != null && window != null) { + if (window.getAccessibleContext().getAccessibleName().equals("Puzzle Pirates")) frame.setVisible(true); + return; + } + frame = new JFrame("MarketUploader"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.getContentPane().setLayout(new FlowLayout()); + //frame.setPreferredSize(new Dimension(200, 60)); + + findMarket = new JButton("Upload Market Data"); + findMarket.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + findMarket.setEnabled(false); + new Thread() { + public void run() { + try { + runPCTB(); + } catch(Exception e) { + error(e.toString()); + e.printStackTrace(); + } finally { + if(sidePanel != null) { + // remove it if it's still attached + sidePanel.removePropertyChangeListener(changeListener); + } + } + //findMarketTable(); + findMarket.setEnabled(true); + } + }.start(); + } + }); + frame.add(findMarket); + frame.pack(); + } + + /** + * Finds the island name from the /who tab, sets global islandName variable + */ + private void getIsland() { + + // If the league tracker is there, we can skip the faff + // and ask for its tooltip, since we're on a boat + + Accessible leagueTracker = descendNodes(window,new int[] {0,1,0,0,2,1,1,1}); + try { + islandName = ((JLabel)leagueTracker).getToolTipText(); + } catch (NullPointerException e) { + + // evidently we're actually on an island + + islandName = null; + AccessibleContext chatArea = descendNodes(window,new int[] {0,1,0,0,0,2,0,0,2}).getAccessibleContext(); + // attach the property change listener to the outer sunshine panel if the "ahoy" tab + // is not active, otherwise attach it to the scroll panel in the "ahoy" tab. + if(!"com.threerings.piracy.client.AttentionListPanel". + equals(descendNodes(window,new int[] {0,1,0,0,2,2,0}).getClass().getCanonicalName())) { + sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2}).getAccessibleContext(); + } else { + sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2,0,0,0}).getAccessibleContext(); + } + sidePanel.addPropertyChangeListener(changeListener); + latch = new java.util.concurrent.CountDownLatch(1); + // make the Players Online ("/who") panel appear + AccessibleEditableText chat = chatArea.getAccessibleEditableText(); + chat.setTextContents("/w"); + int c = chatArea.getAccessibleAction().getAccessibleActionCount(); + for(int i=0;imsg. + * + * @param msg a String describing the error that occured. + */ + private void error(String msg) { + JOptionPane.showMessageDialog(frame,msg,"Error",JOptionPane.ERROR_MESSAGE); + } + + /** + * Run the data collection process, and upload the results. This is the method + * that calls most of the other worker methods for the process. If an error occurs, + * the method will call the error method and return early, freeing up the button + * to be clicked again. + * + * @exception Exception if an error we didn't expect occured + */ + private void runPCTB() throws Exception { + String yarrgts = ""; + ProgressMonitor pm = new ProgressMonitor(frame,"Processing Market Data","Getting table data",0,100); + pm.setMillisToDecideToPopup(0); + pm.setMillisToPopup(0); + + if (uploadToYarrg) { + yarrgts = getYarrgTimestamp(); + } + + AccessibleTable t = findMarketTable(); + if(t == null) { + error("Market table not found! Please open the Buy/Sell Commodities interface."); + return; + } + if(t.getAccessibleRowCount() == 0) { + error("No data found, please wait for the table to have data first!"); + return; + } + if(!isDisplayAll()) { + error("Please select \"All\" from the Display: popup menu."); + return; + } + + getIsland(); + getOcean(); + + latch.await(2, java.util.concurrent.TimeUnit.SECONDS); + + ArrayList> data = getData(t); + + if (uploadToYarrg) { + pm.setNote("Preparing data for Yarrg"); + pm.setProgress(10); + + StringBuilder yarrgsb = new StringBuilder(); + String yarrgdata; // string containing what we'll feed to yarrg + + for (ArrayList row : data) { + if (row.size() > 6) { + row.remove(6); + } + for (String rowitem : row) { + yarrgsb.append(rowitem != null ? rowitem : ""); + yarrgsb.append("\t"); + } + yarrgsb.setLength(yarrgsb.length()-1); // chop + yarrgsb.append("\n"); + } + + yarrgdata = yarrgsb.toString(); + + pm.setNote("Uploading to Yarrg"); + + if (islandName != null) { + runYarrg(yarrgts, oceanName, islandName, yarrgdata); + } else { + System.out.println("Couldn't upload to Yarrg - no island name found"); + } + } + + pm.setNote("Getting stall names"); + pm.setProgress(20); + if(pm.isCanceled()) { + return; + } + TreeSet buys = new TreeSet(); + TreeSet sells = new TreeSet(); + LinkedHashMap stallMap = getStallMap(data); + pm.setProgress(40); + pm.setNote("Sorting offers"); + if(pm.isCanceled()) { + return; + } + // get commod map + + HashMap commodMap = getCommodMap(); + if(commodMap == null) { + return; + } + int[] offerCount = getBuySellMaps(data,buys,sells,stallMap,commodMap); + //println(buys.toString()); + //System.out.println(sells); + //System.out.println("\n\n\n"+buys); + + if (uploadToPCTB) { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + pm.setProgress(60); + pm.setNote("Sending data"); + if(pm.isCanceled()) { + return; + } + GZIPOutputStream out = new GZIPOutputStream(outStream); + //FileOutputStream out = new FileOutputStream(new File("output.text")); + DataOutputStream dos = new DataOutputStream(out); + dos.writeBytes("005\n"); + dos.writeBytes(stallMap.size()+"\n"); + dos.writeBytes(getAbbrevStallList(stallMap)); + writeBuySellOffers(buys,sells,offerCount,out); + out.finish(); + InputStream in = sendInitialData(new ByteArrayInputStream(outStream.toByteArray())); + pm.setProgress(80); + if(pm.isCanceled()) { + return; + } + pm.setNote("Waiting for PCTB..."); + finishUpload(in); + } + pm.setProgress(100); + } + + /** + * Get the offer data out of the table and cache it in an ArrayList. + * + * @param table the AccessibleTable containing the market data + * @return an array of record arrays, each representing a row of the table + */ + private ArrayList> getData(AccessibleTable table) { + ArrayList> data = new ArrayList>(); + for (int i = 0; i < table.getAccessibleRowCount(); i++) { + ArrayList row = new ArrayList(); + for (int j = 0; j < table.getAccessibleColumnCount(); j++) { + row.add(table.getAccessibleAt(i, j).getAccessibleContext().getAccessibleName()); + } + data.add(row); + } + return data; + } + + /** + * @return the table containing market data if it exists, otherwise null + */ + public AccessibleTable findMarketTable() { + Accessible node1 = window; + Accessible node = descendNodes(node1,new int[] {0,1,0,0,0,0,1,0,0,1,0,0}); // commod market + // commod market: {0,1,0,0,0,0,1,0,0,1,0} {0,1,0,0,0,0,1,0,1,0,0,1,0,0}) + //System.out.println(node); + if (!(node instanceof JTable)) { + node = descendNodes(node1,new int[] {0,1,0,0,0,0,1,0,1,0,0,1,0,0}); // commod market + } + if (!(node instanceof JTable)) return null; + AccessibleTable table = node.getAccessibleContext().getAccessibleTable(); + //System.out.println(table); + return table; + } + + /** + * Utility method to descend through several levels of Accessible children + * at once. + * + * @param parent the node on which to start the descent + * @param path an array of ints, each int being the index of the next + * accessible child to descend. + * @return the Accessible reached by following the descent path, + * or null if the desired path was invalid. + */ + private Accessible descendNodes(Accessible parent, int[] path) { + for(int i=0;iAccessible "node". + * + * @param parent the node with children + * @param childNum the index of the child of parent to return + * @return the childNum child of parent or null + * if the child is not found. + */ + private Accessible descend(Accessible parent, int childNum) { + if (childNum >= parent.getAccessibleContext().getAccessibleChildrenCount()) return null; + return parent.getAccessibleContext().getAccessibleChild(childNum); + } + + public static void main(String[] args) { + new MarketUploader(); + } + + /** + * Set the global window variable after the YPP window is created, + * remove the top level window listener, and start the GUI + */ + public void topLevelWindowCreated(Window w) { + window = w; + EventQueueMonitor.removeTopLevelWindowListener(this); + createGUI(); + } + + /** + * Returns true if the "Display:" menu on the commodities interface in YPP is set to "All" + * + * @return true if all commodities are displayed, otherwise false + */ + private boolean isDisplayAll() { + Accessible button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,0,0,1}); + if(!(button instanceof JButton)) { + button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,1,0,0,0,1}); + } + String display = button.getAccessibleContext().getAccessibleName(); + if(!display.equals("All")) { + return false; + } + return true; + } + + public void topLevelWindowDestroyed(Window w) {} + + public void guiInitialized() { + createGUI(); + } + + /** + * Gets the list of commodities and their associated commodity ids. + * On the first run, the data is downloaded from the PCTB server. + * After the first run, the data is cached using Preferences. + *

+ * Potential issues: When more commodities are added to the server, this + * program will currently break unless the user deletes the preferences + * file or we give them a new release with a slighly different storage + * location for the data. + * + * @return a map where the key is the commodity and the value is the commodity id. + */ + private HashMap getCommodMap() { + if(commodMap != null) { + return commodMap; + } + HashMap map = new HashMap(); + Preferences prefs = Preferences.userNodeForPackage(getClass()); + String xml; + try { + URL host = new URL(PCTB_HOST_URL + "commodmap.php"); + BufferedReader br = new BufferedReader(new InputStreamReader(host.openStream())); + StringBuilder sb = new StringBuilder(); + String str; + while((str = br.readLine()) != null) { + sb.append(str); + } + int first = sb.indexOf("

") + 5;
+			int last = sb.indexOf("");
+			xml = sb.substring(first,last);
+			//System.out.println(xml);
+			Reader reader = new CharArrayReader(xml.toCharArray());
+			Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(reader));
+			NodeList maps = d.getElementsByTagName("c");
+			for(int i=0;iLinkedHashMap where the key is the stall name
+	*	and the value is the generated stall id (position in the list).
+	*	

+ * The reason this method returns a LinkedHashMap instead of a simple HashMap is the need + * for iterating over the stall names in insertion order for output to the server. + * + * @param offers the list of records from the commodity buy/sell interface + * @return an iterable ordered map of the stall names and generated stall ids + */ + private LinkedHashMap getStallMap(ArrayList> offers) { + int count = 0; + LinkedHashMap map = new LinkedHashMap(); + for(ArrayList offer : offers) { + String shop = offer.get(1); + if(!map.containsKey(shop)) { + count++; + map.put(shop,count); + } + } + return map; + } + + /** + * Gets a sorted list of Buys and Sells from the list of records. buys and sells + * should be pre-initialized and passed into the method to receive the data. + * Returns a 2-length int array with the number of buys and sells found. + * + * @param offers the data found from the market table in-game + * @param buys an empty initialized TreeSet<Offer> to + * hold the Buy offers. + * @param sells an empty initialized TreeSet<Offer> to + * hold the Sell offers. + * @param stalls the map of stalls to their ids + * @param commodMap the map of commodities to their ids + * @return a 2-length int[] array containing the number of buys and sells, respectively + */ + private int[] getBuySellMaps(ArrayList> offers, TreeSet buys, + TreeSet sells, LinkedHashMap stalls, HashMap commodMap) { + int[] buySellCount = new int[2]; + for(ArrayList offer : offers) { + try { + if(offer.get(2) != null) { + buys.add(new Buy(offer,stalls,commodMap)); + buySellCount[0]++; + } + if(offer.get(4) != null) { + sells.add(new Sell(offer,stalls,commodMap)); + buySellCount[1]++; + } + } catch(IllegalArgumentException e) { + // System.err.println("Error: Unsupported Commodity \"" + offer.get(0) + "\""); + } + } + return buySellCount; + } + + /** + * Prepares the list of stalls for writing to the output stream. + * The String returned by this method is ready to be written + * directly to the stream. + *

+ * All shoppe names are left as they are. Stall names are abbreviated just before the + * apostrophe in the possessive, with an "^" and a letter matching the stall's type + * appended. Example: "Burninator's Ironworking Stall" would become "Burninator^I". + * + * @param stallMap the map of stalls and stall ids in an iterable order + * @return a String containing the list of stalls in format ready + * to be written to the output stream. + */ + private String getAbbrevStallList(LinkedHashMap stallMap) { + // set up some mapping + HashMap types = new HashMap(); + types.put("Apothecary Stall", "A"); + types.put("Distilling Stall", "D"); + types.put("Furnishing Stall", "F"); + types.put("Ironworking Stall", "I"); + types.put("Shipbuilding Stall", "S"); + types.put("Tailoring Stall", "T"); + types.put("Weaving Stall", "W"); + + StringBuilder sb = new StringBuilder(); + for(String name : stallMap.keySet()) { + int index = name.indexOf("'s"); + String finalName = name; + String type = null; + if (index > 0) { + finalName = name.substring(0,index); + if(index + 2 < name.length()) { + String end = name.substring(index+2,name.length()).trim(); + type = types.get(end); + } + } + if(type==null) { + sb.append(name+"\n"); + } else { + sb.append(finalName+"^"+type+"\n"); + } + } + return sb.toString(); + } + + /** + * Writes a list of offers in correct format to the output stream. + *

+ * The format is thus: (all numbers are 2-byte integers in little-endian format) + * (number of offers of this type, aka buy/sell) + * (commodity ID) (number of offers for this commodity) [shopID price qty][shopID price qty]... + * + * @param out the output stream to write the data to + * @param offers the offers to write + */ + private void writeOffers(OutputStream out, TreeSet offers) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + if(offers.size() == 0) { + // nothing to write, and "0" has already been written + return; + } + int commodity = offers.first().commodity; + int count = 0; + for(Offer offer : offers) { + if(commodity != offer.commodity) { + // write out buffer + writeBufferedOffers(out,buffer.toByteArray(),commodity,count); + buffer.reset(); + commodity = offer.commodity; + count = 0; + } + writeLEShort(offer.shoppe,buffer); // stall index + writeLEShort(offer.price,buffer); // buy price + writeLEShort(offer.quantity,buffer); // buy qty + count++; + } + writeBufferedOffers(out,buffer.toByteArray(),commodity,count); + } + + /** + * Writes the buffered data to the output strea for one commodity. + * + * @param out the stream to write to + * @param buffer the buffered data to write + * @param commodity the commmodity id to write before the buffered data + * @param count the number of offers for this commodity to write before the data + */ + private void writeBufferedOffers(OutputStream out, byte[] buffer, int commodity, int count) throws IOException { + writeLEShort(commodity,out); // commod index + writeLEShort(count,out); // offer count + out.write(buffer); // the buffered offers + } + + /** + * Writes the buy and sell offers to the outputstream by calling other methods. + * + * @param buys list of Buy offers to write + * @param sells list of Sell offers to write + * @param offerCount 2-length int array containing the number of buys and sells to write out + * @param out the stream to write to + */ + private void writeBuySellOffers(TreeSet buys, + TreeSet sells, int[] offerCount, OutputStream out) throws IOException { + // # buy offers + writeLEShort(offerCount[0],out); + writeOffers(out,buys); + // # sell offers + writeLEShort(offerCount[1],out); + writeOffers(out,sells); + } + + /** + * Sends the data to the server via multipart-formdata POST, + * with the gzipped data as a file upload. + * + * @param file an InputStream open to the gzipped data we want to send + */ + private InputStream sendInitialData(InputStream file) throws IOException { + ClientHttpRequest http = new ClientHttpRequest(PCTB_HOST_URL + "upload.php"); + http.setParameter("marketdata","marketdata.gz",file,"application/gzip"); + return http.post(); + } + + /** + * Utility method to write a 2-byte int in little-endian form to an output stream. + * + * @param num an integer to write + * @param out stream to write to + */ + private void writeLEShort(int num, OutputStream out) throws IOException { + out.write(num & 0xFF); + out.write((num >>> 8) & 0xFF); + } + + /** + * Reads the response from the server, and selects the correct parameters + * which are sent in a GET request to the server asking it to confirm + * the upload and accept the data into the database. Notably, the island id + * and ocean id are determined, while other parameter such as the filename + * are determined from the hidden form fields. + * + * @param in stream of data from the server to read + */ + private void finishUpload(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + String str; + while((str = br.readLine()) != null) { + sb.append(str+"\n"); + } + String html = sb.toString(); + //System.out.println(html); + String topIsland = "0", ocean, islandNum, action, forceReload, filename; + Matcher m; + Pattern whoIsland = Pattern.compile("