Finish adding the snapshots functionality.
This commit is contained in:
parent
e53fc977bc
commit
633fc37892
6 changed files with 475 additions and 5 deletions
|
@ -36,7 +36,8 @@ class MyApp extends StatelessWidget {
|
|||
"/server/mods": (context) => ModManager(settings: appSettings),
|
||||
"/server/mods/edit": (context) => ModPage(),
|
||||
"/server/discord": (context) => DiscordConfigPage(),
|
||||
"/server/snapshots": (context) => SnapshotsPage()
|
||||
"/server/snapshots": (context) => SnapshotsPage(),
|
||||
"/server/snapshots/restore": (context) => SnapshotListPage()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@ import 'package:libac_dart/nbt/NbtUtils.dart';
|
|||
import 'package:libac_dart/nbt/Stream.dart';
|
||||
import 'package:libac_dart/nbt/Tag.dart';
|
||||
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
|
||||
import 'package:libac_dart/nbt/impl/ListTag.dart';
|
||||
import 'package:libac_dart/nbt/impl/StringTag.dart';
|
||||
import 'package:libac_dart/packets/packets.dart';
|
||||
import 'package:libac_dart/utils/Hashing.dart';
|
||||
import 'package:libac_dart/utils/IOTools.dart';
|
||||
import 'package:libac_dart/utils/uuid/NbtUUID.dart';
|
||||
import 'package:libac_dart/utils/uuid/UUID.dart';
|
||||
import 'package:servermanager/statemachine.dart';
|
||||
|
@ -31,6 +33,21 @@ class ClientPackets {
|
|||
reg.register(C2SUploadSettingsPacket(), () {
|
||||
return C2SUploadSettingsPacket();
|
||||
});
|
||||
reg.register(C2SRequestCreateBackup(), () {
|
||||
return C2SRequestCreateBackup();
|
||||
});
|
||||
reg.register(C2SRequestSnapshotList(), () {
|
||||
return C2SRequestSnapshotList();
|
||||
});
|
||||
reg.register(S2CSnapshotList(), () {
|
||||
return S2CSnapshotList();
|
||||
});
|
||||
reg.register(C2SRequestSnapshotDeletion(), () {
|
||||
return C2SRequestSnapshotDeletion();
|
||||
});
|
||||
reg.register(C2SRequestWorldRestore(), () {
|
||||
return C2SRequestWorldRestore();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +266,6 @@ class C2SUploadSettingsPacket implements IPacket {
|
|||
CompoundTag srvSettings = CompoundTag();
|
||||
bool performRestart = false;
|
||||
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
|
@ -300,7 +316,11 @@ class C2SUploadSettingsPacket implements IPacket {
|
|||
settings.Write();
|
||||
|
||||
if (!performRestart) {
|
||||
DiscordHookHelper.sendWebHook(settings.inst!.discord, DiscordHookProps.ONLINE_ALERT, "Server Wrapper Settings", "Server wrapper settings have been updated.\n\n${performRestart ? "A restart has been requested" : "A restart is not needed"}");
|
||||
DiscordHookHelper.sendWebHook(
|
||||
settings.inst!.discord,
|
||||
DiscordHookProps.ONLINE_ALERT,
|
||||
"Server Wrapper Settings",
|
||||
"Server wrapper settings have been updated.\n\n${performRestart ? "A restart has been requested" : "A restart is not needed"}");
|
||||
|
||||
return PacketResponse.nil;
|
||||
}
|
||||
|
@ -333,3 +353,321 @@ class C2SUploadSettingsPacket implements IPacket {
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class C2SRequestCreateBackup implements IPacket {
|
||||
String fileName = ""; // File name of the backup
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {
|
||||
return; // Json not supported by this packet
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {
|
||||
if (tag is CompoundTag) {
|
||||
CompoundTag ct = tag.asCompoundTag();
|
||||
fileName = ct.get("name")!.asString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag ct = CompoundTag();
|
||||
ct.put("name", StringTag.valueOf(fileName));
|
||||
|
||||
return ct;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "C2SRequestCreateBackup";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {
|
||||
// This is not handled on the client at all
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
// Copy the world file to the new destination
|
||||
Settings settings = Settings();
|
||||
|
||||
File world = File(settings.getWorldGameDB());
|
||||
if (world.existsSync()) {
|
||||
// We're good!
|
||||
// Begin copy operations
|
||||
String destinationFile = "$fileName.db";
|
||||
PathHelper pth = PathHelper(pth: settings.getWorldSnapshotFolder())
|
||||
.resolve(destinationFile);
|
||||
world.copy(pth.build());
|
||||
}
|
||||
|
||||
return PacketResponse.nil;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class C2SRequestSnapshotList implements IPacket {
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
return CompoundTag();
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "C2SRequestSnapshotList";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
// Generate the list of all snapshot files
|
||||
Settings settings = Settings();
|
||||
|
||||
List<String> snapshotFileNames = await settings.getWorldSnapshotFiles();
|
||||
S2CResponse response = S2CResponse();
|
||||
S2CSnapshotList snapshots = S2CSnapshotList();
|
||||
|
||||
List<String> strippedFiles = [];
|
||||
for (String str in snapshotFileNames) {
|
||||
if (str.endsWith(".db")) {
|
||||
// add file name without db extension to the list
|
||||
|
||||
String trimmedFileName = str.trim().substring(0, str.trim().length - 3);
|
||||
strippedFiles.add(trimmedFileName);
|
||||
} else
|
||||
strippedFiles.add(str);
|
||||
}
|
||||
|
||||
snapshots.snapshotFileNames = strippedFiles;
|
||||
|
||||
response.contents = snapshots.encodeTag().asCompoundTag();
|
||||
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class S2CSnapshotList implements IPacket {
|
||||
List<String> snapshotFileNames = [];
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {
|
||||
CompoundTag ct = tag.asCompoundTag();
|
||||
|
||||
ListTag lst = ct.get("items") as ListTag;
|
||||
for (int i = 0; i < lst.size(); i++) {
|
||||
snapshotFileNames.add(lst.get(i).asString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ServerToClient;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return "[]";
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag ct = CompoundTag();
|
||||
|
||||
ListTag lst = ListTag();
|
||||
for (String str in snapshotFileNames) {
|
||||
lst.add(StringTag.valueOf(str));
|
||||
}
|
||||
|
||||
ct.put("items", lst);
|
||||
return ct;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "S2CSnapshotList";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {
|
||||
// Oh hey, its us!
|
||||
// Put the list in the SessionData
|
||||
SessionData.IE_SNAPSHOTS = snapshotFileNames;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class C2SRequestSnapshotDeletion implements IPacket {
|
||||
String snapshotName = "";
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {
|
||||
CompoundTag ct = tag.asCompoundTag();
|
||||
snapshotName = ct.get("name")!.asString();
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag ct = CompoundTag();
|
||||
ct.put("name", StringTag.valueOf(snapshotName));
|
||||
|
||||
return ct;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "C2SRequestSnapshotDeletion";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
Settings settings = Settings();
|
||||
|
||||
String correctedName = "$snapshotName.db";
|
||||
PathHelper ph = PathHelper(pth: settings.getWorldSnapshotFolder())
|
||||
.resolve(correctedName);
|
||||
ph.deleteFile();
|
||||
|
||||
return PacketResponse.nil;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class C2SRequestWorldRestore implements IPacket {
|
||||
String snapshot = "";
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {
|
||||
CompoundTag ct = tag.asCompoundTag();
|
||||
snapshot = ct.get("name")!.asString();
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag ct = CompoundTag();
|
||||
ct.put("name", StringTag.valueOf(snapshot));
|
||||
|
||||
return ct;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "C2SRequestWorldRestore";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
SessionData.isWorldRestore = true;
|
||||
SessionData.snapshotToRestore = snapshot;
|
||||
SessionData.shutdownMessage = "A backup restore has been requested";
|
||||
SessionData.timer.apply(30);
|
||||
SessionData.CURRENT_INTERVAL = WarnIntervals.NONE;
|
||||
|
||||
return PacketResponse.nil;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@ class GameServerPageState extends State<GameServerPage> {
|
|||
title: Text("Server Snapshots"),
|
||||
leading: Icon(Icons.photo),
|
||||
subtitle: Text("Manage server database snapshots"),
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, "/server/snapshots");
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text("Configure AutoRestart"),
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:libac_dart/packets/packets.dart';
|
||||
import 'package:servermanager/packets/ClientPackets.dart';
|
||||
import 'package:servermanager/pages/Constants.dart';
|
||||
import 'package:servermanager/pages/dialogbox.dart';
|
||||
import 'package:servermanager/structs/SessionData.dart';
|
||||
import 'package:servermanager/structs/settings.dart';
|
||||
|
||||
class SnapshotsPage extends StatefulWidget {
|
||||
|
@ -31,6 +36,50 @@ class SnapshotsState extends State<SnapshotsPage> {
|
|||
title: Text("Server DB File"),
|
||||
subtitle: Text(settings.gameServerDBFile),
|
||||
),
|
||||
ListTile(
|
||||
title: Text("Create Backup"),
|
||||
subtitle: Text(
|
||||
"This action will prompt for a filename, then contact the server, which will then take a immediate snapshot of the world under that name."),
|
||||
leading: Icon(Icons.backup),
|
||||
onTap: () {
|
||||
String finalValue = "";
|
||||
InputBox ib =
|
||||
InputBox("", promptText: "Backup Name?", changed: (V) {
|
||||
setState(() {
|
||||
finalValue = V;
|
||||
});
|
||||
}, onSubmit: () {
|
||||
// Send the filename to the server and request backup creation
|
||||
C2SRequestCreateBackup rcb = C2SRequestCreateBackup();
|
||||
rcb.fileName = finalValue;
|
||||
|
||||
settings.client!.send(rcb, true);
|
||||
}, onCancel: () {}, isDefault: true);
|
||||
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return ib;
|
||||
});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text("Restore"),
|
||||
subtitle: Text("Restore a backup file to active status"),
|
||||
leading: Icon(Icons.restore),
|
||||
onTap: () async {
|
||||
// Request the snapshot list, then present the restore list to the end user
|
||||
C2SRequestSnapshotList rsl = C2SRequestSnapshotList();
|
||||
S2CResponse reply = await settings.client!.send(rsl, true);
|
||||
S2CSnapshotList snaps = S2CSnapshotList();
|
||||
snaps.decodeTag(reply.contents);
|
||||
await snaps.handleClientPacket();
|
||||
|
||||
if (SessionData.IE_SNAPSHOTS.isNotEmpty) {
|
||||
// Show the list!
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -38,3 +87,58 @@ class SnapshotsState extends State<SnapshotsPage> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SnapshotListPage extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return SnapshotListState();
|
||||
}
|
||||
}
|
||||
|
||||
class SnapshotListState extends State<SnapshotListPage> {
|
||||
Settings settings = Settings();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"Snapshot Manager - Restore - ${SessionData.IE_SNAPSHOTS.length} backups"),
|
||||
backgroundColor: Constants.TITLEBAR_COLOR,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SingleChildScrollView(
|
||||
child: ListView.builder(itemBuilder: (ctx, index) {
|
||||
String filename = SessionData.IE_SNAPSHOTS[index];
|
||||
return ListTile(
|
||||
title: Text(filename),
|
||||
leading: Icon(Icons.photo),
|
||||
subtitle: Text(
|
||||
"No information is known about this snapshot at this time"),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// Send restore packet
|
||||
},
|
||||
icon: Icon(Icons.restore)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// Send deletion packet
|
||||
C2SRequestSnapshotDeletion rsd =
|
||||
C2SRequestSnapshotDeletion();
|
||||
rsd.snapshotName = filename;
|
||||
|
||||
settings.client!.send(rsd, true);
|
||||
},
|
||||
icon: Icon(Icons.delete)),
|
||||
],
|
||||
),
|
||||
);
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,6 +236,19 @@ class StateMachine {
|
|||
{
|
||||
timer.cancel();
|
||||
|
||||
// Check if we should perform a world restore
|
||||
if (SessionData.isWorldRestore) {
|
||||
await DiscordHookHelper.sendWebHook(
|
||||
settings.inst!.discord,
|
||||
DiscordHookProps.OFFLINE_ALERT,
|
||||
"RESTORE",
|
||||
"Restoring backup file: ${SessionData.snapshotToRestore}");
|
||||
|
||||
// Now restore the backup file
|
||||
File backup = File(SessionData.snapshotToRestore);
|
||||
await backup.copy(settings.getWorldGameDB());
|
||||
}
|
||||
|
||||
//Settings settings = Settings();
|
||||
if (settings.inst!.pterodactylMode) {
|
||||
// Shut down the server processes now
|
||||
|
|
|
@ -19,6 +19,9 @@ class SessionData {
|
|||
static Time mod_update_check_tracker = Time(hours: 0, minutes: 0, seconds: 0);
|
||||
static bool enableRestartTimer = false;
|
||||
static bool canPingServer = false;
|
||||
static bool isWorldRestore = false;
|
||||
static String snapshotToRestore =
|
||||
""; // This is the absolute path to the snapshot being restored
|
||||
|
||||
static Time timeSinceLastPing = Time(hours: 0, minutes: 0, seconds: 0);
|
||||
|
||||
|
@ -33,4 +36,13 @@ class SessionData {
|
|||
static bool shouldCheckModUpdates() {
|
||||
return mod_update_check_tracker.minutes >= 30;
|
||||
}
|
||||
|
||||
/// Interactive Editor - Snapshots
|
||||
///
|
||||
/// This contains the list of snapshot files for the editor client GUI.
|
||||
///
|
||||
/// DO NOT USE ON SERVER
|
||||
///
|
||||
/// Use the [Settings.getWorldSnapshotFiles] function instead
|
||||
static List<String> IE_SNAPSHOTS = [];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue