Add buttons - Show PAC file, backup config
This commit is contained in:
parent
4cff4ba57f
commit
31721ed7c9
|
@ -7,24 +7,34 @@ import java.io.StringWriter;
|
|||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
import static javafx.scene.control.ButtonType.*;
|
||||
|
||||
@Slf4j
|
||||
public class Dialogs {
|
||||
|
||||
private Dialogs() {}
|
||||
|
@ -43,6 +53,76 @@ public class Dialogs {
|
|||
}
|
||||
}
|
||||
|
||||
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(Scene parent, String header, String text) {
|
||||
Runnable r = () -> {
|
||||
AutosizeAlert alert = new AutosizeAlert(Alert.AlertType.ERROR, parent);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ctbrec.ui.settings;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Hmac;
|
||||
|
@ -12,6 +11,7 @@ import ctbrec.docs.DocServer;
|
|||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.ui.DesktopIntegration;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.SiteUI;
|
||||
import ctbrec.ui.SiteUiFactory;
|
||||
import ctbrec.ui.controls.range.DiscreteRange;
|
||||
|
@ -23,6 +23,7 @@ import ctbrec.ui.tabs.TabSelectionListener;
|
|||
import javafx.animation.FadeTransition;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -30,18 +31,33 @@ import javafx.collections.FXCollections;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
/* import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType; */
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Duration;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.*;
|
||||
|
||||
import static ctbrec.Settings.DirectoryStructure.*;
|
||||
import static ctbrec.Settings.ProxyType.*;
|
||||
|
@ -340,7 +356,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("Port", proxyPort).needsRestart(),
|
||||
Setting.of("Username", proxyUser).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("", createPacButton()))),
|
||||
Category.of("Advanced / Devtools",
|
||||
Group.of("Networking",
|
||||
Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests"),
|
||||
|
@ -363,11 +380,13 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
"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("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory")),
|
||||
Group.of("Miscelaneous",
|
||||
Group.of("Miscellaneous",
|
||||
Setting.of("Config file saving delay (ms)", configSavingDelayMs,
|
||||
"Wait specified number of milliseconds before actually writing config to disk"))),
|
||||
Category.of("Help/Cfg/Log", (new HelpTab()).getContent()),
|
||||
Category.of("Donate", (new DonateTabFx()).getContent()));
|
||||
"Wait specified number of milliseconds before actually writing config to disk")),
|
||||
Group.of("Backup",
|
||||
Setting.of("Backup Config", createBackupConfigButton()))),
|
||||
Category.of("Help/Cfg/Log", (new HelpTab()).getContent()),
|
||||
Category.of("Donate", (new DonateTabFx()).getContent()));
|
||||
Region preferencesView = prefs.getView();
|
||||
prefs.onRestartRequired(this::showRestartRequired);
|
||||
storage.setPreferences(prefs);
|
||||
|
@ -447,6 +466,87 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
return postProcessingHelpButton;
|
||||
}
|
||||
|
||||
private Button createPacButton() {
|
||||
Button openPacButton = new Button("View PAC File");
|
||||
openPacButton.setOnAction(e -> {
|
||||
String url = pacUrl.get();
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
Dialogs.showToast(this.getTabPane().getScene(), "Invalid PAC URL: Please enter a valid PAC URL in the settings.");
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
if (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file")) {
|
||||
Platform.runLater(() -> Dialogs.showToast(this.getTabPane().getScene(), "Invalid PAC URL: Must use http, https, or file scheme."));
|
||||
return;
|
||||
}
|
||||
log.info("Opening PAC file in browser: {}", url);
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(uri);
|
||||
} else {
|
||||
log.info("Desktop.browse not supported, trying xdg-open");
|
||||
ProcessBuilder pb = new ProcessBuilder("xdg-open", url);
|
||||
pb.start();
|
||||
}
|
||||
} catch (URISyntaxException ex) {
|
||||
log.error("Invalid PAC URL format: {}", url, ex);
|
||||
Platform.runLater(() -> Dialogs.showToast(this.getTabPane().getScene(), "Invalid PAC URL: " + ex.getMessage()));
|
||||
} catch (IOException ex) {
|
||||
log.error("Failed to open PAC file in browser: {}", url, ex);
|
||||
Platform.runLater(() -> Dialogs.showToast(this.getTabPane().getScene(), "Failed to open PAC file: " + ex.getMessage()));
|
||||
}
|
||||
}, "PAC-Browser-Thread").start();
|
||||
});
|
||||
openPacButton.disableProperty().bind(pacUrl.isNull().or(pacUrl.isEqualTo("")));
|
||||
return openPacButton;
|
||||
}
|
||||
|
||||
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() {
|
||||
var button = new Button("Variable Playground");
|
||||
button.setOnAction(e -> variablePlayGroundDialogFactory.openDialog(this.getTabPane().getScene(), config, recorder));
|
||||
|
|
Loading…
Reference in New Issue