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