Add Backup Config button

This commit is contained in:
Jafea7 2025-09-28 14:00:36 +10:00
parent bd02d27238
commit 11d7bfcdca
2 changed files with 186 additions and 2 deletions

View File

@ -7,24 +7,34 @@ import java.io.StringWriter;
import java.util.Collection; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import javafx.animation.FadeTransition;
import javafx.animation.PauseTransition;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Duration;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream; import java.io.InputStream;
import java.util.Optional; import java.util.Optional;
import static javafx.scene.control.ButtonType.*; import static javafx.scene.control.ButtonType.*;
@Slf4j
public class Dialogs { public class Dialogs {
private Dialogs() {} private Dialogs() {}
@ -35,6 +45,76 @@ public class Dialogs {
Dialogs.scene = scene; Dialogs.scene = scene;
} }
public static void showToast(Scene parent, String message) {
if (parent == null) {
throw new IllegalArgumentException("Scene cannot be null for showing toast");
}
Runnable r = () -> {
Label toast = new Label(message);
toast.setStyle("-fx-background-color: #323232; -fx-text-fill: white; -fx-padding: 10px; -fx-border-radius: 5px; -fx-background-radius: 5px;");
toast.setOpacity(0);
toast.setMaxWidth(Double.MAX_VALUE);
toast.setAlignment(Pos.CENTER);
Node root = parent.getRoot();
if (root instanceof StackPane) {
StackPane parentPane = (StackPane) root;
parentPane.getChildren().add(toast);
StackPane.setAlignment(toast, Pos.BOTTOM_CENTER);
StackPane.setMargin(toast, new Insets(0, 0, 40, 0));
} else if (root instanceof BorderPane) {
BorderPane parentPane = (BorderPane) root;
parentPane.setBottom(toast);
BorderPane.setAlignment(toast, Pos.BOTTOM_CENTER);
BorderPane.setMargin(toast, new Insets(0, 0, 40, 0));
} else if (root instanceof Pane) {
Pane parentPane = (Pane) root;
parentPane.getChildren().add(toast);
toast.setLayoutX((parentPane.getWidth() - toast.getWidth()) / 2);
toast.setLayoutY(parentPane.getHeight() - 40 - toast.getHeight());
} else {
log.error("Unsupported root node type for toast: " + root.getClass().getSimpleName());
return;
}
FadeTransition fadeIn = new FadeTransition(Duration.millis(500), toast);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
FadeTransition fadeOut = new FadeTransition(Duration.millis(500), toast);
fadeOut.setFromValue(1);
fadeOut.setToValue(0);
fadeIn.setOnFinished(e -> {
PauseTransition pause = new PauseTransition(Duration.seconds(3));
pause.setOnFinished(ev -> {
fadeOut.play();
});
pause.play();
});
fadeOut.setOnFinished(e -> {
if (root instanceof StackPane) {
((StackPane) root).getChildren().remove(toast);
} else if (root instanceof BorderPane) {
((BorderPane) root).setBottom(null);
} else if (root instanceof Pane) {
((Pane) root).getChildren().remove(toast);
}
});
fadeIn.play();
};
if (Platform.isFxApplicationThread()) {
r.run();
} else {
Platform.runLater(r);
}
}
public static void showError(String header, String text, Throwable t) { public static void showError(String header, String text, Throwable t) {
if (Objects.nonNull(t)) { if (Objects.nonNull(t)) {
Dialogs.showError(scene, header, text, t); Dialogs.showError(scene, header, text, t);

View File

@ -10,6 +10,7 @@ import ctbrec.docs.DocServer;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.ui.DesktopIntegration; import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.SiteUI; import ctbrec.ui.SiteUI;
import ctbrec.ui.SiteUiFactory; import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.controls.range.DiscreteRange; import ctbrec.ui.controls.range.DiscreteRange;
@ -32,16 +33,26 @@ import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextInputDialog; import javafx.scene.control.TextInputDialog;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.awt.Desktop;
import java.io.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.zip.*;
import static ctbrec.Settings.DirectoryStructure.*; import static ctbrec.Settings.DirectoryStructure.*;
import static ctbrec.Settings.ProxyType.*; import static ctbrec.Settings.ProxyType.*;
@ -228,6 +239,18 @@ public class SettingsTab extends Tab implements TabSelectionListener {
.ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel))); .ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel)));
} }
Button openPacButton = new Button("View PAC File");
openPacButton.setOnAction(e -> {
String url = pacUrl.get();
if (url != null && !url.isEmpty()) {
try {
Desktop.getDesktop().browse(new URI(url));
} catch (Exception ex) {
log.error("Could not open PAC file in browser", ex);
}
}
});
var storage = new CtbrecPreferencesStorage(config); var storage = new CtbrecPreferencesStorage(config);
var prefs = Preferences.of(storage, var prefs = Preferences.of(storage,
Category.of("General", Category.of("General",
@ -323,7 +346,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
Setting.of("Port", proxyPort).needsRestart(), Setting.of("Port", proxyPort).needsRestart(),
Setting.of("Username", proxyUser).needsRestart(), Setting.of("Username", proxyUser).needsRestart(),
Setting.of("Password", proxyPassword).needsRestart(), Setting.of("Password", proxyPassword).needsRestart(),
Setting.of("PAC URL", pacUrl, "URL to your Proxy Auto-Config (PAC) file (e.g. http://example.com/pac.js or file:///G:/path/to/pac.js)").needsRestart())), Setting.of("PAC URL", pacUrl, "URL to your Proxy Auto-Config (PAC) file (e.g. http://example.com/pac.js or file:///G:/path/to/pac.js)").needsRestart(),
Setting.of("", openPacButton))),
Category.of("Advanced / Devtools", Category.of("Advanced / Devtools",
Group.of("Networking", Group.of("Networking",
Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")), Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")),
@ -335,7 +359,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
Setting.of("Use hlsdl (if possible)", useHlsdl, Setting.of("Use hlsdl (if possible)", useHlsdl,
"Use hlsdl to record the live streams. Some features might not work correctly."), "Use hlsdl to record the live streams. Some features might not work correctly."),
Setting.of("hlsdl executable", hlsdlExecutable, "Path to the hlsdl executable"), Setting.of("hlsdl executable", hlsdlExecutable, "Path to the hlsdl executable"),
Setting.of("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory"))), Setting.of("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory")),
Group.of("Backup",
Setting.of("Backup Config", createBackupConfigButton()))),
Category.of("Help/Cfg/Log", (new HelpTab()).getContent()), Category.of("Help/Cfg/Log", (new HelpTab()).getContent()),
Category.of("Donate", (new DonateTabFx()).getContent())); Category.of("Donate", (new DonateTabFx()).getContent()));
Region preferencesView = prefs.getView(); Region preferencesView = prefs.getView();
@ -415,6 +441,51 @@ public class SettingsTab extends Tab implements TabSelectionListener {
return postProcessingHelpButton; return postProcessingHelpButton;
} }
private Button createBackupConfigButton() {
var button = new Button("Backup Config");
button.setTooltip(new Tooltip("Excludes recordings, cache, and backup configs."));
button.setOnAction(e -> {
Path configDir = Config.getInstance().getConfigDir().toPath();
Path rootDir = configDir.getParent();
String backupName = "config_backup_" + System.currentTimeMillis() + ".zip";
Path backupZip = Paths.get(System.getProperty("user.dir")).resolve(backupName);
try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(backupZip))) {
Files.walk(rootDir)
.filter(path -> !path.toString().contains("_backup_"))
.filter(path -> !path.toString().contains("cache"))
.filter(path -> !path.toString().contains("recordings"))
.forEach(path -> {
Path relativePath = rootDir.relativize(path);
try {
if (Files.isDirectory(path)) {
String dirEntry = relativePath.toString() + "/";
if (!dirEntry.equals("./")) {
ZipEntry entry = new ZipEntry(dirEntry);
zos.putNextEntry(entry);
zos.closeEntry();
}
} else {
ZipEntry entry = new ZipEntry(relativePath.toString());
zos.putNextEntry(entry);
try (InputStream is = Files.newInputStream(path)) {
is.transferTo(zos);
}
zos.closeEntry();
}
} catch (IOException ex) {
log.error("Error adding path to backup: " + path, ex);
}
});
log.info("Config backup created: " + backupZip);
Dialogs.showToast(this.getTabPane().getScene(), "Backup finished: " + backupZip.getFileName());
} catch (IOException ex) {
log.error("Failed to create config backup", ex);
Dialogs.showToast(this.getTabPane().getScene(), "Backup failed!");
}
});
return button;
}
private Button createVariablePlayGroundButton() { private Button createVariablePlayGroundButton() {
var button = new Button("Variable Playground"); var button = new Button("Variable Playground");
button.setOnAction(e -> variablePlayGroundDialogFactory.openDialog(this.getTabPane().getScene(), config, recorder)); button.setOnAction(e -> variablePlayGroundDialogFactory.openDialog(this.getTabPane().getScene(), config, recorder));
@ -600,6 +671,39 @@ public class SettingsTab extends Tab implements TabSelectionListener {
} }
} }
private void showToast(String message) {
Label toast = new Label(message);
toast.setStyle("-fx-background-color: #323232; -fx-text-fill: white; -fx-padding: 10px; -fx-border-radius: 5px; -fx-background-radius: 5px;");
toast.setOpacity(0);
StackPane parent = (StackPane) getContent();
toast.setMaxWidth(Double.MAX_VALUE);
toast.setAlignment(Pos.CENTER);
parent.getChildren().add(toast);
StackPane.setAlignment(toast, Pos.BOTTOM_CENTER);
StackPane.setMargin(toast, new Insets(0, 0, 40, 0));
FadeTransition fadeIn = new FadeTransition(Duration.millis(500), toast);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
FadeTransition fadeOut = new FadeTransition(Duration.millis(500), toast);
fadeOut.setFromValue(1);
fadeOut.setToValue(0);
fadeIn.setOnFinished(e -> {
PauseTransition pause = new PauseTransition(Duration.seconds(3));
pause.setOnFinished(ev -> fadeOut.play());
pause.play();
});
fadeOut.setOnFinished(e -> parent.getChildren().remove(toast));
fadeIn.play();
}
public record SplitBiggerThanOption(String label, @Getter long value) { public record SplitBiggerThanOption(String label, @Getter long value) {
@Override @Override