updates for 0.9.4
[debian/openrocket] / src / net / sf / openrocket / communication / Communication.java
diff --git a/src/net/sf/openrocket/communication/Communication.java b/src/net/sf/openrocket/communication/Communication.java
new file mode 100644 (file)
index 0000000..df3b0ac
--- /dev/null
@@ -0,0 +1,262 @@
+package net.sf.openrocket.communication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.Prefs;
+
+public class Communication {
+
+       private static final String BUG_REPORT_URL = 
+               "http://openrocket.sourceforge.net/actions/reportbug";
+       private static final String UPDATE_INFO_URL =
+               "http://openrocket.sourceforge.net/actions/updates";
+
+       private static final String VERSION_PARAM = "version";
+       
+       
+       private static final String BUG_REPORT_PARAM = "content";
+       private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
+       private static final int CONNECTION_TIMEOUT = 10000;  // in milliseconds
+
+       private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
+       private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
+       private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
+
+       
+       private static UpdateInfoFetcher fetcher = null;
+       
+
+       /**
+        * Send the provided report to the OpenRocket bug report URL.  If the connection
+        * fails or the server does not respond with the correct response code, an
+        * exception is thrown.
+        * 
+        * @param report                the report to send.
+        * @throws IOException  if an error occurs while connecting to the server or
+        *                                              the server responds with a wrong response code.
+        */
+       public static void sendBugReport(String report) throws IOException {
+               URL url = new URL(BUG_REPORT_URL);
+               
+               HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+               
+               connection.setConnectTimeout(CONNECTION_TIMEOUT);
+               connection.setInstanceFollowRedirects(true);
+               connection.setRequestMethod("POST");
+               connection.setUseCaches(false);
+               connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+               
+               String post;
+               post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
+                               + "&" + BUG_REPORT_PARAM + "=" + encode(report));
+               
+               OutputStreamWriter wr = null;
+               try {
+                       // Send post information
+                       connection.setDoOutput(true);
+                       wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
+                       wr.write(post);
+                       wr.flush();
+                       
+                       if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
+                               throw new IOException("Server responded with code " + 
+                                               connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
+                       }
+               } finally {
+                       if (wr != null)
+                               wr.close();
+                       connection.disconnect();
+               }
+       }
+       
+       
+       
+       /**
+        * Start an asynchronous task that will fetch information about the latest
+        * OpenRocket version.  This will overwrite any previous fetching operation.
+        */
+       public static void startFetchUpdateInfo() {
+               fetcher = new UpdateInfoFetcher();
+               fetcher.start();
+       }
+       
+       
+       /**
+        * Check whether the update info fetching is still in progress.
+        * 
+        * @return      <code>true</code> if the communication is still in progress.
+        */
+       public static boolean isFetchUpdateInfoRunning() {
+               if (fetcher == null) {
+                       throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+               }
+               return fetcher.isAlive();
+       }
+       
+       
+       /**
+        * Retrieve the result of the background update info fetcher.  This method returns 
+        * the result of the previous call to {@link #startFetchUpdateInfo()}. It must be
+        * called before calling this method.
+        * <p>
+        * This method will return <code>null</code> if the info fetcher is still running or
+        * if it encountered a problem in communicating with the server.  The difference can
+        * be checked using {@link #isFetchUpdateInfoRunning()}.
+        * 
+        * @return      the update result, or <code>null</code> if the fetching is still in progress
+        *                      or an error occurred while communicating with the server.
+        * @throws      IllegalStateException   if {@link #startFetchUpdateInfo()} has not been called.
+        */
+       public static UpdateInfo getUpdateInfo() {
+               if (fetcher == null) {
+                       throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+               }
+               return fetcher.info;
+       }
+       
+       
+       
+       /**
+        * Parse the data received from the server.
+        * 
+        * @param r             the Reader from which to read.
+        * @return              an UpdateInfo construct, or <code>null</code> if the data was invalid.
+        * @throws IOException  if an I/O exception occurs.
+        */
+       /* package-private */
+       static UpdateInfo parseUpdateInput(Reader r) throws IOException {
+               BufferedReader reader;
+               if (r instanceof BufferedReader) {
+                       reader = (BufferedReader)r;
+               } else {
+                       reader = new BufferedReader(r);
+               }
+               
+               
+               String version = null;
+               ArrayList<ComparablePair<Integer,String>> updates = 
+                       new ArrayList<ComparablePair<Integer,String>>();
+               
+               String str = reader.readLine();
+               while (str != null) {
+                       if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
+                               version = str.substring(8).trim();
+                       } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
+                               int index = str.indexOf(':');
+                               int value = Integer.parseInt(str.substring(0, index));
+                               String desc = str.substring(index+1).trim();
+                               if (!desc.equals("")) {
+                                       updates.add(new ComparablePair<Integer,String>(value, desc));
+                               }
+                       }
+                       // Ignore anything else
+                       str = reader.readLine();
+               }
+               
+               if (version != null) {
+                       return new UpdateInfo(version, updates);
+               } else {
+                       return null;
+               }
+       }
+       
+       
+       
+       
+       private static class UpdateInfoFetcher extends Thread {
+
+               private volatile UpdateInfo info = null;
+               
+               @Override
+               public void run() {
+                       try {
+                               doConnection();
+                       } catch (IOException e) {
+                               return;
+                       }
+               }
+               
+               
+               private void doConnection() throws IOException {
+                       URL url;
+                       url = new URL(UPDATE_INFO_URL + "?" + VERSION_PARAM + "=" + 
+                                       encode(Prefs.getVersion()));
+                       
+                       HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                       
+                       connection.setConnectTimeout(CONNECTION_TIMEOUT);
+                       connection.setInstanceFollowRedirects(true);
+                       connection.setRequestMethod("GET");
+                       connection.setUseCaches(false);
+                       connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+                       connection.setRequestProperty("X-OpenRocket-ID", encode(Prefs.getUniqueID()));
+                       connection.setRequestProperty("X-OpenRocket-OS", encode(
+                                       System.getProperty("os.name") + " " + System.getProperty("os.arch")));
+                       connection.setRequestProperty("X-OpenRocket-Java", encode(
+                                       System.getProperty("java.vendor") + " " + System.getProperty("java.version")));
+                       connection.setRequestProperty("X-OpenRocket-Country", encode(
+                                       System.getProperty("user.country")));
+                       
+                       InputStream is = null;
+                       try {
+                               connection.connect();
+                               
+                               if (connection.getResponseCode() == UPDATE_INFO_NO_UPDATE_CODE) {
+                                       // No updates are available
+                                       info = new UpdateInfo();
+                                       return;
+                               }
+                               
+                               if (connection.getResponseCode() != UPDATE_INFO_UPDATE_AVAILABLE) {
+                                       // Error communicating with server
+                                       return;
+                               }
+                               
+                               if (!UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(connection.getContentType())) {
+                                       // Unknown response type
+                                       return;
+                               }
+                               
+                               // Update is available, parse input
+                               is = connection.getInputStream();
+                               String encoding = connection.getContentEncoding();
+                               if (encoding == null)
+                                       encoding = "UTF-8";
+                               BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
+                               
+                               
+
+                       } finally {
+                               if (is != null)
+                                       is.close();
+                               connection.disconnect();
+                       }
+
+                       
+               }
+               
+       }
+       
+       
+       private static String encode(String str) {
+               if (str == null)
+                       return "null";
+               try {
+                       return URLEncoder.encode(str, "UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       throw new RuntimeException("Unsupported encoding UTF-8", e);
+               }
+       }
+
+}