package org.openslx.dozmod.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.log4j.Logger;
import org.openslx.dozmod.App;
import org.openslx.dozmod.Branding;
import org.openslx.sat.thrift.version.Version;
import org.openslx.util.Json;
public class ClientVersion {
private static final Logger LOGGER = Logger.getLogger(ClientVersion.class);
private static long localRevisionTime = 0;
private static long remoteRevisionTime = 0;
private static String localRevision = "???";
private static String remoteRevision = "???";
private static String changelog = "???";
private static Thread remoteThread = null;
static {
loadLocalVersion();
loadRemoteVersion("https://" + Branding.getMasterServerAddress() + "/dozmod/" + Version.VERSION + "/version.json");
}
/**
* Gets the local revision id if loading it worked, "???" otherwise.
*
* @return id as String
*/
public static String getLocalRevision() {
return localRevision;
}
/**
* @return
*/
public static long getLocalRevTimestamp() {
return localRevisionTime;
}
/**
* Gets the revision id of the latest remote version
*
* @return id as String if loading worked, "???" otherwise
*/
public static String getRemoteRevision() {
waitRemote();
return remoteRevision;
}
/**
* Gets the timestamp of the latest remote version
*
* @return timestamp as Long if loading it worked, 0L otherwise
*/
public static long getRemoteRevTimestamp() {
waitRemote();
return remoteRevisionTime;
}
/**
* Gets the changelog
*
* @return log as String
*/
public static String getChangelog() {
waitRemote();
return changelog;
}
/**
* Checks if we are running latest application version
*
* @return true if there is no newer version, false otherwise
*/
public static boolean isNewest() {
// if either local or remote version is unknown, just pretend there's no update
// as there most likely isn't and we'd just annoy the user
// TODO: Report "fail" state so at least on manual update check we can tell that it failed
waitRemote();
if (localRevisionTime == 0 || remoteRevisionTime == 0)
return true;
return localRevisionTime >= remoteRevisionTime;
}
/**
* Loads the local version information from the jar's MANIFEST.MF
* into the fields 'localRevision' and 'localRevisionTime'
*/
private static void loadLocalVersion() {
Class<ClientVersion> clazz = ClientVersion.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
// Class not from JAR
return;
}
String manifestPath = classPath.replaceAll("![^!]*$", "") + "!/META-INF/MANIFEST.MF";
Manifest manifest = null;
try (InputStream stream = new URL(manifestPath).openStream()) {
manifest = new Manifest(stream);
} catch (Exception e) {
if (manifest == null) {
LOGGER.error("Could not open MANIFEST", e);
return;
}
}
Attributes attributes = manifest.getMainAttributes();
// if attr are null, then we couldn't open the jar's MANIFEST
// since we are probably not in a jar context, just do nothing
if (attributes == null)
return;
String manifestRev = null;
String manifestRevTime = null;
try {
manifestRev = attributes.getValue("Build-Revision");
} catch (Exception e) {
LOGGER.warn("Error while reading revision: ", e);
}
try {
manifestRevTime = attributes.getValue("Build-Revision-Timestamp");
} catch (Exception e) {
LOGGER.warn("Error while reading timestamp: ", e);
}
if (manifestRev != null) {
localRevision = manifestRev;
}
if (manifestRevTime != null) {
try {
// hax since we get milliseconds not seconds
localRevisionTime = Long.valueOf(manifestRevTime) / 1000L;
} catch (NumberFormatException e) {
LOGGER.warn("Build timestamp is not a number!", e);
}
}
}
/**
* Loads the given UrlString as JSON and saves the remote information
* into fields 'remoteRevision' and 'remoteRevisionTime'
*
* The remote JSON should have 'timestamp' and 'revision', like:
* { "timestamp": 1, "revision": 2 }
*/
private static void loadRemoteVersion(final String urlString) {
remoteThread = new Thread(new Runnable() {
@Override
public void run() {
App.waitForInit();
String json = null;
try (InputStream reader = new URL(urlString).openStream()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
buffer.write(reader);
buffer.close();
json = new String(buffer.toByteArray(), StandardCharsets.UTF_8);
} catch (Exception e) {
if (json == null) {
LOGGER.error("Could not fetch remote version", e);
return;
}
}
VersionQuery query = Json.deserialize(json, VersionQuery.class);
remoteRevision = query.revision;
// seconds timestamp here...
remoteRevisionTime = query.timestamp;
changelog = query.changelog;
}
});
remoteThread.start();
}
private static synchronized void waitRemote() {
if (remoteThread == null)
return;
try {
remoteThread.join();
} catch (InterruptedException e) {
}
remoteThread = null;
}
/**
* Class for GSON json parsing
*/
static class VersionQuery {
long timestamp;
String revision;
String changelog;
}
public static void createJson(String name) throws IOException {
loadLocalVersion();
if (localRevisionTime == 0)
throw new RuntimeException("Missing manifest/data in jar: No revision time found");
if (localRevision == null || localRevision.isEmpty())
throw new RuntimeException("Missing manifest/data in jar: No commit hash found");
System.out.println("Please enter change log. To finish, put a '.' on a single line");
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for (String line; (line = br.readLine()) != null;) {
if (line.equals("."))
break;
sb.append(line);
sb.append('\n');
}
VersionQuery vq = new VersionQuery();
vq.timestamp = localRevisionTime;
vq.revision = localRevision;
vq.changelog = sb.toString();
String data = Json.serialize(vq);
FileUtils.writeStringToFile(new File(name), data, StandardCharsets.UTF_8);
System.out.println();
System.out.println("Created json file at " + name);
System.out.println("This build is using Thrift RPC interface version >> " + Version.VERSION + " <<");
System.out.println();
}
}