Added BE auto-updater / Server config / Fixed #1266
This commit is contained in:
@@ -18,11 +18,6 @@ public class Administration{
|
||||
private Array<ChatFilter> chatFilters = new Array<>();
|
||||
|
||||
public Administration(){
|
||||
Core.settings.defaults(
|
||||
"strict", true,
|
||||
"servername", "Server"
|
||||
);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -51,21 +46,12 @@ public class Administration{
|
||||
Core.settings.putSave("playerlimit", limit);
|
||||
}
|
||||
|
||||
public void setStrict(boolean on){
|
||||
Core.settings.putSave("strict", on);
|
||||
}
|
||||
|
||||
public boolean getStrict(){
|
||||
return Core.settings.getBool("strict");
|
||||
return Config.strict.bool();
|
||||
}
|
||||
|
||||
public boolean allowsCustomClients(){
|
||||
return Core.settings.getBool("allow-custom", !headless);
|
||||
}
|
||||
|
||||
public void setCustomClients(boolean allowed){
|
||||
Core.settings.put("allow-custom", allowed);
|
||||
Core.settings.save();
|
||||
return Config.allowCustomClients.bool();
|
||||
}
|
||||
|
||||
/** Call when a player joins to update their information here. */
|
||||
@@ -219,11 +205,7 @@ public class Administration{
|
||||
}
|
||||
|
||||
public boolean isWhitelistEnabled(){
|
||||
return Core.settings.getBool("whitelist", false);
|
||||
}
|
||||
|
||||
public void setWhitelist(boolean enabled){
|
||||
Core.settings.putSave("whitelist", enabled);
|
||||
return Config.whitelist.bool();
|
||||
}
|
||||
|
||||
public boolean isWhitelisted(String id, String usid){
|
||||
@@ -333,6 +315,79 @@ public class Administration{
|
||||
whitelist = Core.settings.getObject("whitelisted", Array.class, Array::new);
|
||||
}
|
||||
|
||||
/** Server configuration definition. Each config value can be a string, boolean or number. */
|
||||
public enum Config{
|
||||
name("The server name as displayed on clients.", "Server", "servername"),
|
||||
port("The port to host on.", Vars.port),
|
||||
autoUpdate("Whether to auto-restart when a new update arrives.", false),
|
||||
crashReport("Whether to send crash reports.", false, "crashreport"),
|
||||
logging("Whether to log everything to files.", true),
|
||||
strict("Whether strict mode is on - corrects positions and prevents duplicate UUIDs.", true),
|
||||
socketInput("Allows a local application to control this server through a local TCP socket.", false, "socket", () -> Events.fire(Trigger.socketConfigChanged)),
|
||||
socketInputPort("The port for socket input.", 6859, () -> Events.fire(Trigger.socketConfigChanged)),
|
||||
socketInputAddress("The bind address for socket input.", "localhost", () -> Events.fire(Trigger.socketConfigChanged)),
|
||||
allowCustomClients("Whether custom clients are allowed to connect.", !headless, "allow-custom"),
|
||||
whitelist("Whether the whitelist is used.", false);
|
||||
|
||||
public static final Config[] all = values();
|
||||
|
||||
public final Object defaultValue;
|
||||
public final String key, description;
|
||||
final Runnable changed;
|
||||
|
||||
Config(String description, Object def){
|
||||
this(description, def, null, null);
|
||||
}
|
||||
|
||||
Config(String description, Object def, String key){
|
||||
this(description, def, key, null);
|
||||
}
|
||||
|
||||
Config(String description, Object def, Runnable changed){
|
||||
this(description, def, null, changed);
|
||||
}
|
||||
|
||||
Config(String description, Object def, String key, Runnable changed){
|
||||
this.description = description;
|
||||
this.key = key == null ? name() : key;
|
||||
this.defaultValue = def;
|
||||
this.changed = changed == null ? () -> {} : changed;
|
||||
}
|
||||
|
||||
public boolean isNum(){
|
||||
return defaultValue instanceof Integer;
|
||||
}
|
||||
|
||||
public boolean isBool(){
|
||||
return defaultValue instanceof Boolean;
|
||||
}
|
||||
|
||||
public boolean isString(){
|
||||
return defaultValue instanceof String;
|
||||
}
|
||||
|
||||
public Object get(){
|
||||
return Core.settings.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public boolean bool(){
|
||||
return Core.settings.getBool(key, (Boolean)defaultValue);
|
||||
}
|
||||
|
||||
public int num(){
|
||||
return Core.settings.getInt(key, (Integer)defaultValue);
|
||||
}
|
||||
|
||||
public String string(){
|
||||
return Core.settings.getString(key, (String)defaultValue);
|
||||
}
|
||||
|
||||
public void set(Object value){
|
||||
Core.settings.putSave(key, value);
|
||||
changed.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Serialize
|
||||
public static class PlayerInfo{
|
||||
public String id;
|
||||
|
||||
168
core/src/mindustry/net/BeControl.java
Normal file
168
core/src/mindustry/net/BeControl.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package mindustry.net;
|
||||
|
||||
import arc.*;
|
||||
import arc.Net.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.util.*;
|
||||
import arc.util.async.*;
|
||||
import arc.util.serialization.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.net.Administration.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Handles control of bleeding edge builds. */
|
||||
public class BeControl{
|
||||
private static final int updateInterval = 60;
|
||||
|
||||
private AsyncExecutor executor = new AsyncExecutor(1);
|
||||
private boolean checkUpdates = true;
|
||||
private boolean updateAvailable;
|
||||
private String updateUrl;
|
||||
private int updateBuild;
|
||||
|
||||
/** @return whether this is a bleeding edge build. */
|
||||
public boolean active(){
|
||||
return Version.type.equals("bleeding-edge");
|
||||
}
|
||||
|
||||
public BeControl(){
|
||||
if(active()){
|
||||
Timer.schedule(() -> {
|
||||
if(checkUpdates && !mobile){
|
||||
checkUpdate(t -> {});
|
||||
}
|
||||
}, 1, updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/** asynchronously checks for updates. */
|
||||
public void checkUpdate(Boolc done){
|
||||
Core.net.httpGet("https://api.github.com/repos/Anuken/MindustryBuilds/releases/latest", res -> {
|
||||
if(res.getStatus() == HttpStatus.OK){
|
||||
Jval val = Jval.read(res.getResultAsString());
|
||||
int newBuild = Strings.parseInt(val.getString("tag_name", "0"));
|
||||
if(newBuild > Version.build){
|
||||
Jval asset = val.get("assets").asArray().find(v -> v.getString("name", "").startsWith(headless ? "Mindustry-BE-Server" : "Mindustry-BE-Desktop"));
|
||||
String url = asset.getString("browser_download_url", "");
|
||||
updateAvailable = true;
|
||||
updateBuild = newBuild;
|
||||
updateUrl = url;
|
||||
showUpdateDialog();
|
||||
Core.app.post(() -> done.get(true));
|
||||
}else{
|
||||
Core.app.post(() -> done.get(false));
|
||||
}
|
||||
}else{
|
||||
Core.app.post(() -> done.get(false));
|
||||
Log.err("Update check responded with: {0}", res.getStatus());
|
||||
}
|
||||
}, error -> {
|
||||
if(!headless){
|
||||
ui.showException(error);
|
||||
}else{
|
||||
error.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @return whether a new update is available */
|
||||
public boolean isUpdateAvailable(){
|
||||
return updateAvailable;
|
||||
}
|
||||
|
||||
/** shows the dialog for updating the game on desktop, or a prompt for doing so on the server */
|
||||
public void showUpdateDialog(){
|
||||
if(!updateAvailable) return;
|
||||
|
||||
if(!headless){
|
||||
ui.showCustomConfirm(Core.bundle.format("be.update", "") + " " + updateBuild, "$be.update.confirm", "$ok", "$be.ignore", () -> {
|
||||
boolean[] cancel = {false};
|
||||
float[] progress = {0};
|
||||
int[] length = {0};
|
||||
Fi file = bebuildDirectory.child("client-be-" + updateBuild + ".jar");
|
||||
|
||||
FloatingDialog dialog = new FloatingDialog("$be.updating");
|
||||
download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> {
|
||||
try{
|
||||
Runtime.getRuntime().exec(new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()});
|
||||
System.exit(0);
|
||||
}catch(IOException e){
|
||||
ui.showException(e);
|
||||
}
|
||||
}, e -> {
|
||||
dialog.hide();
|
||||
ui.showException(e);
|
||||
});
|
||||
|
||||
dialog.cont.add(new Bar(() -> length[0] == 0 ? Core.bundle.get("be.updating") : (int)(progress[0] * length[0]) / 1024/ 1024 + "/" + length[0]/1024/1024 + " MB", () -> Pal.accent, () -> progress[0])).width(400f).height(70f);
|
||||
dialog.buttons.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
|
||||
cancel[0] = true;
|
||||
dialog.hide();
|
||||
}).size(210f, 64f);
|
||||
dialog.setFillParent(false);
|
||||
dialog.show();
|
||||
}, () -> checkUpdates = false);
|
||||
}else{
|
||||
Log.info("&lcA new update is available: &lyBleeding Edge build {0}", updateBuild);
|
||||
if(Config.autoUpdate.bool()){
|
||||
Log.info("&lcAuto-downloading next version...");
|
||||
|
||||
try{
|
||||
Fi source = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
|
||||
Fi dest = source.sibling("server-be-" + updateBuild + ".jar");
|
||||
|
||||
download(updateUrl, dest,
|
||||
len -> Core.app.post(() -> Log.info("&ly| Size: {0} MB.", Strings.fixed((float)len / 1024 / 1024, 2))),
|
||||
progress -> {},
|
||||
() -> false,
|
||||
() -> {
|
||||
Log.info("&lcVersion downloaded, exiting. Note that if you are not using the run-server script, the server will not restart automatically.");
|
||||
dest.copyTo(source);
|
||||
dest.delete();
|
||||
System.exit(2); //this will cause a restart if using the script
|
||||
},
|
||||
Throwable::printStackTrace);
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
checkUpdates = false;
|
||||
//todo server updates
|
||||
}
|
||||
}
|
||||
|
||||
private void download(String furl, Fi dest, Intc length, Floatc progressor, Boolp canceled, Runnable done, Cons<Throwable> error){
|
||||
executor.submit(() -> {
|
||||
try{
|
||||
HttpURLConnection con = (HttpURLConnection)new URL(furl).openConnection();
|
||||
BufferedInputStream in = new BufferedInputStream(con.getInputStream());
|
||||
OutputStream out = dest.write(false, 4096);
|
||||
|
||||
byte[] data = new byte[4096];
|
||||
long size = con.getContentLength();
|
||||
long counter = 0;
|
||||
length.get((int)size);
|
||||
int x;
|
||||
while((x = in.read(data, 0, data.length)) >= 0 && !canceled.get()){
|
||||
counter += x;
|
||||
progressor.get((float)counter / (float)size);
|
||||
out.write(data, 0, x);
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
if(!canceled.get()) done.run();
|
||||
}catch(Throwable e){
|
||||
error.get(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,9 @@ public class CrashSender{
|
||||
exception.printStackTrace();
|
||||
|
||||
//don't create crash logs for custom builds, as it's expected
|
||||
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
|
||||
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))){
|
||||
ret();
|
||||
}
|
||||
|
||||
//attempt to load version regardless
|
||||
if(Version.number == 0){
|
||||
@@ -63,7 +65,7 @@ public class CrashSender{
|
||||
try{
|
||||
//check crash report setting
|
||||
if(!Core.settings.getBool("crashreport", true)){
|
||||
return;
|
||||
ret();
|
||||
}
|
||||
}catch(Throwable ignored){
|
||||
//if there's no settings init we don't know what the user wants but chances are it's an important crash, so send it anyway
|
||||
@@ -72,14 +74,14 @@ public class CrashSender{
|
||||
try{
|
||||
//check any mods - if there are any, don't send reports
|
||||
if(Vars.mods != null && !Vars.mods.list().isEmpty()){
|
||||
return;
|
||||
ret();
|
||||
}
|
||||
}catch(Throwable ignored){
|
||||
}
|
||||
|
||||
//do not send exceptions that occur for versions that can't be parsed
|
||||
if(Version.number == 0){
|
||||
return;
|
||||
ret();
|
||||
}
|
||||
|
||||
boolean netActive = false, netServer = false;
|
||||
@@ -130,12 +132,16 @@ public class CrashSender{
|
||||
while(!sent[0]){
|
||||
Thread.sleep(30);
|
||||
}
|
||||
}catch(InterruptedException ignored){
|
||||
}
|
||||
}catch(InterruptedException ignored){}
|
||||
}catch(Throwable death){
|
||||
death.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ret();
|
||||
}
|
||||
|
||||
private static void ret(){
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static void httpPost(String url, String content, Cons<HttpResponse> success, Cons<Throwable> failure){
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package mindustry.net;
|
||||
|
||||
import arc.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.type.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.maps.Map;
|
||||
import mindustry.net.Administration.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
@@ -62,7 +62,7 @@ public class NetworkIO{
|
||||
}
|
||||
|
||||
public static ByteBuffer writeServerData(){
|
||||
String name = (headless ? Core.settings.getString("servername") : player.name);
|
||||
String name = (headless ? Config.name.string() : player.name);
|
||||
String map = world.getMap() == null ? "None" : world.getMap().name();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(256);
|
||||
|
||||
Reference in New Issue
Block a user