updates for 0.9.4
[debian/openrocket] / src / net / sf / openrocket / communication / Communication.java
1 package net.sf.openrocket.communication;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.io.OutputStreamWriter;
8 import java.io.Reader;
9 import java.io.UnsupportedEncodingException;
10 import java.net.HttpURLConnection;
11 import java.net.URL;
12 import java.net.URLEncoder;
13 import java.util.ArrayList;
14
15 import net.sf.openrocket.util.ComparablePair;
16 import net.sf.openrocket.util.Prefs;
17
18 public class Communication {
19
20         private static final String BUG_REPORT_URL = 
21                 "http://openrocket.sourceforge.net/actions/reportbug";
22         private static final String UPDATE_INFO_URL =
23                 "http://openrocket.sourceforge.net/actions/updates";
24
25         private static final String VERSION_PARAM = "version";
26         
27         
28         private static final String BUG_REPORT_PARAM = "content";
29         private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
30         private static final int CONNECTION_TIMEOUT = 10000;  // in milliseconds
31
32         private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
33         private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
34         private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
35
36         
37         private static UpdateInfoFetcher fetcher = null;
38         
39
40         /**
41          * Send the provided report to the OpenRocket bug report URL.  If the connection
42          * fails or the server does not respond with the correct response code, an
43          * exception is thrown.
44          * 
45          * @param report                the report to send.
46          * @throws IOException  if an error occurs while connecting to the server or
47          *                                              the server responds with a wrong response code.
48          */
49         public static void sendBugReport(String report) throws IOException {
50                 URL url = new URL(BUG_REPORT_URL);
51                 
52                 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
53                 
54                 connection.setConnectTimeout(CONNECTION_TIMEOUT);
55                 connection.setInstanceFollowRedirects(true);
56                 connection.setRequestMethod("POST");
57                 connection.setUseCaches(false);
58                 connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
59                 
60                 String post;
61                 post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
62                                 + "&" + BUG_REPORT_PARAM + "=" + encode(report));
63                 
64                 OutputStreamWriter wr = null;
65                 try {
66                         // Send post information
67                         connection.setDoOutput(true);
68                         wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
69                         wr.write(post);
70                         wr.flush();
71                         
72                         if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
73                                 throw new IOException("Server responded with code " + 
74                                                 connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
75                         }
76                 } finally {
77                         if (wr != null)
78                                 wr.close();
79                         connection.disconnect();
80                 }
81         }
82         
83         
84         
85         /**
86          * Start an asynchronous task that will fetch information about the latest
87          * OpenRocket version.  This will overwrite any previous fetching operation.
88          */
89         public static void startFetchUpdateInfo() {
90                 fetcher = new UpdateInfoFetcher();
91                 fetcher.start();
92         }
93         
94         
95         /**
96          * Check whether the update info fetching is still in progress.
97          * 
98          * @return      <code>true</code> if the communication is still in progress.
99          */
100         public static boolean isFetchUpdateInfoRunning() {
101                 if (fetcher == null) {
102                         throw new IllegalStateException("startFetchUpdateInfo() has not been called");
103                 }
104                 return fetcher.isAlive();
105         }
106         
107         
108         /**
109          * Retrieve the result of the background update info fetcher.  This method returns 
110          * the result of the previous call to {@link #startFetchUpdateInfo()}. It must be
111          * called before calling this method.
112          * <p>
113          * This method will return <code>null</code> if the info fetcher is still running or
114          * if it encountered a problem in communicating with the server.  The difference can
115          * be checked using {@link #isFetchUpdateInfoRunning()}.
116          * 
117          * @return      the update result, or <code>null</code> if the fetching is still in progress
118          *                      or an error occurred while communicating with the server.
119          * @throws      IllegalStateException   if {@link #startFetchUpdateInfo()} has not been called.
120          */
121         public static UpdateInfo getUpdateInfo() {
122                 if (fetcher == null) {
123                         throw new IllegalStateException("startFetchUpdateInfo() has not been called");
124                 }
125                 return fetcher.info;
126         }
127         
128         
129         
130         /**
131          * Parse the data received from the server.
132          * 
133          * @param r             the Reader from which to read.
134          * @return              an UpdateInfo construct, or <code>null</code> if the data was invalid.
135          * @throws IOException  if an I/O exception occurs.
136          */
137         /* package-private */
138         static UpdateInfo parseUpdateInput(Reader r) throws IOException {
139                 BufferedReader reader;
140                 if (r instanceof BufferedReader) {
141                         reader = (BufferedReader)r;
142                 } else {
143                         reader = new BufferedReader(r);
144                 }
145                 
146                 
147                 String version = null;
148                 ArrayList<ComparablePair<Integer,String>> updates = 
149                         new ArrayList<ComparablePair<Integer,String>>();
150                 
151                 String str = reader.readLine();
152                 while (str != null) {
153                         if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
154                                 version = str.substring(8).trim();
155                         } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
156                                 int index = str.indexOf(':');
157                                 int value = Integer.parseInt(str.substring(0, index));
158                                 String desc = str.substring(index+1).trim();
159                                 if (!desc.equals("")) {
160                                         updates.add(new ComparablePair<Integer,String>(value, desc));
161                                 }
162                         }
163                         // Ignore anything else
164                         str = reader.readLine();
165                 }
166                 
167                 if (version != null) {
168                         return new UpdateInfo(version, updates);
169                 } else {
170                         return null;
171                 }
172         }
173         
174         
175         
176         
177         private static class UpdateInfoFetcher extends Thread {
178
179                 private volatile UpdateInfo info = null;
180                 
181                 @Override
182                 public void run() {
183                         try {
184                                 doConnection();
185                         } catch (IOException e) {
186                                 return;
187                         }
188                 }
189                 
190                 
191                 private void doConnection() throws IOException {
192                         URL url;
193                         url = new URL(UPDATE_INFO_URL + "?" + VERSION_PARAM + "=" + 
194                                         encode(Prefs.getVersion()));
195                         
196                         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
197                         
198                         connection.setConnectTimeout(CONNECTION_TIMEOUT);
199                         connection.setInstanceFollowRedirects(true);
200                         connection.setRequestMethod("GET");
201                         connection.setUseCaches(false);
202                         connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
203                         connection.setRequestProperty("X-OpenRocket-ID", encode(Prefs.getUniqueID()));
204                         connection.setRequestProperty("X-OpenRocket-OS", encode(
205                                         System.getProperty("os.name") + " " + System.getProperty("os.arch")));
206                         connection.setRequestProperty("X-OpenRocket-Java", encode(
207                                         System.getProperty("java.vendor") + " " + System.getProperty("java.version")));
208                         connection.setRequestProperty("X-OpenRocket-Country", encode(
209                                         System.getProperty("user.country")));
210                         
211                         InputStream is = null;
212                         try {
213                                 connection.connect();
214                                 
215                                 if (connection.getResponseCode() == UPDATE_INFO_NO_UPDATE_CODE) {
216                                         // No updates are available
217                                         info = new UpdateInfo();
218                                         return;
219                                 }
220                                 
221                                 if (connection.getResponseCode() != UPDATE_INFO_UPDATE_AVAILABLE) {
222                                         // Error communicating with server
223                                         return;
224                                 }
225                                 
226                                 if (!UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(connection.getContentType())) {
227                                         // Unknown response type
228                                         return;
229                                 }
230                                 
231                                 // Update is available, parse input
232                                 is = connection.getInputStream();
233                                 String encoding = connection.getContentEncoding();
234                                 if (encoding == null)
235                                         encoding = "UTF-8";
236                                 BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
237                                 
238                                 
239
240                         } finally {
241                                 if (is != null)
242                                         is.close();
243                                 connection.disconnect();
244                         }
245
246                         
247                 }
248                 
249         }
250         
251         
252         private static String encode(String str) {
253                 if (str == null)
254                         return "null";
255                 try {
256                         return URLEncoder.encode(str, "UTF-8");
257                 } catch (UnsupportedEncodingException e) {
258                         throw new RuntimeException("Unsupported encoding UTF-8", e);
259                 }
260         }
261
262 }