diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java index 72117c9c..3900c29f 100644 --- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java +++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java @@ -1,6 +1,7 @@ package ctbrec.ui.settings; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.time.LocalTime; import java.util.List; import java.util.Objects; @@ -25,6 +26,7 @@ import ctbrec.ui.settings.api.Setting; import ctbrec.ui.settings.api.SimpleDirectoryProperty; import ctbrec.ui.settings.api.SimpleFileProperty; import ctbrec.ui.settings.api.SimpleRangeProperty; +import ctbrec.io.BoundField; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ListProperty; @@ -115,13 +117,13 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { optionB.setSelected(!optionA.isSelected()); optionB.setToggleGroup(toggleGroup); optionA.selectedProperty().bindBidirectional(prop); - prop.addListener((obs, oldV, newV) -> saveValue(() -> { - var field = Settings.class.getField(setting.getKey()); - field.set(settings, newV); // NOSONAR - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { - runRestartRequiredCallback(); + prop.addListener((obs, oldV, newV) -> saveValue(() -> { + if (setIfChanged(setting.getKey(), newV)) { + if (setting.doesNeedRestart()) { + runRestartRequiredCallback(); + } + config.save(); } - config.save(); })); var row = new HBox(); row.getChildren().addAll(optionA, optionB); @@ -155,15 +157,15 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { resolutionRange.setHigh(highValue >= 0 ? highValue : values.get(values.size() - 1)); resolutionRange.getLow().addListener((obs, o, n) -> saveValue(() -> { int newV = labels.get(n.intValue()); - var field = Settings.class.getField(rangeProperty.getLowKey()); - field.set(settings, newV); // NOSONAR - config.save(); + if (setIfChanged(rangeProperty.getLowKey(), newV)) { + config.save(); + } })); resolutionRange.getHigh().addListener((obs, o, n) -> saveValue(() -> { int newV = labels.get(n.intValue()); - var field = Settings.class.getField(rangeProperty.getHighKey()); - field.set(settings, newV); // NOSONAR - config.save(); + if (setIfChanged(rangeProperty.getHighKey(), newV)) { + config.save(); + } })); return resolutionRange; } @@ -181,11 +183,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { private Node createFileSelector(Setting setting) { var programSelector = new ProgramSelectionBox(""); programSelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> { - String path = n; - var field = Settings.class.getField(setting.getKey()); - String oldValue = (String) field.get(settings); - if (!Objects.equals(path, oldValue)) { - field.set(settings, path); // NOSONAR + if (setIfChanged(setting.getKey(), n)) { if (setting.doesNeedRestart()) { runRestartRequiredCallback(); } @@ -201,11 +199,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { var directorySelector = new DirectorySelectionBox(""); directorySelector.prefWidth(400); directorySelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> { - String path = n; - var field = Settings.class.getField(setting.getKey()); - String oldValue = (String) field.get(settings); - if (!Objects.equals(path, oldValue)) { - field.set(settings, path); // NOSONAR + if (setIfChanged(setting.getKey(), n)) { if (setting.doesNeedRestart()) { runRestartRequiredCallback(); } @@ -221,10 +215,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { LocalTime time = (LocalTime) setting.getProperty().getValue(); var timePicker = new TimePicker(time); timePicker.valueProperty().addListener((obs, o, n) -> saveValue(() -> { - var field = Settings.class.getField(setting.getKey()); - LocalTime oldValue = (LocalTime) field.get(settings); - if (!Objects.equals(n, oldValue)) { - field.set(settings, n); // NOSONAR + if (setIfChanged(setting.getKey(), n)) { if (setting.doesNeedRestart()) { runRestartRequiredCallback(); } @@ -237,12 +228,12 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { private Node createStringProperty(Setting setting) { var ctrl = new TextField(); ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> { - var field = Settings.class.getField(setting.getKey()); - field.set(settings, newV); // NOSONAR - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { - runRestartRequiredCallback(); + if (setIfChanged(setting.getKey(), newV)) { + if (setting.doesNeedRestart()) { + runRestartRequiredCallback(); + } + config.save(); } - config.save(); })); StringProperty prop = (StringProperty) setting.getProperty(); ctrl.textProperty().bindBidirectional(prop); @@ -256,10 +247,8 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { if (!newV.matches("\\d*")) { ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, "")); } - if (!ctrl.getText().isEmpty()) { - var field = Settings.class.getField(setting.getKey()); - field.set(settings, Integer.parseInt(ctrl.getText())); // NOSONAR - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV) && prefs != null) { + if (!ctrl.getText().isEmpty() && setIfChanged(setting.getKey(), Integer.parseInt(ctrl.getText()))) { + if (setting.doesNeedRestart() && prefs != null) { runRestartRequiredCallback(); } config.save(); @@ -282,12 +271,12 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { if (setting.getConverter() != null) { value = (long) setting.getConverter().convertFrom(value); } - var field = Settings.class.getField(setting.getKey()); - field.set(settings, value); // NOSONAR - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { - runRestartRequiredCallback(); + if (setIfChanged(setting.getKey(), value)) { + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + runRestartRequiredCallback(); + } + config.save(); } - config.save(); } })); Property prop = setting.getProperty(); @@ -298,12 +287,12 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { private Node createBooleanProperty(Setting setting) { var ctrl = new CheckBox(); ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> { - var field = Settings.class.getField(setting.getKey()); - field.set(settings, newV); // NOSONAR - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { - runRestartRequiredCallback(); + if (setIfChanged(setting.getKey(), newV)) { + if (setting.doesNeedRestart()) { + runRestartRequiredCallback(); + } + config.save(); } - config.save(); })); BooleanProperty prop = (BooleanProperty) setting.getProperty(); ctrl.selectedProperty().bindBidirectional(prop); @@ -311,11 +300,10 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private Node createComboBox(Setting setting) throws NoSuchFieldException, IllegalAccessException { + private Node createComboBox(Setting setting) throws IllegalAccessException, NoSuchFieldException { ListProperty listProp = (ListProperty) setting.getProperty(); ComboBox comboBox = new ComboBox(listProp); - var field = Settings.class.getField(setting.getKey()); - Object value = field.get(settings); + Object value = BoundField.of(settings, setting.getKey()).get(); if (StringUtil.isNotBlank(value.toString())) { if (setting.getConverter() != null) { comboBox.getSelectionModel().select(setting.getConverter().convertTo(value)); @@ -325,21 +313,29 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { } comboBox.valueProperty().addListener((obs, oldV, newV) -> saveValue(() -> { LOG.debug("Saving setting {}", setting.getKey()); - if (setting.getConverter() != null) { - field.set(settings, setting.getConverter().convertFrom(newV)); // NOSONAR - } else { - field.set(settings, newV); // NOSONAR + if (setIfChanged(setting.getKey(), setting.getConverter() != null ? setting.getConverter().convertFrom(newV) : newV)) { + if (setting.doesNeedRestart()) { + runRestartRequiredCallback(); + } + config.save(); } - if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { - runRestartRequiredCallback(); - } - config.save(); })); if (setting.getChangeListener() != null) { comboBox.valueProperty().addListener((ChangeListener) setting.getChangeListener()); } return comboBox; } + + + private boolean setIfChanged(String key, Object n) throws IllegalAccessException, NoSuchFieldException, InvocationTargetException { + var field = BoundField.of(settings, key); + var o = field.get(); + if (!Objects.equals(n, o)) { + field.set(n); // NOSONAR + return true; + } + return false; + } private void saveValue(Exec exe) { try { @@ -351,6 +347,6 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { @FunctionalInterface private interface Exec { - public void run() throws IllegalAccessException, IOException, NoSuchFieldException; + public void run() throws IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException; } } diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index 0e548f80..d18d9492 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -60,6 +60,8 @@ public class SettingsTab extends Tab implements TabSelectionListener { private final Settings settings; private boolean initialized = false; + private SimpleStringProperty flaresolverrApiUrl; + private SimpleIntegerProperty flaresolverrTimeoutInMillis; private SimpleStringProperty httpUserAgent; private SimpleStringProperty httpUserAgentMobile; private SimpleIntegerProperty overviewUpdateIntervalInSecs; @@ -144,6 +146,8 @@ public class SettingsTab extends Tab implements TabSelectionListener { } private void initializeProperties() { + flaresolverrApiUrl = new SimpleStringProperty(null, "flaresolverr.apiUrl", settings.flaresolverr.apiUrl); + flaresolverrTimeoutInMillis = new SimpleIntegerProperty(null, "flaresolverr.timeoutInMillis", settings.flaresolverr.timeoutInMillis); httpUserAgent = new SimpleStringProperty(null, "httpUserAgent", settings.httpUserAgent); httpUserAgentMobile = new SimpleStringProperty(null, "httpUserAgentMobile", settings.httpUserAgentMobile); overviewUpdateIntervalInSecs = new SimpleIntegerProperty(null, "overviewUpdateIntervalInSecs", settings.overviewUpdateIntervalInSecs); @@ -258,7 +262,11 @@ public class SettingsTab extends Tab implements TabSelectionListener { Group.of("Browser", Setting.of("Browser", browserOverride), Setting.of("Start parameters", browserParams), - Setting.of("Force use (ignore default browser)", forceBrowserOverride, "Default behaviour will fallback to OS default if the above browser fails"))), + Setting.of("Force use (ignore default browser)", forceBrowserOverride, "Default behaviour will fallback to OS default if the above browser fails")), + + Group.of("Flaresolverr", + Setting.of("API URL", flaresolverrApiUrl), + Setting.of("Request timeout", flaresolverrTimeoutInMillis))), Category.of("Look & Feel", Group.of("Look & Feel", Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart(), @@ -354,6 +362,8 @@ public class SettingsTab extends Tab implements TabSelectionListener { setContent(stackPane); prefs.expandTree(); + prefs.getSetting("flaresolverr.apiUrl").ifPresent(s -> bindEnabledProperty(s, recordLocal.not())); + prefs.getSetting("flaresolverr.timeoutInMillis").ifPresent(s -> bindEnabledProperty(s, recordLocal.not())); prefs.getSetting("httpServer").ifPresent(s -> bindEnabledProperty(s, recordLocal)); prefs.getSetting("httpPort").ifPresent(s -> bindEnabledProperty(s, recordLocal)); prefs.getSetting("servletContext").ifPresent(s -> bindEnabledProperty(s, recordLocal)); diff --git a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java index 4ceee14a..30fba0cd 100644 --- a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java +++ b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java @@ -7,11 +7,7 @@ import ctbrec.ui.settings.SettingsTab; import ctbrec.ui.sites.AbstractConfigUI; import javafx.geometry.Insets; import javafx.scene.Parent; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Label; -import javafx.scene.control.PasswordField; -import javafx.scene.control.TextField; +import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; @@ -103,6 +99,18 @@ public class ChaturbateConfigUi extends AbstractConfigUI { GridPane.setHgrow(requestThrottle, Priority.ALWAYS); GridPane.setColumnSpan(requestThrottle, 2); layout.add(requestThrottle, 1, row++); + + var label = new Label("Use Flaresolverr"); + label.setTooltip(new Tooltip("Use Flaresolverr for solving the Cloudflare challenge. This also overrides the User Agent used for HTTP requests (only for the site)")); + layout.add(label, 0, row); + var flaresolverrToggle = new CheckBox(); + flaresolverrToggle.setSelected(settings.chaturbateUseFlaresolverr); + flaresolverrToggle.setOnAction(e -> { + settings.chaturbateUseFlaresolverr = flaresolverrToggle.isSelected(); + save(); + }); + GridPane.setMargin(flaresolverrToggle, new Insets(0, 0, SettingsTab.CHECKBOX_MARGIN, SettingsTab.CHECKBOX_MARGIN)); + layout.add(flaresolverrToggle, 1, row++); var createAccount = new Button("Create new Account"); createAccount.setOnAction(e -> DesktopIntegration.open(Chaturbate.REGISTRATION_LINK)); diff --git a/common/src/main/java/ctbrec/io/BoundField.java b/common/src/main/java/ctbrec/io/BoundField.java new file mode 100644 index 00000000..9ca891aa --- /dev/null +++ b/common/src/main/java/ctbrec/io/BoundField.java @@ -0,0 +1,32 @@ +package ctbrec.io; + +import java.lang.reflect.*; + +public class BoundField { + public Object object = null; + public Field field = null; + + public BoundField(Object o, Field f) { + object = o; + field = f; + } + + public Object get() throws IllegalAccessException { + return field.get(object); + } + + public void set(Object value) throws IllegalAccessException { + field.set(object, value); + } + + // by-path field resolver (i.e: "a.b.c") + public static BoundField of(Object root, String path) throws NoSuchFieldException, IllegalAccessException { + var keys = path.split("\\."); + var result = new BoundField(root, root.getClass().getField(keys[0])); + for (int i = 1; i