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