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;
10 import java.util.Locale;
12 import net.sf.openrocket.logging.LogHelper;
13 import net.sf.openrocket.startup.Application;
14 import net.sf.openrocket.util.BuildProperties;
15 import net.sf.openrocket.util.ComparablePair;
16 import net.sf.openrocket.util.LimitedInputStream;
18 public class UpdateInfoRetriever {
19 private static final LogHelper log = Application.getLogger();
21 private UpdateInfoFetcher fetcher = null;
25 * Start an asynchronous task that will fetch information about the latest
26 * OpenRocket version. This will overwrite any previous fetching operation.
27 * This call will return immediately.
30 fetcher = new UpdateInfoFetcher();
31 fetcher.setName("UpdateInfoFetcher");
32 fetcher.setDaemon(true);
38 * Check whether the update info fetching is still in progress.
40 * @return <code>true</code> if the communication is still in progress.
42 public boolean isRunning() {
43 if (fetcher == null) {
44 throw new IllegalStateException("startFetchUpdateInfo() has not been called");
46 return fetcher.isAlive();
51 * Retrieve the result of the background update info fetcher. This method returns
52 * the result of the previous call to {@link #start()}. It must be
53 * called before calling this method.
55 * This method will return <code>null</code> if the info fetcher is still running or
56 * if it encountered a problem in communicating with the server. The difference can
57 * be checked using {@link #isRunning()}.
59 * @return the update result, or <code>null</code> if the fetching is still in progress
60 * or an error occurred while communicating with the server.
61 * @throws IllegalStateException if {@link #start()} has not been called.
63 public UpdateInfo getUpdateInfo() {
64 if (fetcher == null) {
65 throw new IllegalStateException("start() has not been called");
73 * Parse the data received from the server.
75 * @param r the Reader from which to read.
76 * @return an UpdateInfo construct, or <code>null</code> if the data was invalid.
77 * @throws IOException if an I/O exception occurs.
80 static UpdateInfo parseUpdateInput(Reader r) throws IOException {
81 BufferedReader reader;
82 if (r instanceof BufferedReader) {
83 reader = (BufferedReader) r;
85 reader = new BufferedReader(r);
89 String version = null;
90 ArrayList<ComparablePair<Integer, String>> updates =
91 new ArrayList<ComparablePair<Integer, String>>();
93 String str = reader.readLine();
95 if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
96 version = str.substring(8).trim();
97 } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
98 int index = str.indexOf(':');
99 int value = Integer.parseInt(str.substring(0, index));
100 String desc = str.substring(index + 1).trim();
101 if (!desc.equals("")) {
102 updates.add(new ComparablePair<Integer, String>(value, desc));
105 // Ignore anything else
106 str = reader.readLine();
109 if (version != null) {
110 return new UpdateInfo(version, updates);
119 * An asynchronous task that fetches and parses the update info.
121 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
123 private class UpdateInfoFetcher extends Thread {
125 private volatile UpdateInfo info = null;
131 } catch (IOException e) {
132 log.info("Fetching update failed: " + e);
138 private void doConnection() throws IOException {
139 String url = Communicator.UPDATE_INFO_URL + "?" + Communicator.VERSION_PARAM + "="
140 + Communicator.encode(BuildProperties.getVersion());
142 HttpURLConnection connection = Communicator.connectionSource.getConnection(url);
144 connection.setConnectTimeout(Communicator.CONNECTION_TIMEOUT);
145 connection.setInstanceFollowRedirects(true);
146 connection.setRequestMethod("GET");
147 connection.setUseCaches(false);
148 connection.setDoInput(true);
149 connection.setRequestProperty("X-OpenRocket-Version",
150 Communicator.encode(BuildProperties.getVersion() + " " + BuildProperties.getBuildSource()));
151 connection.setRequestProperty("X-OpenRocket-ID",
152 Communicator.encode(Application.getPreferences().getUniqueID()));
153 connection.setRequestProperty("X-OpenRocket-OS",
154 Communicator.encode(System.getProperty("os.name") + " " +
155 System.getProperty("os.arch")));
156 connection.setRequestProperty("X-OpenRocket-Java",
157 Communicator.encode(System.getProperty("java.vendor") + " " +
158 System.getProperty("java.version")));
159 connection.setRequestProperty("X-OpenRocket-Country",
160 Communicator.encode(System.getProperty("user.country") + " " +
161 System.getProperty("user.timezone")));
162 connection.setRequestProperty("X-OpenRocket-Locale",
163 Communicator.encode(Locale.getDefault().toString()));
164 connection.setRequestProperty("X-OpenRocket-CPUs", "" + Runtime.getRuntime().availableProcessors());
166 InputStream is = null;
168 connection.connect();
170 log.debug("Update response code: " + connection.getResponseCode());
172 if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
173 // No updates are available
174 log.info("No updates available");
175 info = new UpdateInfo();
179 if (connection.getResponseCode() != Communicator.UPDATE_INFO_UPDATE_AVAILABLE) {
180 // Error communicating with server
181 log.warn("Unknown server response code: " + connection.getResponseCode());
185 String contentType = connection.getContentType();
186 if (contentType == null ||
187 contentType.toLowerCase(Locale.ENGLISH).indexOf(Communicator.UPDATE_INFO_CONTENT_TYPE) < 0) {
188 // Unknown response type
189 log.warn("Unknown Content-type received:" + contentType);
193 // Update is available, parse input
194 is = connection.getInputStream();
195 is = new LimitedInputStream(is, Communicator.MAX_INPUT_BYTES);
196 String encoding = connection.getContentEncoding();
197 if (encoding == null || encoding.equals(""))
199 BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
201 String version = null;
202 ArrayList<ComparablePair<Integer, String>> updates =
203 new ArrayList<ComparablePair<Integer, String>>();
205 String line = reader.readLine();
206 while (line != null) {
208 if (line.matches("^Version:[a-zA-Z0-9._ -]{1,30}$")) {
209 version = line.substring(8).trim();
210 } else if (line.matches("^[0-9]{1,9}:\\P{Cntrl}{1,300}$")) {
211 String[] split = line.split(":", 2);
212 int n = Integer.parseInt(split[0]);
213 updates.add(new ComparablePair<Integer, String>(n, split[1].trim()));
215 // Ignore line otherwise
216 line = reader.readLine();
219 // Check version input
220 if (version == null || version.length() == 0 ||
221 version.equalsIgnoreCase(BuildProperties.getVersion())) {
223 log.warn("Invalid version received, ignoring.");
228 info = new UpdateInfo(version, updates);
229 log.info("Found update: " + info);
234 connection.disconnect();
235 } catch (Exception e) {