Gridarta Editor
Updater.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 The Gridarta Developers.
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 along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.updater;
21 
22 import java.awt.Component;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InterruptedIOException;
28 import java.io.OutputStream;
29 import java.net.Proxy;
30 import java.net.URL;
31 import java.net.URLConnection;
32 import java.net.UnknownHostException;
33 import java.util.MissingResourceException;
34 import java.util.PropertyResourceBundle;
35 import java.util.ResourceBundle;
36 import java.util.prefs.Preferences;
37 import javax.swing.JOptionPane;
38 import javax.swing.ProgressMonitor;
39 import javax.swing.ProgressMonitorInputStream;
40 import net.sf.gridarta.MainControl;
43 import net.sf.gridarta.utils.Exiter;
44 import net.sf.japi.swing.action.ActionBuilder;
45 import net.sf.japi.swing.action.ActionBuilderFactory;
46 import org.apache.log4j.Category;
47 import org.apache.log4j.Logger;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50 
58 public class Updater implements Runnable {
59 
63  @NotNull
64  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
65 
69  @NotNull
70  private static final Category LOG = Logger.getLogger(Updater.class);
71 
75  @NotNull
76  private static final Preferences PREFERENCES = Preferences.userNodeForPackage(MainControl.class);
77 
81  @NotNull
82  public static final String LAST_UPDATE_KEY = "UpdateTimestamp";
83 
87  @Nullable
88  private final Component parentComponent;
89 
93  @NotNull
94  private final Exiter exiter;
95 
99  @NotNull
100  private final String updateFileName;
101 
105  private static final int BUF_SIZE = 4096;
106 
113  public Updater(@Nullable final Component parentComponent, @NotNull final Exiter exiter, @NotNull final String updateFileName) {
114  this.parentComponent = parentComponent;
115  this.exiter = exiter;
116  this.updateFileName = updateFileName;
117  if (parentComponent != null) {
118  parentComponent.setEnabled(false);
119  }
120  }
121 
122  @Override
123  public void run() {
124  try {
125  final String propUrl = ACTION_BUILDER.getString("update.url");
126  if (propUrl == null) {
127  return;
128  }
129 
130  try (InputStream pin = openStream(propUrl)) {
131  final ResourceBundle updateBundle = new PropertyResourceBundle(pin);
132  final String downloadUrl = updateBundle.getString("update.url");
133  if (downloadUrl == null) {
134  ACTION_BUILDER.showMessageDialog(parentComponent, "updateError", "invalid server response: update.url is missing");
135  return;
136  }
137  final VersionInfo update = new VersionInfo(updateBundle, "update");
139  try {
140  active = new VersionInfo(ResourceBundle.getBundle("build"), "build");
141  } catch (final MissingResourceException e) {
142  ACTION_BUILDER.showMessageDialog(parentComponent, "updateActiveVersionUnavailable");
143  }
144  PREFERENCES.putLong(LAST_UPDATE_KEY, System.currentTimeMillis());
145  if (active == null || update.isNewerThan(active)) {
146  if (askIfUserWantsUpdate(active, update, propUrl, downloadUrl)) {
147  downloadAndInstallUpdate(downloadUrl);
148  }
149  } else {
150  noNewUpdate(active, update, propUrl, downloadUrl);
151  }
152  }
153  } catch (final UnknownHostException e) {
154  ACTION_BUILDER.showMessageDialog(parentComponent, "updateError", e.getLocalizedMessage());
155  } catch (final IOException e) {
156  ACTION_BUILDER.showMessageDialog(parentComponent, "updateError", e);
157  } finally {
158  if (parentComponent != null) {
159  parentComponent.setEnabled(true);
160  }
161  }
162  }
163 
173  private boolean askIfUserWantsUpdate(@Nullable final VersionInfo active, @NotNull final VersionInfo update, @NotNull final String propUrl, @NotNull final String downloadUrl) {
174  return ACTION_BUILDER.showConfirmDialog(parentComponent, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, "updateAvailable", updateFileName, active == null ? "?" : active.version, update.version, active == null ? "?" : active.developer, update.developer, active == null ? "?" : active.timestamp, update.timestamp, propUrl, downloadUrl) == JOptionPane.YES_OPTION;
175  }
176 
184  private void noNewUpdate(@Nullable final VersionInfo active, @NotNull final VersionInfo update, @NotNull final String propUrl, @NotNull final String downloadUrl) {
185  ACTION_BUILDER.showMessageDialog(parentComponent, "updateUnavailable", active == null ? "?" : active.version, update.version, active == null ? "?" : active.developer, update.developer, active == null ? "?" : active.timestamp, update.timestamp, propUrl, downloadUrl);
186  }
187 
192  private void downloadAndInstallUpdate(@NotNull final String url) {
193  //noinspection TooBroadScope
194  final File download = new File(updateFileName + ".tmp"); // TODO: print error message if file already exists
195  //noinspection TooBroadScope
196  final File backup = new File(updateFileName + ".bak");
197  //noinspection TooBroadScope
198  final File orig = new File(updateFileName);
199  try {
200  try (InputStream in = openStream(url)) {
201  try (OutputStream out = new FileOutputStream(download)) {
202  final byte[] buf = new byte[BUF_SIZE];
203  while (true) {
204  final int bytesRead = in.read(buf);
205  if (bytesRead == -1) {
206  break;
207  }
208  out.write(buf, 0, bytesRead);
209  }
210  }
211  }
212  if (/* !backup.delete() || */ !orig.renameTo(backup)) {
213  ACTION_BUILDER.showMessageDialog(parentComponent, "updateFailedNoBackup", updateFileName);
214  } else if (!download.renameTo(orig)) {
215  backup.renameTo(orig);
216  ACTION_BUILDER.showMessageDialog(parentComponent, "updateFailedNoDownload");
217  } else {
218  ACTION_BUILDER.showMessageDialog(parentComponent, "updateRestart", updateFileName);
219  exiter.doExit(0);
220  }
221  } catch (final InterruptedIOException e) {
222  ACTION_BUILDER.showMessageDialog(parentComponent, "updateAborted");
223  } catch (final Exception e) {
224  LOG.warn("updateError", e);
225  ACTION_BUILDER.showMessageDialog(parentComponent, "updateError", e);
226  }
227  }
228 
235  @NotNull
236  // The stream is closed by caller
237  private InputStream openStream(@NotNull final String url) throws IOException {
238  final Proxy proxy = NetPreferences.getProxy();
239  final URLConnection con = new URL(url).openConnection(proxy);
240  final ProgressMonitorInputStream stream = new ProgressMonitorInputStream(parentComponent, ActionBuilderUtils.getString(ACTION_BUILDER, "updateProgress.title"), con.getInputStream());
241  final ProgressMonitor monitor = stream.getProgressMonitor();
242  monitor.setMaximum(con.getContentLength());
243  monitor.setNote(ActionBuilderUtils.getString(ACTION_BUILDER, "updateProgress"));
244  monitor.setMillisToDecideToPopup(10);
245  monitor.setMillisToPopup(10);
246  return stream;
247  }
248 
252  private static class VersionInfo {
253 
258  @NotNull
259  private final String version;
260 
264  @NotNull
265  private final String timestamp;
266 
270  @NotNull
271  private final String developer;
272 
276  @NotNull
277  private static final VersionInfo UNAVAILABLE = new VersionInfo();
278 
282  private VersionInfo() {
283  this("unavailable", "unavailable", "unavailable");
284  }
285 
292  private VersionInfo(@NotNull final String version, @NotNull final String timestamp, @NotNull final String developer) {
293  this.version = version;
294  this.timestamp = timestamp;
295  this.developer = developer;
296  }
297 
306  private VersionInfo(@NotNull final ResourceBundle bundle, @NotNull final String prefix) {
307  this(bundle.getString(prefix + ".number"), bundle.getString(prefix + ".tstamp"), bundle.getString(prefix + ".developer"));
308  }
309 
316  boolean isNewerThan(@NotNull final VersionInfo other) {
317  return this != UNAVAILABLE && (other == UNAVAILABLE || timestamp.compareTo(other.timestamp) > 0);
318  }
319 
320  }
321 
322 }
net.sf.gridarta.updater.Updater.openStream
InputStream openStream(@NotNull final String url)
Opens an input stream on an URL.
Definition: Updater.java:237
net.sf.gridarta.updater.Updater.VersionInfo
Class for holding version information and quickly comparing it.
Definition: Updater.java:252
net.sf.gridarta
Base package of all Gridarta classes.
net.sf
net.sf.gridarta.updater.Updater.VersionInfo.VersionInfo
VersionInfo(@NotNull final String version, @NotNull final String timestamp, @NotNull final String developer)
Private constructor to map the strings.
Definition: Updater.java:292
net.sf.gridarta.updater.Updater.LAST_UPDATE_KEY
static final String LAST_UPDATE_KEY
The preferences key for last update.
Definition: Updater.java:82
net.sf.gridarta.gui.dialog.prefs.NetPreferences
Preferences Module for networking preferences.
Definition: NetPreferences.java:55
net.sf.gridarta.updater.Updater.run
void run()
Definition: Updater.java:123
net.sf.gridarta.gui
Graphical User Interface of Gridarta.
net.sf.gridarta.updater.Updater.exiter
final Exiter exiter
The Exiter for terminating the application.
Definition: Updater.java:94
net.sf.gridarta.updater.Updater.VersionInfo.version
final String version
Update information: Version of update version, usually the build number.
Definition: Updater.java:259
net.sf.gridarta.gui.dialog.prefs
The preferences ui modules.
Definition: AppPreferences.java:20
net
net.sf.gridarta.updater.Updater.BUF_SIZE
static final int BUF_SIZE
The buffer size in bytes.
Definition: Updater.java:105
net.sf.gridarta.updater.Updater.VersionInfo.VersionInfo
VersionInfo(@NotNull final ResourceBundle bundle, @NotNull final String prefix)
Creates update information from a ResourceBundle.
Definition: Updater.java:306
net.sf.gridarta.updater.Updater.VersionInfo.developer
final String developer
Update information: Developer that created the update version.
Definition: Updater.java:271
net.sf.gridarta.updater.Updater.LOG
static final Category LOG
The logger.
Definition: Updater.java:70
net.sf.gridarta.utils.ActionBuilderUtils.getString
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
Definition: ActionBuilderUtils.java:71
net.sf.gridarta.utils.Exiter.doExit
void doExit(int returnCode)
Exits the application.
net.sf.gridarta.gui.dialog
net.sf.gridarta.updater.Updater.Updater
Updater(@Nullable final Component parentComponent, @NotNull final Exiter exiter, @NotNull final String updateFileName)
Creates a new instance.
Definition: Updater.java:113
net.sf.gridarta.updater.Updater.VersionInfo.isNewerThan
boolean isNewerThan(@NotNull final VersionInfo other)
Checks whether this version is newer than another version.
Definition: Updater.java:316
net.sf.gridarta.updater.Updater.parentComponent
final Component parentComponent
The parent component to show dialogs on.
Definition: Updater.java:88
net.sf.gridarta.updater.Updater.noNewUpdate
void noNewUpdate(@Nullable final VersionInfo active, @NotNull final VersionInfo update, @NotNull final String propUrl, @NotNull final String downloadUrl)
Tells the user there is no update.
Definition: Updater.java:184
net.sf.gridarta.updater.Updater.askIfUserWantsUpdate
boolean askIfUserWantsUpdate(@Nullable final VersionInfo active, @NotNull final VersionInfo update, @NotNull final String propUrl, @NotNull final String downloadUrl)
Asks the user whether he wants to update.
Definition: Updater.java:173
net.sf.gridarta.updater.Updater.updateFileName
final String updateFileName
The file to update.
Definition: Updater.java:100
net.sf.gridarta.utils.ActionBuilderUtils
Utility class for ActionBuilder related functions.
Definition: ActionBuilderUtils.java:31
net.sf.gridarta.updater.Updater.VersionInfo.timestamp
final String timestamp
Update information: Time stamp of update version.
Definition: Updater.java:265
net.sf.gridarta.updater.Updater.VersionInfo.VersionInfo
VersionInfo()
Private constructor used for unavailable versions.
Definition: Updater.java:282
net.sf.gridarta.updater.Updater
This class handles updating the map editor.
Definition: Updater.java:58
net.sf.gridarta.gui.dialog.prefs.NetPreferences.getProxy
static Proxy getProxy()
Returns the currently preferred proxy.
Definition: NetPreferences.java:198
net.sf.gridarta.utils
Definition: ActionBuilderUtils.java:20
net.sf.gridarta.updater.Updater.downloadAndInstallUpdate
void downloadAndInstallUpdate(@NotNull final String url)
Downloads and installs an update.
Definition: Updater.java:192
net.sf.gridarta.updater.Updater.PREFERENCES
static final Preferences PREFERENCES
The preferences.
Definition: Updater.java:76
net.sf.gridarta.updater.Updater.ACTION_BUILDER
static final ActionBuilder ACTION_BUILDER
The action builder to create Actions.
Definition: Updater.java:64
net.sf.gridarta.updater.Updater.VersionInfo.UNAVAILABLE
static final VersionInfo UNAVAILABLE
Special Version "unavailable".
Definition: Updater.java:277
net.sf.gridarta.utils.Exiter
Exits the application.
Definition: Exiter.java:28
net.sf.gridarta.MainControl
Interface used as preferences location.
Definition: MainControl.java:27