a80eb4fafdb6fcd978707d277db4fbbe5bdb624a
[jSite.git] / src / de / todesbaum / jsite / application / UpdateChecker.java
1 /*
2  * jSite-remote - UpdateChecker.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package de.todesbaum.jsite.application;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Properties;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import de.todesbaum.jsite.main.Main;
31 import de.todesbaum.jsite.main.Version;
32 import de.todesbaum.util.freenet.fcp2.Client;
33 import de.todesbaum.util.freenet.fcp2.ClientGet;
34 import de.todesbaum.util.freenet.fcp2.Connection;
35 import de.todesbaum.util.freenet.fcp2.Message;
36 import de.todesbaum.util.freenet.fcp2.Persistence;
37 import de.todesbaum.util.freenet.fcp2.ReturnType;
38 import de.todesbaum.util.freenet.fcp2.Verbosity;
39 import de.todesbaum.util.io.Closer;
40
41 /**
42  * Checks for newer versions of jSite.
43  *
44  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
45  */
46 public class UpdateChecker implements Runnable {
47
48         /** The logger. */
49         private static final Logger logger = Logger.getLogger(UpdateChecker.class.getName());
50
51         /** Counter for connection names. */
52         private static int counter = 0;
53
54         /** The edition for the update check URL. */
55         private static final int UPDATE_EDITION = 11;
56
57         /** The URL for update checks. */
58         private static final String UPDATE_KEY = "USK@e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE";
59
60         /** Object used for synchronization. */
61         private final Object syncObject = new Object();
62
63         /** Update listeners. */
64         private final List<UpdateListener> updateListeners = new ArrayList<UpdateListener>();
65
66         /** Whether the main thread should stop. */
67         private boolean shouldStop = false;
68
69         /** Current last found edition of update key. */
70         private int lastUpdateEdition = UPDATE_EDITION;
71
72         /** Last found version. */
73         private Version lastVersion = Main.getVersion();
74
75         /** The freenet interface. */
76         private final Freenet7Interface freenetInterface;
77
78         /**
79          * Creates a new update checker that uses the given frame as its parent and
80          * communications via the given freenet interface.
81          *
82          * @param freenetInterface
83          *            The freenet interface
84          */
85         public UpdateChecker(Freenet7Interface freenetInterface) {
86                 this.freenetInterface = freenetInterface;
87         }
88
89         //
90         // EVENT LISTENER MANAGEMENT
91         //
92
93         /**
94          * Adds an update listener to the list of registered listeners.
95          *
96          * @param updateListener
97          *            The update listener to add
98          */
99         public void addUpdateListener(UpdateListener updateListener) {
100                 updateListeners.add(updateListener);
101         }
102
103         /**
104          * Removes the given listener from the list of registered listeners.
105          *
106          * @param updateListener
107          *            The update listener to remove
108          */
109         public void removeUpdateListener(UpdateListener updateListener) {
110                 updateListeners.remove(updateListener);
111         }
112
113         /**
114          * Notifies all listeners that a version was found.
115          *
116          * @param foundVersion
117          *            The version that was found
118          * @param versionTimestamp
119          *            The timestamp of the version
120          */
121         protected void fireUpdateFound(Version foundVersion, long versionTimestamp) {
122                 for (UpdateListener updateListener : updateListeners) {
123                         updateListener.foundUpdateData(foundVersion, versionTimestamp);
124                 }
125         }
126
127         //
128         // ACCESSORS
129         //
130
131         /**
132          * Returns the latest version that was found.
133          *
134          * @return The latest found version
135          */
136         public Version getLatestVersion() {
137                 return lastVersion;
138         }
139
140         //
141         // ACTIONS
142         //
143
144         /**
145          * Starts the update checker.
146          */
147         public void start() {
148                 new Thread(this).start();
149         }
150
151         /**
152          * Stops the update checker.
153          */
154         public void stop() {
155                 synchronized (syncObject) {
156                         shouldStop = true;
157                         syncObject.notifyAll();
158                 }
159         }
160
161         //
162         // PRIVATE METHODS
163         //
164
165         /**
166          * Returns whether the update checker should stop.
167          *
168          * @return <code>true</code> if the update checker should stop,
169          *         <code>false</code> otherwise
170          */
171         private boolean shouldStop() {
172                 synchronized (syncObject) {
173                         return shouldStop;
174                 }
175         }
176
177         /**
178          * Creates the URI of the update file for the given edition.
179          *
180          * @param edition
181          *            The edition number
182          * @return The URI for the update file for the given edition
183          */
184         private String constructUpdateKey(int edition) {
185                 return UPDATE_KEY + "/jSite/" + edition + "/jSite.properties";
186         }
187
188         //
189         // INTERFACE Runnable
190         //
191
192         /**
193          * {@inheritDoc}
194          */
195         public void run() {
196                 Connection connection = freenetInterface.getConnection("jSite-" + ++counter + "-UpdateChecker");
197                 try {
198                         connection.connect();
199                 } catch (IOException e1) {
200                         e1.printStackTrace();
201                 }
202                 Client client = new Client(connection);
203                 boolean checkNow = false;
204                 int currentEdition = lastUpdateEdition;
205                 while (!shouldStop()) {
206                         checkNow = false;
207                         logger.log(Level.FINE, "Trying " + constructUpdateKey(currentEdition));
208                         ClientGet clientGet = new ClientGet("get-update-key");
209                         clientGet.setUri(constructUpdateKey(currentEdition));
210                         clientGet.setPersistence(Persistence.CONNECTION);
211                         clientGet.setReturnType(ReturnType.direct);
212                         clientGet.setVerbosity(Verbosity.ALL);
213                         try {
214                                 client.execute(clientGet);
215                                 boolean stop = false;
216                                 while (!stop) {
217                                         Message message = client.readMessage();
218                                         logger.log(Level.FINEST, "Received message: " + message);
219                                         if ("GetFailed".equals(message.getName())) {
220                                                 if ("27".equals(message.get("code"))) {
221                                                         String editionString = message.get("redirecturi").split("/")[2];
222                                                         int editionNumber = -1;
223                                                         try {
224                                                                 editionNumber = Integer.parseInt(editionString);
225                                                         } catch (NumberFormatException nfe1) {
226                                                                 /* ignore. */
227                                                         }
228                                                         if (editionNumber != -1) {
229                                                                 logger.log(Level.INFO, "Found new edition " + editionNumber);
230                                                                 currentEdition = editionNumber;
231                                                                 lastUpdateEdition = editionNumber;
232                                                                 checkNow = true;
233                                                                 break;
234                                                         }
235                                                 }
236                                         }
237                                         if ("AllData".equals(message.getName())) {
238                                                 logger.log(Level.FINE, "Update data found.");
239                                                 InputStream dataInputStream = null;
240                                                 Properties properties = new Properties();
241                                                 try {
242                                                         dataInputStream = message.getPayloadInputStream();
243                                                         properties.load(dataInputStream);
244                                                 } finally {
245                                                         Closer.close(dataInputStream);
246                                                 }
247
248                                                 String foundVersionString = properties.getProperty("jSite.Version");
249                                                 if (foundVersionString != null) {
250                                                         Version foundVersion = Version.parse(foundVersionString);
251                                                         if (foundVersion != null) {
252                                                                 lastVersion = foundVersion;
253                                                                 String versionTimestampString = properties.getProperty("jSite.Date");
254                                                                 logger.log(Level.FINEST, "Version timestamp: " + versionTimestampString);
255                                                                 long versionTimestamp = -1;
256                                                                 try {
257                                                                         versionTimestamp = Long.parseLong(versionTimestampString);
258                                                                 } catch (NumberFormatException nfe1) {
259                                                                         /* ignore. */
260                                                                 }
261                                                                 fireUpdateFound(foundVersion, versionTimestamp);
262                                                                 stop = true;
263                                                                 checkNow = true;
264                                                                 ++currentEdition;
265                                                         }
266                                                 }
267                                         }
268                                 }
269                         } catch (IOException e) {
270                                 logger.log(Level.INFO, "Got IOException: " + e.getMessage());
271                                 e.printStackTrace();
272                         }
273                         if (!checkNow && !shouldStop()) {
274                                 synchronized (syncObject) {
275                                         try {
276                                                 syncObject.wait(15 * 60 * 1000);
277                                         } catch (InterruptedException ie1) {
278                                                 /* ignore. */
279                                         }
280                                 }
281                         }
282                 }
283         }
284
285 }