1 package net.sf.openrocket.communication;
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
8 import java.net.HttpURLConnection;
9 import java.util.ArrayList;
11 import net.sf.openrocket.logging.LogHelper;
12 import net.sf.openrocket.startup.Application;
13 import net.sf.openrocket.util.ComparablePair;
14 import net.sf.openrocket.util.LimitedInputStream;
15 import net.sf.openrocket.util.Prefs;
17 public class UpdateInfoRetriever {
18 private static final LogHelper log = Application.getLogger();
20 private UpdateInfoFetcher fetcher = null;
24 * Start an asynchronous task that will fetch information about the latest
25 * OpenRocket version. This will overwrite any previous fetching operation.
26 * This call will return immediately.
29 fetcher = new UpdateInfoFetcher();
30 fetcher.setDaemon(true);
36 * Check whether the update info fetching is still in progress.
38 * @return <code>true</code> if the communication is still in progress.
40 public boolean isRunning() {
41 if (fetcher == null) {
42 throw new IllegalStateException("startFetchUpdateInfo() has not been called");
44 return fetcher.isAlive();
49 * Retrieve the result of the background update info fetcher. This method returns
50 * the result of the previous call to {@link #start()}. It must be
51 * called before calling this method.
53 * This method will return <code>null</code> if the info fetcher is still running or
54 * if it encountered a problem in communicating with the server. The difference can
55 * be checked using {@link #isRunning()}.
57 * @return the update result, or <code>null</code> if the fetching is still in progress
58 * or an error occurred while communicating with the server.
59 * @throws IllegalStateException if {@link #start()} has not been called.
61 public UpdateInfo getUpdateInfo() {
62 if (fetcher == null) {
63 throw new IllegalStateException("start() has not been called");
71 * Parse the data received from the server.
73 * @param r the Reader from which to read.
74 * @return an UpdateInfo construct, or <code>null</code> if the data was invalid.
75 * @throws IOException if an I/O exception occurs.
78 static UpdateInfo parseUpdateInput(Reader r) throws IOException {
79 BufferedReader reader;
80 if (r instanceof BufferedReader) {
81 reader = (BufferedReader) r;
83 reader = new BufferedReader(r);
87 String version = null;
88 ArrayList<ComparablePair<Integer, String>> updates =
89 new ArrayList<ComparablePair<Integer, String>>();
91 String str = reader.readLine();
93 if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
94 version = str.substring(8).trim();
95 } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
96 int index = str.indexOf(':');
97 int value = Integer.parseInt(str.substring(0, index));
98 String desc = str.substring(index + 1).trim();
99 if (!desc.equals("")) {
100 updates.add(new ComparablePair<Integer, String>(value, desc));
103 // Ignore anything else
104 str = reader.readLine();
107 if (version != null) {
108 return new UpdateInfo(version, updates);
117 * An asynchronous task that fetches and parses the update info.
119 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
121 private class UpdateInfoFetcher extends Thread {
123 private volatile UpdateInfo info = null;
129 } catch (IOException e) {
130 log.info("Fetching update failed: " + e);
136 private void doConnection() throws IOException {
137 String url = Communicator.UPDATE_INFO_URL + "?" + Communicator.VERSION_PARAM + "="
138 + Communicator.encode(Prefs.getVersion());
140 HttpURLConnection connection = Communicator.connectionSource.getConnection(url);
142 connection.setConnectTimeout(Communicator.CONNECTION_TIMEOUT);
143 connection.setInstanceFollowRedirects(true);
144 connection.setRequestMethod("GET");
145 connection.setUseCaches(false);
146 connection.setDoInput(true);
147 connection.setRequestProperty("X-OpenRocket-Version",
148 Communicator.encode(Prefs.getVersion() + " " + Prefs.getBuildSource()));
149 connection.setRequestProperty("X-OpenRocket-ID",
150 Communicator.encode(Prefs.getUniqueID()));
151 connection.setRequestProperty("X-OpenRocket-OS",
152 Communicator.encode(System.getProperty("os.name") + " " +
153 System.getProperty("os.arch")));
154 connection.setRequestProperty("X-OpenRocket-Java",
155 Communicator.encode(System.getProperty("java.vendor") + " " +
156 System.getProperty("java.version")));
157 connection.setRequestProperty("X-OpenRocket-Country",
158 Communicator.encode(System.getProperty("user.country") + " " +
159 System.getProperty("user.timezone")));
160 connection.setRequestProperty("X-OpenRocket-CPUs", "" + Runtime.getRuntime().availableProcessors());
162 InputStream is = null;
164 connection.connect();
166 log.debug("Update response code: " + connection.getResponseCode());
168 if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
169 // No updates are available
170 log.info("No updates available");
171 info = new UpdateInfo();
175 if (connection.getResponseCode() != Communicator.UPDATE_INFO_UPDATE_AVAILABLE) {
176 // Error communicating with server
177 log.warn("Unknown server response code: " + connection.getResponseCode());
181 String contentType = connection.getContentType();
182 if (contentType == null ||
183 contentType.toLowerCase().indexOf(Communicator.UPDATE_INFO_CONTENT_TYPE) < 0) {
184 // Unknown response type
185 log.warn("Unknown Content-type received:" + contentType);
189 // Update is available, parse input
190 is = connection.getInputStream();
191 is = new LimitedInputStream(is, Communicator.MAX_INPUT_BYTES);
192 String encoding = connection.getContentEncoding();
193 if (encoding == null || encoding.equals(""))
195 BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
197 String version = null;
198 ArrayList<ComparablePair<Integer, String>> updates =
199 new ArrayList<ComparablePair<Integer, String>>();
201 String line = reader.readLine();
202 while (line != null) {
204 if (line.matches("^Version:[a-zA-Z0-9._ -]{1,30}$")) {
205 version = line.substring(8).trim();
206 } else if (line.matches("^[0-9]{1,9}:\\P{Cntrl}{1,300}$")) {
207 String[] split = line.split(":", 2);
208 int n = Integer.parseInt(split[0]);
209 updates.add(new ComparablePair<Integer, String>(n, split[1].trim()));
211 // Ignore line otherwise
212 line = reader.readLine();
215 // Check version input
216 if (version == null || version.length() == 0 ||
217 version.equalsIgnoreCase(Prefs.getVersion())) {
219 log.warn("Invalid version received, ignoring.");
224 info = new UpdateInfo(version, updates);
225 log.info("Found update: " + info);
230 connection.disconnect();
231 } catch (Exception e) {