017f6add3fa1272a7697ff2a15c372a3560a8723
[debian/openrocket] / core / src / net / sf / openrocket / communication / UpdateInfoRetriever.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.Reader;
8 import java.net.HttpURLConnection;
9 import java.util.ArrayList;
10 import java.util.Locale;
11
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;
17
18 public class UpdateInfoRetriever {
19         private static final LogHelper log = Application.getLogger();
20         
21         private UpdateInfoFetcher fetcher = null;
22         
23         
24         /**
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.
28          */
29         public void start() {
30                 fetcher = new UpdateInfoFetcher();
31                 fetcher.setDaemon(true);
32                 fetcher.start();
33         }
34         
35         
36         /**
37          * Check whether the update info fetching is still in progress.
38          * 
39          * @return      <code>true</code> if the communication is still in progress.
40          */
41         public boolean isRunning() {
42                 if (fetcher == null) {
43                         throw new IllegalStateException("startFetchUpdateInfo() has not been called");
44                 }
45                 return fetcher.isAlive();
46         }
47         
48         
49         /**
50          * Retrieve the result of the background update info fetcher.  This method returns 
51          * the result of the previous call to {@link #start()}. It must be
52          * called before calling this method.
53          * <p>
54          * This method will return <code>null</code> if the info fetcher is still running or
55          * if it encountered a problem in communicating with the server.  The difference can
56          * be checked using {@link #isRunning()}.
57          * 
58          * @return      the update result, or <code>null</code> if the fetching is still in progress
59          *                      or an error occurred while communicating with the server.
60          * @throws      IllegalStateException   if {@link #start()} has not been called.
61          */
62         public UpdateInfo getUpdateInfo() {
63                 if (fetcher == null) {
64                         throw new IllegalStateException("start() has not been called");
65                 }
66                 return fetcher.info;
67         }
68         
69         
70         
71         /**
72          * Parse the data received from the server.
73          * 
74          * @param r             the Reader from which to read.
75          * @return              an UpdateInfo construct, or <code>null</code> if the data was invalid.
76          * @throws IOException  if an I/O exception occurs.
77          */
78         /* package-private */
79         static UpdateInfo parseUpdateInput(Reader r) throws IOException {
80                 BufferedReader reader;
81                 if (r instanceof BufferedReader) {
82                         reader = (BufferedReader) r;
83                 } else {
84                         reader = new BufferedReader(r);
85                 }
86                 
87                 
88                 String version = null;
89                 ArrayList<ComparablePair<Integer, String>> updates =
90                                 new ArrayList<ComparablePair<Integer, String>>();
91                 
92                 String str = reader.readLine();
93                 while (str != null) {
94                         if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
95                                 version = str.substring(8).trim();
96                         } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
97                                 int index = str.indexOf(':');
98                                 int value = Integer.parseInt(str.substring(0, index));
99                                 String desc = str.substring(index + 1).trim();
100                                 if (!desc.equals("")) {
101                                         updates.add(new ComparablePair<Integer, String>(value, desc));
102                                 }
103                         }
104                         // Ignore anything else
105                         str = reader.readLine();
106                 }
107                 
108                 if (version != null) {
109                         return new UpdateInfo(version, updates);
110                 } else {
111                         return null;
112                 }
113         }
114         
115         
116         
117         /**
118          * An asynchronous task that fetches and parses the update info.
119          * 
120          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
121          */
122         private class UpdateInfoFetcher extends Thread {
123                 
124                 private volatile UpdateInfo info = null;
125                 
126                 @Override
127                 public void run() {
128                         try {
129                                 doConnection();
130                         } catch (IOException e) {
131                                 log.info("Fetching update failed: " + e);
132                                 return;
133                         }
134                 }
135                 
136                 
137                 private void doConnection() throws IOException {
138                         String url = Communicator.UPDATE_INFO_URL + "?" + Communicator.VERSION_PARAM + "="
139                                         + Communicator.encode(BuildProperties.getVersion());
140                         
141                         HttpURLConnection connection = Communicator.connectionSource.getConnection(url);
142                         
143                         connection.setConnectTimeout(Communicator.CONNECTION_TIMEOUT);
144                         connection.setInstanceFollowRedirects(true);
145                         connection.setRequestMethod("GET");
146                         connection.setUseCaches(false);
147                         connection.setDoInput(true);
148                         connection.setRequestProperty("X-OpenRocket-Version",
149                                         Communicator.encode(BuildProperties.getVersion() + " " + BuildProperties.getBuildSource()));
150                         connection.setRequestProperty("X-OpenRocket-ID",
151                                         Communicator.encode(Application.getPreferences().getUniqueID()));
152                         connection.setRequestProperty("X-OpenRocket-OS",
153                                         Communicator.encode(System.getProperty("os.name") + " " +
154                                                         System.getProperty("os.arch")));
155                         connection.setRequestProperty("X-OpenRocket-Java",
156                                         Communicator.encode(System.getProperty("java.vendor") + " " +
157                                                         System.getProperty("java.version")));
158                         connection.setRequestProperty("X-OpenRocket-Country",
159                                         Communicator.encode(System.getProperty("user.country") + " " +
160                                                         System.getProperty("user.timezone")));
161                         connection.setRequestProperty("X-OpenRocket-Locale",
162                                         Communicator.encode(Locale.getDefault().toString()));
163                         connection.setRequestProperty("X-OpenRocket-CPUs", "" + Runtime.getRuntime().availableProcessors());
164                         
165                         InputStream is = null;
166                         try {
167                                 connection.connect();
168                                 
169                                 log.debug("Update response code: " + connection.getResponseCode());
170                                 
171                                 if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
172                                         // No updates are available
173                                         log.info("No updates available");
174                                         info = new UpdateInfo();
175                                         return;
176                                 }
177                                 
178                                 if (connection.getResponseCode() != Communicator.UPDATE_INFO_UPDATE_AVAILABLE) {
179                                         // Error communicating with server
180                                         log.warn("Unknown server response code: " + connection.getResponseCode());
181                                         return;
182                                 }
183                                 
184                                 String contentType = connection.getContentType();
185                                 if (contentType == null ||
186                                                 contentType.toLowerCase(Locale.ENGLISH).indexOf(Communicator.UPDATE_INFO_CONTENT_TYPE) < 0) {
187                                         // Unknown response type
188                                         log.warn("Unknown Content-type received:" + contentType);
189                                         return;
190                                 }
191                                 
192                                 // Update is available, parse input
193                                 is = connection.getInputStream();
194                                 is = new LimitedInputStream(is, Communicator.MAX_INPUT_BYTES);
195                                 String encoding = connection.getContentEncoding();
196                                 if (encoding == null || encoding.equals(""))
197                                         encoding = "UTF-8";
198                                 BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
199                                 
200                                 String version = null;
201                                 ArrayList<ComparablePair<Integer, String>> updates =
202                                                 new ArrayList<ComparablePair<Integer, String>>();
203                                 
204                                 String line = reader.readLine();
205                                 while (line != null) {
206                                         
207                                         if (line.matches("^Version:[a-zA-Z0-9._ -]{1,30}$")) {
208                                                 version = line.substring(8).trim();
209                                         } else if (line.matches("^[0-9]{1,9}:\\P{Cntrl}{1,300}$")) {
210                                                 String[] split = line.split(":", 2);
211                                                 int n = Integer.parseInt(split[0]);
212                                                 updates.add(new ComparablePair<Integer, String>(n, split[1].trim()));
213                                         }
214                                         // Ignore line otherwise
215                                         line = reader.readLine();
216                                 }
217                                 
218                                 // Check version input
219                                 if (version == null || version.length() == 0 ||
220                                                 version.equalsIgnoreCase(BuildProperties.getVersion())) {
221                                         // Invalid response
222                                         log.warn("Invalid version received, ignoring.");
223                                         return;
224                                 }
225                                 
226                                 
227                                 info = new UpdateInfo(version, updates);
228                                 log.info("Found update: " + info);
229                         } finally {
230                                 try {
231                                         if (is != null)
232                                                 is.close();
233                                         connection.disconnect();
234                                 } catch (Exception e) {
235                                         e.printStackTrace();
236                                 }
237                         }
238                 }
239         }
240 }